From b5d7e9cbb06b491dd585d367197711915e25cfe0 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Tue, 1 May 2018 22:50:20 -0700 Subject: [PATCH] Fix all issues with rotations All tests now pass --- src/ocrmypdf/exec/ghostscript.py | 27 ++++++++++++++++++---- src/ocrmypdf/pipeline.py | 39 +++++++++++++++++++++++--------- tests/test_main.py | 10 ++++---- 3 files changed, 54 insertions(+), 22 deletions(-) diff --git a/src/ocrmypdf/exec/ghostscript.py b/src/ocrmypdf/exec/ghostscript.py index 98256e33..8f64d4c1 100644 --- a/src/ocrmypdf/exec/ghostscript.py +++ b/src/ocrmypdf/exec/ghostscript.py @@ -49,7 +49,7 @@ def _gs_error_reported(stream): def rasterize_pdf(input_file, output_file, xres, yres, raster_device, log, - pageno=1, page_dpi=None): + pageno=1, page_dpi=None, rotation=None): """ Rasterize one page of a PDF at resolution (xres, yres) in canvas units. @@ -71,6 +71,9 @@ def rasterize_pdf(input_file, output_file, xres, yres, raster_device, log, int_res = round(xres), round(yres) if not page_dpi: page_dpi = res + + autorotate = '/PageByPage' if rotation is None else '/None' + with NamedTemporaryFile(delete=True) as tmp: args_gs = [ 'gs', @@ -83,9 +86,12 @@ def rasterize_pdf(input_file, output_file, xres, yres, raster_device, log, '-dLastPage=%i' % pageno, '-r{0}x{1}'.format(str(int_res[0]), str(int_res[1])), '-o', tmp.name, + '-dAutoRotatePages=%s' % autorotate, + '-f', fspath(input_file) ] + log.debug(args_gs) p = run(args_gs, stdout=PIPE, stderr=STDOUT, universal_newlines=True) if _gs_error_reported(p.stdout): @@ -110,10 +116,21 @@ def rasterize_pdf(input_file, output_file, xres, yres, raster_device, log, log.debug( "Ghostscript: resize output image {} -> {}".format( im.size, expected_size)) - im.resize(expected_size).save( - fspath(output_file), dpi=page_dpi) - else: - copy(tmp.name, fspath(output_file)) + im = im.resize(expected_size) + + if rotation is not None: + log.debug("Rotating output by %i", rotation) + # rotation is a clockwise angle and Image.ROTATE_* is + # counterclockwise so this cancels out the rotation + # if rotation == 90: + # im = im.transpose(Image.ROTATE_90) + # elif rotation == 180: + # im = im.transpose(Image.ROTATE_180) + # elif rotation == 270: + # im = im.transpose(Image.ROTATE_270) + # if rotation % 180 == 90: + # page_dpi = page_dpi[1], page_dpi[0] + im.save(fspath(output_file), dpi=page_dpi) def generate_pdfa(pdf_pages, output_file, compression, log, diff --git a/src/ocrmypdf/pipeline.py b/src/ocrmypdf/pipeline.py index 9116534a..a5b58ae5 100644 --- a/src/ocrmypdf/pipeline.py +++ b/src/ocrmypdf/pipeline.py @@ -66,6 +66,7 @@ class JobContext: self.pdfinfo = None self.options = None self.work_folder = None + self.rotations = {} def generate_pdfinfo(self, infile): self.pdfinfo = PdfInfo(infile) @@ -89,6 +90,12 @@ class JobContext: def set_work_folder(self, work_folder): self.work_folder = work_folder + def get_rotation(self, pageno): + return self.rotations.get(pageno, 0) + + def set_rotation(self, pageno, value): + self.rotations[pageno] = value + from multiprocessing.managers import SyncManager class JobContextManager(SyncManager): @@ -418,7 +425,7 @@ def rasterize_preview( ghostscript.rasterize_pdf( input_file, output_file, xres=canvas_dpi, yres=canvas_dpi, raster_device='jpeggray', log=log, page_dpi=(page_dpi, page_dpi), - pageno=page_number(input_file)) + pageno=page_number(input_file), rotation=pageinfo.rotation) def orient_page( @@ -475,8 +482,8 @@ def orient_page( if apply_correction: pageno = page_number(page_pdf) - 1 pdfinfo = context.get_pdfinfo() - pdfinfo[pageno].rotation = orient_conf.angle - context.set_pdfinfo(pdfinfo) # This is expensive for large files + correction = (pdfinfo[pageno].rotation - orient_conf.angle) % 360 + context.set_rotation(pageno, correction) def rasterize_with_ghostscript( @@ -507,10 +514,12 @@ def rasterize_with_ghostscript( canvas_dpi = get_canvas_square_dpi(pageinfo, options) page_dpi = get_page_square_dpi(pageinfo, options) + correction = context.get_rotation(page_number(input_file)) + ghostscript.rasterize_pdf( input_file, output_file, xres=canvas_dpi, yres=canvas_dpi, raster_device=device, log=log, page_dpi=(page_dpi, page_dpi), - pageno=page_number(input_file)) + pageno=page_number(input_file), rotation=correction) def preprocess_remove_background( @@ -720,7 +729,7 @@ def render_hocr_debug_page( interwordSpaces=True) -def _weave_layers_graft(pdf_base, page_num, text, font, font_key, log): +def _weave_layers_graft(pdf_base, page_num, text, font, font_key, rotation, log): from math import cos, sin, pi log.info("Graft") @@ -732,8 +741,6 @@ def _weave_layers_graft(pdf_base, page_num, text, font, font_key, log): pdf_text_contents = pdf_text.pages[0].Contents.read_bytes() base_page = pdf_base.pages.p(page_num) - rotation = int(base_page.get('/Rotate', 0)) - rotation = rotation % 360 # The text page always will be oriented up by this stage but the original # content may have a rotation applied. Wrap the text stream with a rotation @@ -878,15 +885,25 @@ def weave_layers( keep_open.append(pdf_image) image_page = pdf_image.pages[0] pdf_base.pages[page_num - 1] = image_page + #pdfinfo[page_num - 1].rotation = 0 + + log.info("Content rotation " + str(pdfinfo[page_num - 1].rotation)) + + text_content_rotation = 0 #pdfinfo[page_num - 1].rotation + log.info("Text content rotation ±" + str(text_content_rotation)) + + ctx_rotation = context.get_rotation(page_num - 1) + log.info("Saved correction ±" + str(ctx_rotation)) if text and font: # Graft the text layer onto this page, whether new or old _weave_layers_graft( - pdf_base, page_num, text, font, font_key, log) + pdf_base, page_num, text, font, font_key, text_content_rotation, + log + ) - # Correct the rotation - rotation = pdfinfo[page_num - 1].rotation - rotation = -rotation % 360 + # Correct the rotation if applicable + rotation = context.get_rotation(page_num - 1) if rotation != 0: pdf_base.pages[page_num - 1].Rotate = rotation diff --git a/tests/test_main.py b/tests/test_main.py index e8ff3d42..ae5942d5 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -639,15 +639,16 @@ def test_rotated_skew_timeout(resources, outpdf): '--deskew', '--tesseract-timeout', '0') out_pageinfo = PdfInfo(out)[0] + w, h = out_pageinfo.width_pixels, out_pageinfo.height_pixels - assert out_pageinfo.height_pixels > out_pageinfo.width_pixels, \ + assert h > w, \ "Expected the output page to be portrait" assert out_pageinfo.rotation == 0, \ "Expected no page rotation for output" - assert in_pageinfo.width_pixels == out_pageinfo.height_pixels and \ - in_pageinfo.height_pixels == out_pageinfo.width_pixels, \ + assert in_pageinfo.width_pixels == h and \ + in_pageinfo.height_pixels == w, \ "Expected page rotation to be baked in" @@ -986,9 +987,6 @@ def test_bad_utf8(spoof_tess_bad_utf8, renderer, resources, no_outpdf): assert '\\x96' in err, 'should repeat backslash encoded output' -@pytest.mark.skipif( - not tesseract.has_textonly_pdf(), - reason="issue only affects sandwich") def test_rotate_deskew_timeout(resources, outdir): check_ocrmypdf( resources / 'rotated_skew.pdf',