From 6bc9499e689bab0f301f8f04522af8024b9ef7a3 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Fri, 5 Jun 2026 01:07:27 -0700 Subject: [PATCH] feat: recognize pngmonod device in pypdfium rasterizer (#1688) --- src/ocrmypdf/builtin_plugins/pypdfium.py | 21 ++++++++++++++---- tests/test_rasterizer.py | 28 ++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 4 deletions(-) diff --git a/src/ocrmypdf/builtin_plugins/pypdfium.py b/src/ocrmypdf/builtin_plugins/pypdfium.py index 8edab8f3..973b40c9 100644 --- a/src/ocrmypdf/builtin_plugins/pypdfium.py +++ b/src/ocrmypdf/builtin_plugins/pypdfium.py @@ -96,7 +96,12 @@ def _render_page_to_bitmap( # Render the page to a bitmap # The scale parameter controls the resolution # Render in grayscale for mono and gray devices (better input for 1-bit conversion) - grayscale = raster_device.lower() in ('pngmono', 'pnggray', 'jpeggray') + grayscale = raster_device.lower() in ( + 'pngmono', + 'pngmonod', + 'pnggray', + 'jpeggray', + ) # Default (use_cropbox=False) renders MediaBox for consistency with Ghostscript if not use_cropbox: @@ -157,8 +162,8 @@ def _process_image_for_output( # This ensures pypdfium output matches Ghostscript's native device output raster_device_lower = raster_device.lower() - if raster_device_lower == 'pngmono': - # Convert to 1-bit black and white (matches Ghostscript pngmono device) + if raster_device_lower in ('pngmono', 'pngmonod'): + # Convert to 1-bit black and white (matches Ghostscript pngmono/pngmonod) if pil_image.mode != '1': if pil_image.mode not in ('L', '1'): pil_image = pil_image.convert('L') @@ -184,7 +189,15 @@ def _process_image_for_output( # pngalpha: keep RGBA as-is # Determine output format based on raster_device - png_devices = ('png', 'pngmono', 'pnggray', 'png256', 'png16m', 'pngalpha') + png_devices = ( + 'png', + 'pngmono', + 'pngmonod', + 'pnggray', + 'png256', + 'png16m', + 'pngalpha', + ) if raster_device_lower in png_devices: format_name = 'PNG' elif raster_device_lower in ('jpeg', 'jpeggray', 'jpg'): diff --git a/tests/test_rasterizer.py b/tests/test_rasterizer.py index dec805c1..56e79593 100644 --- a/tests/test_rasterizer.py +++ b/tests/test_rasterizer.py @@ -213,6 +213,34 @@ class TestRasterizerHookDirect: assert result == img assert img.exists() + @pytest.mark.skipif(not PYPDFIUM_AVAILABLE, reason="pypdfium2 not installed") + def test_pypdfium_pngmonod_produces_1bit(self, resources, tmp_path): + """Pngmonod is treated like pngmono by pypdfium: it yields a 1-bit PNG.""" + pm = get_plugin_manager([]) + options = OcrOptions( + input_file=resources / 'graph.pdf', + output_file=tmp_path / 'out.pdf', + rasterizer='pypdfium', + ) + + img = tmp_path / 'pngmonod_test.png' + result = pm.rasterize_pdf_page( + input_file=resources / 'graph.pdf', + output_file=img, + raster_device='pngmonod', + raster_dpi=Resolution(50, 50), + page_dpi=Resolution(50, 50), + pageno=1, + rotation=0, + filter_vector=False, + stop_on_soft_error=True, + options=options, + use_cropbox=False, + ) + assert result == img + with Image.open(img) as im: + assert im.mode == '1' + def _create_gradient_image(width: int, height: int) -> Image.Image: """Create an image with multiple gradients to detect rasterization errors.