From bc745d4d819d64fb787099b93dd1b4389c28bf94 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Tue, 11 Nov 2025 14:03:37 -0800 Subject: [PATCH] Replace magic Ghostscript raster device strings with StrEnum --- src/ocrmypdf/_exec/ghostscript.py | 3 ++- src/ocrmypdf/_pipeline.py | 21 +++++++++++++-------- src/ocrmypdf/pluginspec.py | 15 ++++++++++++++- tests/test_ghostscript.py | 11 ++++++----- tests/test_optimize.py | 3 ++- tests/test_preprocessing.py | 5 +++-- tests/test_rotation.py | 7 ++++--- 7 files changed, 44 insertions(+), 21 deletions(-) diff --git a/src/ocrmypdf/_exec/ghostscript.py b/src/ocrmypdf/_exec/ghostscript.py index e81db3d6..92f757fe 100644 --- a/src/ocrmypdf/_exec/ghostscript.py +++ b/src/ocrmypdf/_exec/ghostscript.py @@ -22,6 +22,7 @@ from ocrmypdf.exceptions import ( SubprocessOutputError, ) from ocrmypdf.helpers import Resolution +from ocrmypdf.pluginspec import GhostscriptRasterDevice from ocrmypdf.subprocess import get_version, run, run_polling_stderr COLOR_CONVERSION_STRATEGIES = frozenset( @@ -98,7 +99,7 @@ def rasterize_pdf( input_file: os.PathLike, output_file: os.PathLike, *, - raster_device: str, + raster_device: GhostscriptRasterDevice, raster_dpi: Resolution, pageno: int = 1, page_dpi: Resolution | None = None, diff --git a/src/ocrmypdf/_pipeline.py b/src/ocrmypdf/_pipeline.py index 08ba8141..af9e1c61 100644 --- a/src/ocrmypdf/_pipeline.py +++ b/src/ocrmypdf/_pipeline.py @@ -45,7 +45,7 @@ from ocrmypdf.pdfa import ( speculative_pdfa_conversion, ) from ocrmypdf.pdfinfo import Colorspace, Encoding, FloatRect, PageInfo, PdfInfo -from ocrmypdf.pluginspec import OrientationConfidence +from ocrmypdf.pluginspec import GhostscriptRasterDevice, OrientationConfidence try: from pi_heif import register_heif_opener @@ -403,7 +403,7 @@ def rasterize_preview(input_file: Path, page_context: PageContext) -> Path: page_context.plugin_manager.rasterize_pdf_page( input_file=input_file, output_file=output_file, - raster_device='jpeggray', + raster_device=GhostscriptRasterDevice.JPEGGRAY, raster_dpi=canvas_dpi, pageno=page_context.pageinfo.pageno + 1, page_dpi=page_dpi, @@ -526,7 +526,12 @@ def rasterize( Returns: Path: The output PNG file path. """ - colorspaces = ['pngmono', 'pnggray', 'png256', 'png16m'] + colorspaces = [ + GhostscriptRasterDevice.PNGMONO, + GhostscriptRasterDevice.PNGGRAY, + GhostscriptRasterDevice.PNG256, + GhostscriptRasterDevice.PNG16M, + ] device_idx = 0 if remove_vectors is None: @@ -543,15 +548,15 @@ def rasterize( continue # ignore masks if image.bpc > 1: if image.color == Colorspace.index: - device_idx = at_least('png256') + device_idx = at_least(GhostscriptRasterDevice.PNG256) elif image.color == Colorspace.gray: - device_idx = at_least('pnggray') + device_idx = at_least(GhostscriptRasterDevice.PNGGRAY) else: - device_idx = at_least('png16m') + device_idx = at_least(GhostscriptRasterDevice.PNG16M) if pageinfo.has_vector: - log.debug("Page has vector content, using png16m") - device_idx = at_least('png16m') + log.debug(f"Page has vector content, using {GhostscriptRasterDevice.PNG16M}") + device_idx = at_least(GhostscriptRasterDevice.PNG16M) device = colorspaces[device_idx] diff --git a/src/ocrmypdf/pluginspec.py b/src/ocrmypdf/pluginspec.py index 86270a76..d50e03ca 100644 --- a/src/ocrmypdf/pluginspec.py +++ b/src/ocrmypdf/pluginspec.py @@ -8,6 +8,7 @@ from __future__ import annotations from abc import ABC, abstractmethod from argparse import ArgumentParser from collections.abc import Sequence, Set +from enum import StrEnum from logging import Handler from pathlib import Path from typing import TYPE_CHECKING, NamedTuple @@ -30,6 +31,18 @@ if TYPE_CHECKING: # pylint: enable=ungrouped-imports + +class GhostscriptRasterDevice(StrEnum): + """Possible raster devices for Ghostscript.""" + + JPEGGRAY = 'jpeggray' + JPEGCOLOR = 'jpeg' + PNGMONO = 'pngmono' + PNGGRAY = 'pnggray' + PNG256 = 'png256' + PNG16M = 'png16m' + + hookspec = pluggy.HookspecMarker('ocrmypdf') # pylint: disable=unused-argument @@ -207,7 +220,7 @@ def validate(pdfinfo: PdfInfo, options: OcrOptions) -> None: def rasterize_pdf_page( input_file: Path, output_file: Path, - raster_device: str, + raster_device: GhostscriptRasterDevice, raster_dpi: Resolution, pageno: int, page_dpi: Resolution | None, diff --git a/tests/test_ghostscript.py b/tests/test_ghostscript.py index 33c97ea3..7d971216 100644 --- a/tests/test_ghostscript.py +++ b/tests/test_ghostscript.py @@ -20,6 +20,7 @@ from ocrmypdf._exec.ghostscript import DuplicateFilter, rasterize_pdf from ocrmypdf.builtin_plugins.ghostscript import _repair_gs106_jpeg_corruption from ocrmypdf.exceptions import ColorConversionNeededError, ExitCode, InputFileError from ocrmypdf.helpers import Resolution +from ocrmypdf.pluginspec import GhostscriptRasterDevice from .conftest import check_ocrmypdf, run_ocrmypdf_api @@ -43,7 +44,7 @@ def test_rasterize_size(francais, outdir): rasterize_pdf( path, outdir / 'out.png', - raster_device='pngmono', + raster_device=GhostscriptRasterDevice.PNGMONO, raster_dpi=Resolution( target_size[0] / page_size[0], target_size[1] / page_size[1] ), @@ -67,7 +68,7 @@ def test_rasterize_rotated(francais, outdir, caplog): rasterize_pdf( path, outdir / 'out.png', - raster_device='pngmono', + raster_device=GhostscriptRasterDevice.PNGMONO, raster_dpi=Resolution( target_size[0] / page_size[0], target_size[1] / page_size[1] ), @@ -157,7 +158,7 @@ def test_rasterize_pdf_errors(resources, no_outpdf, caplog): rasterize_pdf( resources / 'francais.pdf', no_outpdf, - raster_device='pngmono', + raster_device=GhostscriptRasterDevice.PNGMONO, raster_dpi=Resolution(100, 100), ) assert "this is an error" in caplog.text @@ -267,7 +268,7 @@ def test_recoverable_image_error(pdf_with_invalid_image, outdir, caplog): rasterize_pdf( outdir / 'invalid_image.pdf', outdir / 'out.png', - raster_device='pngmono', + raster_device=GhostscriptRasterDevice.PNGMONO, raster_dpi=Resolution(10, 10), stop_on_error=False, ) @@ -289,7 +290,7 @@ def test_recoverable_image_error_with_stop(pdf_with_invalid_image, outdir, caplo rasterize_pdf( outdir / 'invalid_image.pdf', outdir / 'out.png', - raster_device='pngmono', + raster_device=GhostscriptRasterDevice.PNGMONO, raster_dpi=Resolution(100, 100), stop_on_error=True, ) diff --git a/tests/test_optimize.py b/tests/test_optimize.py index 603dbc19..710ffb6a 100644 --- a/tests/test_optimize.py +++ b/tests/test_optimize.py @@ -19,6 +19,7 @@ from ocrmypdf._exec import jbig2enc, pngquant from ocrmypdf._exec.ghostscript import rasterize_pdf from ocrmypdf.helpers import IMG2PDF_KWARGS, Resolution from ocrmypdf.optimize import PdfImage, extract_image_filter +from ocrmypdf.pluginspec import GhostscriptRasterDevice from tests.conftest import check_ocrmypdf needs_pngquant = pytest.mark.skipif( @@ -54,7 +55,7 @@ def test_mono_not_inverted(resources, outdir): rasterize_pdf( outdir / 'out.pdf', outdir / 'im.png', - raster_device='pnggray', + raster_device=GhostscriptRasterDevice.PNGGRAY, raster_dpi=Resolution(10, 10), ) diff --git a/tests/test_preprocessing.py b/tests/test_preprocessing.py index 498524b2..9e42c888 100644 --- a/tests/test_preprocessing.py +++ b/tests/test_preprocessing.py @@ -12,6 +12,7 @@ from ocrmypdf._exec import ghostscript, tesseract from ocrmypdf.exceptions import ExitCode from ocrmypdf.helpers import Resolution from ocrmypdf.pdfinfo import PdfInfo +from ocrmypdf.pluginspec import GhostscriptRasterDevice from .conftest import check_ocrmypdf, have_unpaper, run_ocrmypdf @@ -28,7 +29,7 @@ def test_deskew(resources, outdir): ghostscript.rasterize_pdf( deskewed_pdf, deskewed_png, - raster_device='pngmono', + raster_device=GhostscriptRasterDevice.PNGMONO, raster_dpi=Resolution(150, 150), pageno=1, ) @@ -65,7 +66,7 @@ def test_remove_background(resources, outdir): ghostscript.rasterize_pdf( output_pdf, output_png, - raster_device='png16m', + raster_device=GhostscriptRasterDevice.PNG16M, raster_dpi=Resolution(100, 100), pageno=1, ) diff --git a/tests/test_rotation.py b/tests/test_rotation.py index e881d3ee..b23f6d0f 100644 --- a/tests/test_rotation.py +++ b/tests/test_rotation.py @@ -19,6 +19,7 @@ from ocrmypdf._exec import ghostscript from ocrmypdf._plugin_manager import get_plugin_manager from ocrmypdf.helpers import IMG2PDF_KWARGS, Resolution from ocrmypdf.pdfinfo import PdfInfo +from ocrmypdf.pluginspec import GhostscriptRasterDevice from .conftest import check_ocrmypdf, run_ocrmypdf_api @@ -40,7 +41,7 @@ def compare_images_monochrome( ghostscript.rasterize_pdf( pdf, png, - raster_device='pngmono', + raster_device=GhostscriptRasterDevice.PNGMONO, raster_dpi=Resolution(100, 100), pageno=pageno, rotation=0, @@ -348,7 +349,7 @@ def test_rasterize_rotates(resources, tmp_path, rasterizer): pm.rasterize_pdf_page( input_file=resources / 'graph.pdf', output_file=img, - raster_device='pngmono', + raster_device=GhostscriptRasterDevice.PNGMONO, raster_dpi=Resolution(20, 20), page_dpi=Resolution(20, 20), pageno=1, @@ -365,7 +366,7 @@ def test_rasterize_rotates(resources, tmp_path, rasterizer): pm.rasterize_pdf_page( input_file=resources / 'graph.pdf', output_file=img, - raster_device='pngmono', + raster_device=GhostscriptRasterDevice.PNGMONO, raster_dpi=Resolution(20, 20), page_dpi=Resolution(20, 20), pageno=1,