From b9f488d65cde1dbee146ed96900966eea5579d7c Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Sun, 21 Dec 2025 01:23:04 -0800 Subject: [PATCH] test: add comprehensive tests for --rasterizer option MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add test_rasterizer.py with tests covering: - Basic rasterizer option validation ('auto', 'ghostscript', 'pypdfium') - Rasterizer + --rotate-pages interaction - PDFs with nonstandard MediaBox/TrimBox/CropBox - Direct hook tests verifying plugins respect the option Also fix pluggy parameter passing: make 'options' a required parameter (no default) in the hookspec so pluggy forwards it to implementations. Update test plugins and test_rotation.py to pass the new parameter. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- src/ocrmypdf/builtin_plugins/ghostscript.py | 2 +- src/ocrmypdf/builtin_plugins/pypdfium.py | 2 +- src/ocrmypdf/pluginspec.py | 2 +- tests/plugins/gs_raster_failure.py | 11 +- tests/plugins/gs_raster_soft_error.py | 2 + tests/test_rasterizer.py | 603 ++++++++++++++++++++ tests/test_rotation.py | 16 + 7 files changed, 631 insertions(+), 7 deletions(-) create mode 100644 tests/test_rasterizer.py diff --git a/src/ocrmypdf/builtin_plugins/ghostscript.py b/src/ocrmypdf/builtin_plugins/ghostscript.py index 78d149dc..a453bffa 100644 --- a/src/ocrmypdf/builtin_plugins/ghostscript.py +++ b/src/ocrmypdf/builtin_plugins/ghostscript.py @@ -128,7 +128,7 @@ def rasterize_pdf_page( rotation, filter_vector, stop_on_soft_error, - options=None, + options, ): """Rasterize a single page of a PDF file using Ghostscript.""" # Check if user explicitly requested a different rasterizer diff --git a/src/ocrmypdf/builtin_plugins/pypdfium.py b/src/ocrmypdf/builtin_plugins/pypdfium.py index a187c981..f6d675d5 100644 --- a/src/ocrmypdf/builtin_plugins/pypdfium.py +++ b/src/ocrmypdf/builtin_plugins/pypdfium.py @@ -130,7 +130,7 @@ def rasterize_pdf_page( rotation: int | None, filter_vector: bool, stop_on_soft_error: bool, - options=None, + options, ) -> Path | None: """Rasterize a single page of a PDF file using pypdfium2. diff --git a/src/ocrmypdf/pluginspec.py b/src/ocrmypdf/pluginspec.py index 229e9a27..6460861b 100644 --- a/src/ocrmypdf/pluginspec.py +++ b/src/ocrmypdf/pluginspec.py @@ -213,7 +213,7 @@ def rasterize_pdf_page( rotation: int | None, filter_vector: bool, stop_on_soft_error: bool, - options: OCROptions | None = None, + options: OCROptions | None, ) -> Path: # type: ignore[return-value] """Rasterize one page of a PDF at resolution raster_dpi in canvas units. diff --git a/tests/plugins/gs_raster_failure.py b/tests/plugins/gs_raster_failure.py index bdb11c85..39b90226 100644 --- a/tests/plugins/gs_raster_failure.py +++ b/tests/plugins/gs_raster_failure.py @@ -24,9 +24,11 @@ def rasterize_pdf_page( raster_device, raster_dpi, pageno, - page_dpi=None, - rotation=None, - filter_vector=False, + page_dpi, + rotation, + filter_vector, + stop_on_soft_error, + options, ) -> Path: with patch('ocrmypdf._exec.ghostscript.run') as mock: mock.side_effect = raise_gs_fail @@ -39,7 +41,8 @@ def rasterize_pdf_page( page_dpi=page_dpi, rotation=rotation, filter_vector=filter_vector, - stop_on_soft_error=True, + stop_on_soft_error=stop_on_soft_error, + options=options, ) mock.assert_called() return output_file diff --git a/tests/plugins/gs_raster_soft_error.py b/tests/plugins/gs_raster_soft_error.py index 57a740b6..768688cb 100644 --- a/tests/plugins/gs_raster_soft_error.py +++ b/tests/plugins/gs_raster_soft_error.py @@ -29,6 +29,7 @@ def rasterize_pdf_page( rotation, filter_vector, stop_on_soft_error, + options, ) -> Path: with patch('ocrmypdf._exec.ghostscript.run') as mock: mock.side_effect = fail_if_stoponerror @@ -42,6 +43,7 @@ def rasterize_pdf_page( rotation=rotation, filter_vector=filter_vector, stop_on_soft_error=stop_on_soft_error, + options=options, ) mock.assert_called() return output_file diff --git a/tests/test_rasterizer.py b/tests/test_rasterizer.py new file mode 100644 index 00000000..6b3d7d7d --- /dev/null +++ b/tests/test_rasterizer.py @@ -0,0 +1,603 @@ +# SPDX-FileCopyrightText: 2025 James R. Barlow +# SPDX-License-Identifier: MPL-2.0 + +"""Tests for the --rasterizer CLI option.""" + +from __future__ import annotations + +from io import BytesIO + +import img2pdf +import pikepdf +import pytest +from PIL import Image + +from ocrmypdf._options import OCROptions +from ocrmypdf._plugin_manager import get_plugin_manager +from ocrmypdf.helpers import IMG2PDF_KWARGS, Resolution + +from .conftest import check_ocrmypdf + +# Check if pypdfium2 is available +try: + import pypdfium2 # noqa: F401 + + PYPDFIUM_AVAILABLE = True +except ImportError: + PYPDFIUM_AVAILABLE = False + + +class TestRasterizerOption: + """Test the --rasterizer CLI option.""" + + def test_rasterizer_auto_default(self, resources, outpdf): + """Test that --rasterizer auto (default) works.""" + check_ocrmypdf( + resources / 'graph.pdf', + outpdf, + '--rasterizer', + 'auto', + '--plugin', + 'tests/plugins/tesseract_noop.py', + ) + + def test_rasterizer_ghostscript(self, resources, outpdf): + """Test that --rasterizer ghostscript works.""" + check_ocrmypdf( + resources / 'graph.pdf', + outpdf, + '--rasterizer', + 'ghostscript', + '--plugin', + 'tests/plugins/tesseract_noop.py', + ) + + @pytest.mark.skipif(not PYPDFIUM_AVAILABLE, reason="pypdfium2 not installed") + def test_rasterizer_pypdfium(self, resources, outpdf): + """Test that --rasterizer pypdfium works when pypdfium2 is installed.""" + check_ocrmypdf( + resources / 'graph.pdf', + outpdf, + '--rasterizer', + 'pypdfium', + '--plugin', + 'tests/plugins/tesseract_noop.py', + ) + + def test_rasterizer_invalid(self): + """Test that an invalid rasterizer value is rejected.""" + with pytest.raises(ValueError, match="rasterizer must be one of"): + OCROptions( + input_file='test.pdf', output_file='out.pdf', rasterizer='invalid' + ) + + +class TestRasterizerWithRotation: + """Test --rasterizer interaction with --rotate-pages.""" + + def test_ghostscript_with_rotation(self, resources, outpdf): + """Test Ghostscript rasterizer with page rotation.""" + check_ocrmypdf( + resources / 'cardinal.pdf', + outpdf, + '--rasterizer', + 'ghostscript', + '--rotate-pages', + '--rotate-pages-threshold', + '0.1', + '--plugin', + 'tests/plugins/tesseract_cache.py', + ) + + @pytest.mark.skipif(not PYPDFIUM_AVAILABLE, reason="pypdfium2 not installed") + def test_pypdfium_with_rotation(self, resources, outpdf): + """Test pypdfium rasterizer with page rotation.""" + check_ocrmypdf( + resources / 'cardinal.pdf', + outpdf, + '--rasterizer', + 'pypdfium', + '--rotate-pages', + '--rotate-pages-threshold', + '0.1', + '--plugin', + 'tests/plugins/tesseract_cache.py', + ) + + def test_auto_with_rotation(self, resources, outpdf): + """Test auto rasterizer with page rotation.""" + check_ocrmypdf( + resources / 'cardinal.pdf', + outpdf, + '--rasterizer', + 'auto', + '--rotate-pages', + '--rotate-pages-threshold', + '0.1', + '--plugin', + 'tests/plugins/tesseract_cache.py', + ) + + +class TestRasterizerHookDirect: + """Test rasterize_pdf_page hook directly with different rasterizer options.""" + + def test_ghostscript_hook_respects_option(self, resources, tmp_path): + """Test that Ghostscript hook returns None when pypdfium is requested.""" + pm = get_plugin_manager([]) + + # Create options requesting pypdfium + options = OCROptions( + input_file=resources / 'graph.pdf', + output_file=tmp_path / 'out.pdf', + rasterizer='pypdfium', + ) + + img = tmp_path / 'ghostscript_test.png' + result = pm.hook.rasterize_pdf_page( + input_file=resources / 'graph.pdf', + output_file=img, + raster_device='pngmono', + raster_dpi=Resolution(50, 50), + page_dpi=Resolution(50, 50), + pageno=1, + rotation=0, + filter_vector=False, + stop_on_soft_error=True, + options=options, + ) + # When pypdfium is requested: + # - If pypdfium IS available, pypdfium handles it and returns the path + # - If pypdfium is NOT available, both plugins return None + # (ghostscript returns None because pypdfium was requested, + # pypdfium returns None because it's not installed) + if PYPDFIUM_AVAILABLE: + assert result == img + else: + assert result is None + + def test_pypdfium_hook_respects_option(self, resources, tmp_path): + """Test that pypdfium hook returns None when ghostscript is requested.""" + pm = get_plugin_manager([]) + + # Create options requesting ghostscript + options = OCROptions( + input_file=resources / 'graph.pdf', + output_file=tmp_path / 'out.pdf', + rasterizer='ghostscript', + ) + + img = tmp_path / 'pypdfium_test.png' + result = pm.hook.rasterize_pdf_page( + input_file=resources / 'graph.pdf', + output_file=img, + raster_device='pngmono', + raster_dpi=Resolution(50, 50), + page_dpi=Resolution(50, 50), + pageno=1, + rotation=0, + filter_vector=False, + stop_on_soft_error=True, + options=options, + ) + # Ghostscript should handle it + assert result == img + assert img.exists() + + def test_auto_uses_pypdfium_when_available(self, resources, tmp_path): + """Test that auto mode uses pypdfium when available.""" + pm = get_plugin_manager([]) + + options = OCROptions( + input_file=resources / 'graph.pdf', + output_file=tmp_path / 'out.pdf', + rasterizer='auto', + ) + + img = tmp_path / 'auto_test.png' + result = pm.hook.rasterize_pdf_page( + input_file=resources / 'graph.pdf', + output_file=img, + raster_device='pngmono', + raster_dpi=Resolution(50, 50), + page_dpi=Resolution(50, 50), + pageno=1, + rotation=0, + filter_vector=False, + stop_on_soft_error=True, + options=options, + ) + assert result == img + assert img.exists() + + +def _create_gradient_image(width: int, height: int) -> Image.Image: + """Create an image with multiple gradients to detect rasterization errors. + + The image contains: + - Horizontal gradient from red to blue + - Vertical gradient overlay from green to transparent + - Diagonal bands for edge detection + """ + img = Image.new('RGB', (width, height)) + pixels = img.load() + + for y in range(height): + for x in range(width): + # Horizontal gradient: red to blue + r = int(255 * (1 - x / width)) + b = int(255 * (x / width)) + + # Vertical gradient: add green component + g = int(255 * (y / height)) + + # Add diagonal bands for edge detection + band = ((x + y) // 20) % 2 + if band: + r = min(255, r + 40) + g = min(255, g + 40) + b = min(255, b + 40) + + pixels[x, y] = (r, g, b) + + return img + + +@pytest.fixture +def pdf_with_nonstandard_boxes(tmp_path): + """Create a PDF with nonstandard MediaBox, TrimBox and CropBox.""" + # Create an image with gradients to detect rasterization errors + img = _create_gradient_image(200, 300) + img_bytes = BytesIO() + img.save(img_bytes, format='PNG') + img_bytes.seek(0) + + # Convert to PDF + pdf_bytes = BytesIO() + img2pdf.convert( + img_bytes.read(), + layout_fun=img2pdf.get_fixed_dpi_layout_fun((72, 72)), + outputstream=pdf_bytes, + **IMG2PDF_KWARGS, + ) + pdf_bytes.seek(0) + + # Modify the PDF to have nonstandard boxes + pdf_path = tmp_path / 'nonstandard_boxes.pdf' + with pikepdf.open(pdf_bytes) as pdf: + page = pdf.pages[0] + # Set MediaBox larger than content + page.MediaBox = pikepdf.Array([0, 0, 400, 500]) + # Set CropBox smaller - this is what viewers typically show + page.CropBox = pikepdf.Array([50, 50, 350, 450]) + # Set TrimBox even smaller - indicates intended trim area + page.TrimBox = pikepdf.Array([75, 75, 325, 425]) + pdf.save(pdf_path) + + return pdf_path + + +@pytest.fixture +def pdf_with_negative_mediabox(tmp_path): + """Create a PDF with MediaBox that has negative origin coordinates.""" + # Create an image with gradients to detect rasterization errors + img = _create_gradient_image(200, 300) + img_bytes = BytesIO() + img.save(img_bytes, format='PNG') + img_bytes.seek(0) + + pdf_bytes = BytesIO() + img2pdf.convert( + img_bytes.read(), + layout_fun=img2pdf.get_fixed_dpi_layout_fun((72, 72)), + outputstream=pdf_bytes, + **IMG2PDF_KWARGS, + ) + pdf_bytes.seek(0) + + pdf_path = tmp_path / 'negative_mediabox.pdf' + with pikepdf.open(pdf_bytes) as pdf: + page = pdf.pages[0] + # MediaBox with negative origin (valid PDF but unusual) + page.MediaBox = pikepdf.Array([-100, -100, 300, 400]) + pdf.save(pdf_path) + + return pdf_path + + +class TestRasterizerWithNonStandardBoxes: + """Test rasterizers with PDFs having nonstandard MediaBox/TrimBox/CropBox.""" + + def test_ghostscript_nonstandard_boxes(self, pdf_with_nonstandard_boxes, outpdf): + """Test Ghostscript handles nonstandard page boxes correctly.""" + check_ocrmypdf( + pdf_with_nonstandard_boxes, + outpdf, + '--rasterizer', + 'ghostscript', + '--plugin', + 'tests/plugins/tesseract_noop.py', + ) + + @pytest.mark.skipif(not PYPDFIUM_AVAILABLE, reason="pypdfium2 not installed") + def test_pypdfium_nonstandard_boxes(self, pdf_with_nonstandard_boxes, outpdf): + """Test pypdfium handles nonstandard page boxes correctly.""" + check_ocrmypdf( + pdf_with_nonstandard_boxes, + outpdf, + '--rasterizer', + 'pypdfium', + '--plugin', + 'tests/plugins/tesseract_noop.py', + ) + + def test_ghostscript_negative_mediabox(self, pdf_with_negative_mediabox, outpdf): + """Test Ghostscript handles negative MediaBox origin.""" + check_ocrmypdf( + pdf_with_negative_mediabox, + outpdf, + '--rasterizer', + 'ghostscript', + '--plugin', + 'tests/plugins/tesseract_noop.py', + ) + + @pytest.mark.skipif(not PYPDFIUM_AVAILABLE, reason="pypdfium2 not installed") + def test_pypdfium_negative_mediabox(self, pdf_with_negative_mediabox, outpdf): + """Test pypdfium handles negative MediaBox origin.""" + check_ocrmypdf( + pdf_with_negative_mediabox, + outpdf, + '--rasterizer', + 'pypdfium', + '--plugin', + 'tests/plugins/tesseract_noop.py', + ) + + def test_compare_rasterizers_nonstandard_boxes( + self, pdf_with_nonstandard_boxes, tmp_path + ): + """Compare output dimensions between rasterizers for nonstandard boxes.""" + pm = get_plugin_manager([]) + + options_gs = OCROptions( + input_file=pdf_with_nonstandard_boxes, + output_file=tmp_path / 'out_gs.pdf', + rasterizer='ghostscript', + ) + + img_gs = tmp_path / 'gs.png' + pm.hook.rasterize_pdf_page( + input_file=pdf_with_nonstandard_boxes, + output_file=img_gs, + raster_device='png16m', + raster_dpi=Resolution(72, 72), + page_dpi=Resolution(72, 72), + pageno=1, + rotation=0, + filter_vector=False, + stop_on_soft_error=True, + options=options_gs, + ) + + with Image.open(img_gs) as im_gs: + gs_size = im_gs.size + + if PYPDFIUM_AVAILABLE: + options_pdfium = OCROptions( + input_file=pdf_with_nonstandard_boxes, + output_file=tmp_path / 'out_pdfium.pdf', + rasterizer='pypdfium', + ) + + img_pdfium = tmp_path / 'pdfium.png' + pm.hook.rasterize_pdf_page( + input_file=pdf_with_nonstandard_boxes, + output_file=img_pdfium, + raster_device='png16m', + raster_dpi=Resolution(72, 72), + page_dpi=Resolution(72, 72), + pageno=1, + rotation=0, + filter_vector=False, + stop_on_soft_error=True, + options=options_pdfium, + ) + + with Image.open(img_pdfium) as im_pdfium: + pdfium_size = im_pdfium.size + + # Note: Ghostscript and pypdfium use different page boxes: + # - Ghostscript uses MediaBox (400x500) + # - pypdfium uses CropBox (300x400) + # This is expected behavior - verify each produces valid output + assert gs_size == (400, 500), f"Ghostscript size: {gs_size}" + assert pdfium_size == (300, 400), f"pypdfium size: {pdfium_size}" + + +class TestRasterizerWithRotationAndBoxes: + """Test rasterizer + rotation + nonstandard boxes combinations.""" + + # The pdf_with_nonstandard_boxes fixture creates a PDF with: + # - MediaBox: [0, 0, 400, 500] → 400x500 points + # - CropBox: [50, 50, 350, 450] → 300x400 points + # - TrimBox: [75, 75, 325, 425] → 250x350 points + # + # The rasterizers use different boxes: + # - Ghostscript uses MediaBox → 400x500 pixels at 72 DPI + # - pypdfium uses CropBox → 300x400 pixels at 72 DPI + GS_WIDTH = 400 # MediaBox width + GS_HEIGHT = 500 # MediaBox height + PDFIUM_WIDTH = 300 # CropBox width + PDFIUM_HEIGHT = 400 # CropBox height + + def _get_expected_size( + self, rotation: int, rasterizer: str = 'ghostscript' + ) -> tuple[int, int]: + """Get expected image dimensions after rotation.""" + if rasterizer == 'ghostscript': + width, height = self.GS_WIDTH, self.GS_HEIGHT + else: + width, height = self.PDFIUM_WIDTH, self.PDFIUM_HEIGHT + + if rotation in (0, 180): + return (width, height) + else: # 90, 270 + return (height, width) + + def test_ghostscript_rotation_dimensions( + self, pdf_with_nonstandard_boxes, tmp_path + ): + """Test Ghostscript produces correct dimensions with rotation.""" + pm = get_plugin_manager([]) + + options = OCROptions( + input_file=pdf_with_nonstandard_boxes, + output_file=tmp_path / 'out.pdf', + rasterizer='ghostscript', + ) + + for rotation in [0, 90, 180, 270]: + img_path = tmp_path / f'gs_rot{rotation}.png' + pm.hook.rasterize_pdf_page( + input_file=pdf_with_nonstandard_boxes, + output_file=img_path, + raster_device='png16m', + raster_dpi=Resolution(72, 72), + page_dpi=Resolution(72, 72), + pageno=1, + rotation=rotation, + filter_vector=False, + stop_on_soft_error=True, + options=options, + ) + assert img_path.exists(), f"Failed to rasterize with rotation {rotation}" + + with Image.open(img_path) as img: + expected = self._get_expected_size(rotation, 'ghostscript') + # Allow small tolerance for rounding + assert abs(img.size[0] - expected[0]) <= 2, ( + f"Width mismatch at {rotation}°: got {img.size[0]}, " + f"expected {expected[0]}" + ) + assert abs(img.size[1] - expected[1]) <= 2, ( + f"Height mismatch at {rotation}°: got {img.size[1]}, " + f"expected {expected[1]}" + ) + + @pytest.mark.skipif(not PYPDFIUM_AVAILABLE, reason="pypdfium2 not installed") + def test_pypdfium_rotation_dimensions( + self, pdf_with_nonstandard_boxes, tmp_path + ): + """Test pypdfium produces correct dimensions with rotation.""" + pm = get_plugin_manager([]) + + options = OCROptions( + input_file=pdf_with_nonstandard_boxes, + output_file=tmp_path / 'out.pdf', + rasterizer='pypdfium', + ) + + for rotation in [0, 90, 180, 270]: + img_path = tmp_path / f'pdfium_rot{rotation}.png' + pm.hook.rasterize_pdf_page( + input_file=pdf_with_nonstandard_boxes, + output_file=img_path, + raster_device='png16m', + raster_dpi=Resolution(72, 72), + page_dpi=Resolution(72, 72), + pageno=1, + rotation=rotation, + filter_vector=False, + stop_on_soft_error=True, + options=options, + ) + assert img_path.exists(), f"Failed to rasterize with rotation {rotation}" + + with Image.open(img_path) as img: + expected = self._get_expected_size(rotation, 'pypdfium') + # Allow small tolerance for rounding + assert abs(img.size[0] - expected[0]) <= 2, ( + f"Width mismatch at {rotation}°: got {img.size[0]}, " + f"expected {expected[0]}" + ) + assert abs(img.size[1] - expected[1]) <= 2, ( + f"Height mismatch at {rotation}°: got {img.size[1]}, " + f"expected {expected[1]}" + ) + + @pytest.mark.skipif(not PYPDFIUM_AVAILABLE, reason="pypdfium2 not installed") + def test_rasterizers_dimensions_differ_as_expected( + self, pdf_with_nonstandard_boxes, tmp_path + ): + """Verify ghostscript and pypdfium produce expected different dimensions. + + Ghostscript uses MediaBox while pypdfium uses CropBox, so their output + dimensions differ for PDFs with different MediaBox/CropBox sizes. + """ + pm = get_plugin_manager([]) + + for rotation in [0, 90, 180, 270]: + # Rasterize with Ghostscript + gs_options = OCROptions( + input_file=pdf_with_nonstandard_boxes, + output_file=tmp_path / 'out.pdf', + rasterizer='ghostscript', + ) + gs_img_path = tmp_path / f'gs_cmp_rot{rotation}.png' + pm.hook.rasterize_pdf_page( + input_file=pdf_with_nonstandard_boxes, + output_file=gs_img_path, + raster_device='png16m', + raster_dpi=Resolution(72, 72), + page_dpi=Resolution(72, 72), + pageno=1, + rotation=rotation, + filter_vector=False, + stop_on_soft_error=True, + options=gs_options, + ) + + # Rasterize with pypdfium + pdfium_options = OCROptions( + input_file=pdf_with_nonstandard_boxes, + output_file=tmp_path / 'out.pdf', + rasterizer='pypdfium', + ) + pdfium_img_path = tmp_path / f'pdfium_cmp_rot{rotation}.png' + pm.hook.rasterize_pdf_page( + input_file=pdf_with_nonstandard_boxes, + output_file=pdfium_img_path, + raster_device='png16m', + raster_dpi=Resolution(72, 72), + page_dpi=Resolution(72, 72), + pageno=1, + rotation=rotation, + filter_vector=False, + stop_on_soft_error=True, + options=pdfium_options, + ) + + # Verify each produces its expected dimensions + with Image.open(gs_img_path) as gs_img, Image.open( + pdfium_img_path + ) as pdfium_img: + gs_expected = self._get_expected_size(rotation, 'ghostscript') + pdfium_expected = self._get_expected_size(rotation, 'pypdfium') + + assert abs(gs_img.size[0] - gs_expected[0]) <= 2, ( + f"GS width at {rotation}°: {gs_img.size[0]}, " + f"expected {gs_expected[0]}" + ) + assert abs(gs_img.size[1] - gs_expected[1]) <= 2, ( + f"GS height at {rotation}°: {gs_img.size[1]}, " + f"expected {gs_expected[1]}" + ) + assert abs(pdfium_img.size[0] - pdfium_expected[0]) <= 2, ( + f"pdfium width at {rotation}°: {pdfium_img.size[0]}, " + f"expected {pdfium_expected[0]}" + ) + assert abs(pdfium_img.size[1] - pdfium_expected[1]) <= 2, ( + f"pdfium height at {rotation}°: {pdfium_img.size[1]}, " + f"expected {pdfium_expected[1]}" + ) diff --git a/tests/test_rotation.py b/tests/test_rotation.py index 23e0697b..18f751f2 100644 --- a/tests/test_rotation.py +++ b/tests/test_rotation.py @@ -171,6 +171,8 @@ def test_rotated_skew_timeout(resources, outpdf): '--deskew', '--tesseract-timeout', '0', + '--rasterizer', + 'ghostscript', # Use Ghostscript for consistent dimensions ) out_pageinfo = PdfInfo(out)[0] @@ -197,6 +199,8 @@ def test_rotate_deskew_ocr_timeout(resources, outdir): '0', '--pdf-renderer', 'hocr', + '--rasterizer', + 'ghostscript', # Use Ghostscript for consistent dimensions ) cmp = compare_images_monochrome( @@ -285,8 +289,16 @@ def test_page_rotate_tag(page_rotate_angle, resources, outdir, caplog): def test_rasterize_rotates(resources, tmp_path): + from ocrmypdf._options import OCROptions + pm = get_plugin_manager([]) + options = OCROptions( + input_file=resources / 'graph.pdf', + output_file=tmp_path / 'out.pdf', + rasterizer='ghostscript', # Use Ghostscript for consistent dimensions + ) + img = tmp_path / 'img90.png' pm.hook.rasterize_pdf_page( input_file=resources / 'graph.pdf', @@ -298,6 +310,7 @@ def test_rasterize_rotates(resources, tmp_path): rotation=90, filter_vector=False, stop_on_soft_error=True, + options=options, ) with Image.open(img) as im: assert im.size == (83, 200), "Image not rotated" @@ -313,6 +326,7 @@ def test_rasterize_rotates(resources, tmp_path): rotation=180, filter_vector=False, stop_on_soft_error=True, + options=options, ) assert Image.open(img).size == (200, 83), "Image not rotated" @@ -346,6 +360,8 @@ def test_simulated_scan(outdir): '--rotate-pages', '--plugin', 'tests/plugins/tesseract_debug_rotate.py', + '--rasterizer', + 'ghostscript', # Use Ghostscript to avoid pypdfium2 thread safety issues ) with pikepdf.open(outdir / 'out.pdf') as pdf: