diff --git a/ocrmypdf/exec/tesseract.py b/ocrmypdf/exec/tesseract.py index 30ce5b92..21e2cc4f 100644 --- a/ocrmypdf/exec/tesseract.py +++ b/ocrmypdf/exec/tesseract.py @@ -11,6 +11,7 @@ from ..helpers import page_number from . import get_program from collections import namedtuple from textwrap import dedent +import PyPDF2 as pypdf from subprocess import Popen, PIPE, CalledProcessError, \ TimeoutExpired, check_output, STDOUT, DEVNULL @@ -272,6 +273,24 @@ def generate_hocr(input_file, output_hocr, language: list, engine_mode, f_out.write(line) +def use_skip_page(text_only, skip_pdf, output_pdf): + if not text_only: + os.symlink(skip_pdf, output_pdf) + return + + # For text only we must create a blank page with dimensions identical + # to the skip page because this is equivalent to a page with no text + + pdf_in = pypdf.PdfFileReader(skip_pdf) + page0 = pdf_in.pages[0] + + with open(output_pdf, 'wb') as out: + pdf_out = pypdf.PdfFileWriter() + w, h = page0.mediaBox.getWidth(), page0.mediaBox.getHeight() + pdf_out.addBlankPage(w, h) + pdf_out.write(out) + + def generate_pdf(input_image, skip_pdf, output_pdf, language: list, engine_mode, text_only: bool, tessconfig: list, timeout: float, pagesegmode: int, log): @@ -309,14 +328,14 @@ def generate_pdf(input_image, skip_pdf, output_pdf, language: list, universal_newlines=True, timeout=timeout) except TimeoutExpired: page_timedout(log, input_image) - shutil.copy(skip_pdf, output_pdf) + use_skip_page(text_only, skip_pdf, output_pdf) except CalledProcessError as e: tesseract_log_output(log, e.output, input_image) if 'read_params_file: parameter not found' in e.output: raise TesseractConfigError() from e if 'Image too large' in e.output: - shutil.copy(skip_pdf, output_pdf) + use_skip_page(text_only, skip_pdf, output_pdf) return raise e from e else: diff --git a/ocrmypdf/pipeline.py b/ocrmypdf/pipeline.py index 08a688a8..2281115a 100644 --- a/ocrmypdf/pipeline.py +++ b/ocrmypdf/pipeline.py @@ -773,7 +773,8 @@ def generate_postscript_stub( def skip_page( input_file, output_file, - log): + log, + context): # The purpose of this step is its filter to forward only the skipped # files (.skip.oriented.pdf) while disregarding the processed ones # (.ocr.oriented.pdf). Alternative would be for merge_pages to filter @@ -1048,7 +1049,7 @@ def build_pipeline(options, work_folder, log, context): filter=suffix('.skip.oriented.pdf'), output='.done.pdf', output_dir=work_folder, - extras=[log]) + extras=[log, context]) # Merge pages task_merge_pages_ghostscript = main_pipeline.merge( diff --git a/tests/test_tess4.py b/tests/test_tess4.py index c46e0f5b..e1d71294 100644 --- a/tests/test_tess4.py +++ b/tests/test_tess4.py @@ -6,11 +6,42 @@ from ocrmypdf.exceptions import ExitCode from ocrmypdf.exec import tesseract from ocrmypdf import pageinfo import sys +import os +import PyPDF2 as pypdf + + +spoof = pytest.helpers.spoof + + +def tess4_possible_location(): + """The location of tesseract 4 may be OCRMYPDF_TESS4, OCRMYPDF_TESSERACT, + or the installed version on PATH.""" + return os.environ.get('OCRMYPDF_TESS4') or \ + os.environ.get('OCRMYPDF_TESSERACT') or \ + 'tesseract' + + +@pytest.fixture +def ensure_tess4(): + return spoof(tesseract=tess4_possible_location()) + + +def tess4_available(): + """Check if a tesseract 4 binary is available, even if it's not the + official "tesseract" on PATH + + """ + old_environ = os.environ.copy() + try: + os.environ['OCRMYPDF_TESSERACT'] = tess4_possible_location() + return tesseract.v4() and tesseract.has_textonly_pdf() + finally: + os.environ = old_environ # Skip all tests in this file if not tesseract 4 pytestmark = pytest.mark.skipif( - not (tesseract.v4() and tesseract.has_textonly_pdf()), + not tess4_available(), reason="tesseract 4.0 with textonly_pdf feature required") check_ocrmypdf = pytest.helpers.check_ocrmypdf @@ -18,14 +49,15 @@ run_ocrmypdf = pytest.helpers.run_ocrmypdf spoof = pytest.helpers.spoof -def test_textonly_pdf(resources, outdir): +def test_textonly_pdf(ensure_tess4, resources, outdir): check_ocrmypdf( resources / 'linn.pdf', - outdir / 'linn_textonly.pdf', '--pdf-renderer', 'tess4') + outdir / 'linn_textonly.pdf', '--pdf-renderer', 'tess4', + env=ensure_tess4) @pytest.mark.skipif(sys.version_info < (3, 5), reason="needs math.isclose") -def test_pagesize_consistency_tess4(resources, outpdf): +def test_pagesize_consistency_tess4(ensure_tess4, resources, outpdf): from math import isclose infile = resources / 'linn.pdf' @@ -35,9 +67,33 @@ def test_pagesize_consistency_tess4(resources, outpdf): check_ocrmypdf( infile, outpdf, '--pdf-renderer', 'tess4', - '--clean', '--deskew', '--remove-background', '--clean-final') + '--clean', '--deskew', '--remove-background', '--clean-final', + env=ensure_tess4) after_dims = pytest.helpers.first_page_dimensions(outpdf) assert isclose(before_dims[0], after_dims[0]) assert isclose(before_dims[1], after_dims[1]) + + +@pytest.mark.parametrize('basename', ['graph_ocred.pdf', 'cardinal.pdf']) +def test_skip_pages_does_not_replicate( + ensure_tess4, resources, basename, outdir): + infile = resources / basename + outpdf = outdir / basename + + check_ocrmypdf( + infile, + outpdf, '--pdf-renderer', 'tess4', '--force-ocr', + '--tesseract-timeout', '0', + env=ensure_tess4 + ) + + info_in = pageinfo.pdf_get_all_pageinfo(str(infile)) + + info = pageinfo.pdf_get_all_pageinfo(str(outpdf)) + for page in info: + assert len(page['images']) == 1, "skipped page was replicated" + + for n in range(len(info_in)): + assert info[n]['width_inches'] == info_in[n]['width_inches']