From 6ff6c8614f2c4f34a8b1d2b39833ff185d508d70 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Wed, 24 May 2017 23:26:07 -0700 Subject: [PATCH] =?UTF-8?q?=E2=80=94output-type=3Dpdf=20now=20outputs=20/U?= =?UTF-8?q?serUnit=20PDFs=20at=20the=20correct=20size?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This currently distorts the output size because Tesseract assumes it knows the DPI better than we do. Does not work for Ghostscript, because it emerges that Ghostscript honors /UserUnit for rasterizing but not in pdfwrite (resolve/wontfix). https://bugs.ghostscript.com/show_bug.cgi?id=690781 Ghostscript’s output would need to be patched in a PDF/A safe way for this to work. Temporary route may be to block Ghostscript if /UserUnit. --- ocrmypdf/exec/ghostscript.py | 4 ++- ocrmypdf/exec/qpdf.py | 5 ++-- ocrmypdf/pipeline.py | 55 ++++++++++++++++++++++++++++-------- tests/test_pageinfo.py | 8 +++++- 4 files changed, 57 insertions(+), 15 deletions(-) diff --git a/ocrmypdf/exec/ghostscript.py b/ocrmypdf/exec/ghostscript.py index f01707c6..8ed8fe95 100644 --- a/ocrmypdf/exec/ghostscript.py +++ b/ocrmypdf/exec/ghostscript.py @@ -64,7 +64,8 @@ def rasterize_pdf(input_file, output_file, xres, yres, raster_device, log, raise SubprocessOutputError() -def generate_pdfa(pdf_pages, output_file, compression, log, threads=1): +def generate_pdfa(*, pdf_version, pdf_pages, output_file, compression, log, + threads=1): compression_args = [] if compression == 'jpeg': compression_args = [ @@ -92,6 +93,7 @@ def generate_pdfa(pdf_pages, output_file, compression, log, threads=1): "-dQUIET", "-dBATCH", "-dNOPAUSE", + '-dCompatibilityLevel=' + str(pdf_version), '-dNumRenderingThreads=' + str(threads), "-sDEVICE=pdfwrite", "-dAutoRotatePages=/None", diff --git a/ocrmypdf/exec/qpdf.py b/ocrmypdf/exec/qpdf.py index 6f2f3297..47f94882 100644 --- a/ocrmypdf/exec/qpdf.py +++ b/ocrmypdf/exec/qpdf.py @@ -120,13 +120,14 @@ def split_pages(input_file, work_folder, npages): run(args_qpdf, check=True) -def merge(input_files, output_file): +def merge(*, min_version, input_files, output_file): """Merge the list of input files (all filenames) into the output file. The input files may contain one or more pages. """ args_qpdf = [ - get_program('qpdf'), input_files[0], '--pages' + get_program('qpdf'), '--min-version={}'.format(min_version), + input_files[0], '--pages' ] + input_files + ['--', output_file] run(args_qpdf, check=True) diff --git a/ocrmypdf/pipeline.py b/ocrmypdf/pipeline.py index 0d45ce44..caa4dc7a 100644 --- a/ocrmypdf/pipeline.py +++ b/ocrmypdf/pipeline.py @@ -205,10 +205,18 @@ def get_page_dpi(pageinfo, options): def get_page_square_dpi(pageinfo, options): - "Get the DPI when we require xres == yres" + "Get the working DPI when we require xres == yres" return float(max( - pageinfo.xres or VECTOR_PAGE_DPI, - pageinfo.yres or VECTOR_PAGE_DPI, + (pageinfo.xres * pageinfo.userunit) or VECTOR_PAGE_DPI, + (pageinfo.yres * pageinfo.userunit) or VECTOR_PAGE_DPI, + options.oversample or 0)) + + +def get_page_true_square_dpi(pageinfo, options): + "Get the true DPI when we require xres == yres, and scaled in userunits" + return float(max( + (pageinfo.xres) or VECTOR_PAGE_DPI, + (pageinfo.yres) or VECTOR_PAGE_DPI, options.oversample or 0)) @@ -414,11 +422,24 @@ def rasterize_with_ghostscript( os.path.basename(input_file), device)) # Produce the page image with square resolution or else deskew and OCR - # will not work properly - dpi = get_page_square_dpi(pageinfo, options) - ghostscript.rasterize_pdf( - input_file, output_file, xres=dpi, yres=dpi, raster_device=device, - log=log) + # will not work properly. + true_dpi = get_page_true_square_dpi(pageinfo, options) + working_dpi = get_page_square_dpi(pageinfo, options) + + if true_dpi == working_dpi: + dpi = true_dpi + ghostscript.rasterize_pdf( + input_file, output_file, xres=dpi, yres=dpi, + raster_device=device, log=log) + else: + # Ghostscript respects /UserUnit when rasterizing so replace the + # image's DPI with the working DPI + ghostscript.rasterize_pdf( + input_file, output_file + '.tmp', xres=true_dpi, yres=true_dpi, + raster_device=device, log=log) + + with Image.open(output_file + ".tmp") as im: + im.save(output_file, dpi=(working_dpi, working_dpi)) def preprocess_remove_background( @@ -692,6 +713,12 @@ def combine_layers( pdf_output = pypdf.PdfFileWriter() pdf_output.addPage(page_text) + # If the input was scaled, re-apply the scaling + pageinfo = get_pageinfo(text, context) + if pageinfo.userunit != 1: + page_text[pypdf.generic.NameObject('/UserUnit')] = pageinfo.userunit + pdf_output._header = b'%PDF-1.6' # Hack header to correct version + with open(output_file, "wb") as out: pdf_output.write(out) @@ -835,9 +862,14 @@ def merge_pages_ghostscript( if not f.endswith('.txt')) pdf_pages = sorted(input_files, key=input_file_order) log.debug("Final pages: " + "\n".join(pdf_pages)) + input_pdfinfo = context.get_pdfinfo() ghostscript.generate_pdfa( - pdf_pages, output_file, options.pdfa_image_compression, - log, options.jobs or 1) + pdf_version=input_pdfinfo.min_version, + pdf_pages=pdf_pages, + output_file=output_file, + compression=options.pdfa_image_compression, + log=log, + threads=options.jobs or 1) def merge_pages_qpdf( @@ -879,7 +911,8 @@ def merge_pages_qpdf( pdf_pages[0] = writer_file - qpdf.merge(pdf_pages, output_file) + qpdf.merge(input_files=pdf_pages, output_file=output_file, + min_version=context.get_pdfinfo().min_version) def merge_sidecars( diff --git a/tests/test_pageinfo.py b/tests/test_pageinfo.py index 02d393ae..1b6459f0 100644 --- a/tests/test_pageinfo.py +++ b/tests/test_pageinfo.py @@ -116,4 +116,10 @@ def test_no_contents(resources): pdf = pdfinfo.PdfInfo(filename) assert len(pdf[0].images) == 0 - assert pdf[0].has_text == False \ No newline at end of file + assert pdf[0].has_text == False + + +def test_oversized_page(resources): + pdf = pdfinfo.PdfInfo(resources / 'poster.pdf') + image = pdf[0].images[0] + assert image.width * image.xres > 200, "this is supposed to be oversized" \ No newline at end of file