diff --git a/src/ocrmypdf/_pipeline.py b/src/ocrmypdf/_pipeline.py index 3eab75f9..89eb8382 100644 --- a/src/ocrmypdf/_pipeline.py +++ b/src/ocrmypdf/_pipeline.py @@ -39,6 +39,7 @@ from ocrmypdf.hocrtransform import DebugRenderOptions, HocrTransform from ocrmypdf.hocrtransform._font import Courier from ocrmypdf.pdfa import generate_pdfa_ps from ocrmypdf.pdfinfo import Colorspace, Encoding, PageInfo, PdfInfo +from ocrmypdf.pdfinfo.info import FloatRect from ocrmypdf.pluginspec import OrientationConfidence try: @@ -832,6 +833,23 @@ def _offset_rect(rect: tuple[float, float, float, float], offset: tuple[float, f ) +def _adjust_pagebox( + page: pikepdf.Page, + media_box: FloatRect, + name: pikepdf.Name, + target_box: FloatRect, + offset: tuple[float, float], + swap_axis: bool, +): + if media_box == target_box: + return + box = _offset_rect(target_box, offset) + if swap_axis: + box = box[1], box[0], box[3], box[2] + page[name] = box + log.debug(f"{str(name)} = {target_box}") + + def fix_pagepdf_boxes( infile: Path | BinaryIO, out_file: Path, @@ -856,22 +874,25 @@ def fix_pagepdf_boxes( """ with pikepdf.open(infile) as pdf: for page in pdf.pages: - # page.BleedBox = page_context.pageinfo.bleedbox - # page.ArtBox = page_context.pageinfo.artbox log.debug( - f"initial mediabox={page.MediaBox} and pageinfo mediabox={page_context.pageinfo.mediabox}" + f"initial mediabox={page.MediaBox} and pageinfo " + f"mediabox={page_context.pageinfo.mediabox}" ) mediabox = page_context.pageinfo.mediabox offset = -mediabox[0], -mediabox[1] - cropbox = _offset_rect(page_context.pageinfo.cropbox, offset) - trimbox = _offset_rect(page_context.pageinfo.trimbox, offset) if swap_axis: - cropbox = cropbox[1], cropbox[0], cropbox[3], cropbox[2] - trimbox = trimbox[1], trimbox[0], trimbox[3], trimbox[2] mediabox = mediabox[1], mediabox[0], mediabox[3], mediabox[2] - page.CropBox = cropbox - page.TrimBox = trimbox - log.debug(f"cropbox={cropbox}, trimbox={trimbox}, mediabox={mediabox}") + boxes = ['CropBox', 'TrimBox', 'ArtBox', 'BleedBox'] + for box_name in boxes: + _adjust_pagebox( + page, + mediabox, + pikepdf.Name(f"/{box_name}"), + getattr(page_context.pageinfo, box_name.lower()), + offset, + swap_axis, + ) + pdf.save(out_file) return out_file diff --git a/src/ocrmypdf/pdfinfo/info.py b/src/ocrmypdf/pdfinfo/info.py index 88d20c7f..31a77a22 100644 --- a/src/ocrmypdf/pdfinfo/info.py +++ b/src/ocrmypdf/pdfinfo/info.py @@ -901,8 +901,8 @@ class PageInfo: width_pt = mediabox[2] - mediabox[0] height_pt = mediabox[3] - mediabox[1] - # self._artbox = [float(d) for d in page.artbox.as_list()] - # self._bleedbox = [float(d) for d in page.bleedbox.as_list()] + self._artbox = [float(d) for d in page.artbox.as_list()] + self._bleedbox = [float(d) for d in page.bleedbox.as_list()] self._cropbox = [float(d) for d in page.cropbox.as_list()] self._mediabox = [float(d) for d in page.mediabox.as_list()] self._trimbox = [float(d) for d in page.trimbox.as_list()] @@ -1039,6 +1039,16 @@ class PageInfo: """Return trimbox of page in PDF coordinates.""" return self._trimbox + @property + def artbox(self) -> FloatRect: + """Return artbox of page in PDF coordinates.""" + return self._artbox + + @property + def bleedbox(self) -> FloatRect: + """Return bleedbox of page in PDF coordinates.""" + return self._bleedbox + @property def images(self) -> list[ImageInfo]: """Return images.""" diff --git a/tests/test_page_boxes.py b/tests/test_page_boxes.py index cc3386b1..9251a04d 100644 --- a/tests/test_page_boxes.py +++ b/tests/test_page_boxes.py @@ -102,7 +102,6 @@ def test_crop_box( page = pdf.pages[0] page.CropBox = crop_to pdf.save(outdir / 'cropped.pdf') - pdf.save('cropped.pdf') args = [ '--jobs', '1', @@ -110,6 +109,8 @@ def test_crop_box( renderer, '--output-type', output_type, + '--optimize', + '0', ] if mode: args.append(mode) @@ -118,5 +119,4 @@ def test_crop_box( with pikepdf.open(outdir / 'processed.pdf') as pdf: page = pdf.pages[0] - pdf.save('processed.pdf') assert page.CropBox == crop_expected diff --git a/tests/test_rotation.py b/tests/test_rotation.py index 23e0697b..bbd9afa4 100644 --- a/tests/test_rotation.py +++ b/tests/test_rotation.py @@ -51,20 +51,27 @@ def compare_images_monochrome( with Image.open(reference_png) as reference_im, Image.open(test_png) as test_im: assert reference_im.mode == test_im.mode == '1' - difference = ImageChops.logical_xor(reference_im, test_im) - assert difference.mode == '1' + # Pillow uses black is 0 for '1'. Invert so that foreground is 1, then + # compare + inv_ref_im = ImageChops.invert(reference_im) + inv_test_im = ImageChops.invert(test_im) + foreground_match = ImageChops.logical_and(inv_ref_im, inv_test_im) + foreground_total = ImageChops.logical_or(inv_ref_im, inv_test_im) + assert foreground_match.mode == '1' - histogram = difference.histogram() + histogram = foreground_match.histogram() + histogram_total = foreground_total.histogram() assert ( len(histogram) == 256 ), "Expected Pillow to convert to grayscale for histogram" # All entries other than first and last will be 0 - count_same = histogram[0] - count_different = histogram[-1] - total = count_same + count_different - - return count_same / (total) + # count_same = histogram[0] + # count_different = histogram[-1] + # total = count_same + count_different + # print(f"{count_same / (total)}") + # return count_same / (total) + return histogram[-1] / (histogram_total[-1] + 1) def test_monochrome_comparison(resources, outdir): @@ -211,7 +218,7 @@ def test_rotate_deskew_ocr_timeout(resources, outdir): assert cmp > 0.95 -def make_rotate_test(imagefile, outdir, prefix, image_angle, page_angle): +def make_rotate_test(imagefile, outdir, prefix, image_angle, page_angle, cropbox=None): memimg = BytesIO() with Image.open(fspath(imagefile)) as im: if image_angle != 0: @@ -230,6 +237,8 @@ def make_rotate_test(imagefile, outdir, prefix, image_angle, page_angle): with pikepdf.open(mempdf) as pdf: pdf.pages[0].Rotate = page_angle target = outdir / f'{prefix}_{image_angle}_{page_angle}.pdf' + if cropbox: + pdf.pages[0].CropBox = cropbox pdf.save(target) return target @@ -284,6 +293,44 @@ def test_page_rotate_tag(page_rotate_angle, resources, outdir, caplog): assert 'is a' in test_text, test_text +@pytest.mark.parametrize('page_rotate_angle', (0, 90, 180, 270)) +@pytest.mark.parametrize('renderer', ['sandwich', 'hocr']) +@pytest.mark.parametrize('output_type', ['pdf', 'pdfa']) +def test_rotate_and_crop( + resources, outdir, page_rotate_angle, renderer, output_type, caplog +): + cropbox = (100, 200, 1000, 800) + reference = make_rotate_test( + resources / 'typewriter.png', outdir, 'ref', 0, 0, cropbox + ) + test = make_rotate_test( + resources / 'typewriter.png', + outdir, + 'test', + -page_rotate_angle, + page_rotate_angle, + cropbox, + ) + out = test.with_suffix('.out.pdf') + + exitcode = run_ocrmypdf_api( + test, + out, + '-O0', + '--rotate-pages', + '--rotate-pages-threshold', + '0', + '--pdf-renderer', + renderer, + '--output-type', + output_type, + '--no-progress-bar', + ) + assert exitcode == 0, caplog.text + + assert compare_images_monochrome(outdir, reference, 1, out, 1) > 0.9 + + def test_rasterize_rotates(resources, tmp_path): pm = get_plugin_manager([])