From e8ed5105430f0a6951b32fd1c28e5b4f6bbb4f55 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Thu, 1 Jun 2023 23:49:34 -0700 Subject: [PATCH 1/5] Add stop on soft render errors and option to override --- docs/advanced.rst | 13 ++++++++- docs/pdfsecurity.rst | 18 +++++-------- src/ocrmypdf/_exec/ghostscript.py | 6 ++++- src/ocrmypdf/_pipeline.py | 3 +++ src/ocrmypdf/api.py | 1 + src/ocrmypdf/builtin_plugins/ghostscript.py | 4 +++ src/ocrmypdf/cli.py | 8 ++++++ src/ocrmypdf/pluginspec.py | 29 ++++++++++++++++----- tests/plugins/gs_feature_elision.py | 1 + tests/plugins/gs_pdfa_failure.py | 1 + tests/plugins/gs_raster_failure.py | 1 + tests/plugins/gs_render_failure.py | 1 + tests/test_rotation.py | 2 ++ 13 files changed, 68 insertions(+), 20 deletions(-) diff --git a/docs/advanced.rst b/docs/advanced.rst index 1b4db7c5..5061c575 100644 --- a/docs/advanced.rst +++ b/docs/advanced.rst @@ -104,7 +104,7 @@ was requested, the preprocessed image layer will be inserted. If you want to adjust the amount of time spent on OCR, change ``--tesseract-timeout``. You can also automatically skip images that exceed a certain number of megapixels with ``--skip-big``. (A 300 DPI, -8.5×11" page is 8.4 megapixels.) +8.5×11" page image is 8.4 megapixels.) .. code-block:: bash @@ -241,6 +241,17 @@ PDF.js viewer. This works in all versions of Tesseract. +Rendering and rasterizing options +================================= + +.. versionadded:: 14.3.0 + +The ``--continue-on-soft-render-error`` option allows OCRmyPDF to +proceed if a page cannot be rasterized rendered. This is useful if you are +trying to get the best possible OCR from a PDF that is not well-formed, +and you are willing to accept some pages that may not visually match the +input, and that may not OCR well. + Return code policy ================== diff --git a/docs/pdfsecurity.rst b/docs/pdfsecurity.rst index db2f3410..40ea55cb 100644 --- a/docs/pdfsecurity.rst +++ b/docs/pdfsecurity.rst @@ -29,13 +29,9 @@ attack vectors. In short, PDFs `may contain viruses `__. -This -`article `__ -describes a high-paranoia method which allows potentially hostile PDFs -to be viewed and rasterized safely in a disposable virtual machine. A -trusted PDF created in this manner is converted to images and loses all -information making it searchable and losing all compression. OCRmyPDF -could be used to restore searchability. +If you do not trust a PDF or its source, do not open it or use OCRmyPDF +on it. Consider using a Docker container or virtual machine to isolate +an untrusted PDF from your system. How OCRmyPDF processes PDFs =========================== @@ -43,11 +39,11 @@ How OCRmyPDF processes PDFs OCRmyPDF must open and interpret your PDF in order to insert an OCR layer. First, it runs all PDFs through `pikepdf `__, a library based on -`qpdf `__, a program that repairs PDFs +`QPDF `__, a program that repairs PDFs with syntax errors. This is done because, in the author's experience, a significant number of PDFs in the wild, especially those created by -scanners, are not well-formed files. qpdf makes it more likely that -OCRmyPDF will succeed, but offers no security guarantees. qpdf is also +scanners, are not well-formed files. QPDF makes it more likely that +OCRmyPDF will succeed, but offers no security guarantees. QPDF is also used to split the PDF into single page PDFs. Finally, OCRmyPDF rasterizes each page of the PDF using @@ -133,7 +129,7 @@ The author also provides professional services that include OCR and building databases around PDFs, and is happy to provide consultation. Abbyy Cloud OCR is viable commercial alternative with a web services -API. Amazon Textract, Google Cloud Vision, and Microsoft Azure +API. Amazon Textract, Google Cloud Vision, and Microsoft Azure Computer Vision provide advanced OCR but have less PDF rendering capability. Password protection, digital signatures and certification diff --git a/src/ocrmypdf/_exec/ghostscript.py b/src/ocrmypdf/_exec/ghostscript.py index 5a488a3a..56672d4a 100644 --- a/src/ocrmypdf/_exec/ghostscript.py +++ b/src/ocrmypdf/_exec/ghostscript.py @@ -30,7 +30,7 @@ except AttributeError: log = logging.getLogger(__name__) -# Most reliable what to get the bitness of Python interpreter, according to Python docs +# Most reliable way to get the bitness of Python interpreter, according to Python docs _IS_64BIT = sys.maxsize > 2**32 _GSWIN = None @@ -63,6 +63,7 @@ def rasterize_pdf( page_dpi: Resolution | None = None, rotation: int | None = None, filter_vector: bool = False, + stop_on_error: bool = False, ): """Rasterize one page of a PDF at resolution raster_dpi in canvas units.""" raster_dpi = raster_dpi.round(6) @@ -83,6 +84,7 @@ def rasterize_pdf( f'-r{raster_dpi.x:f}x{raster_dpi.y:f}', ] + (['-dFILTERVECTOR'] if filter_vector else []) + + (['-dPDFSTOPONERROR'] if stop_on_error else []) + [ '-o', '-', @@ -161,6 +163,7 @@ def generate_pdfa( pdf_version: str = '1.5', pdfa_part: str = '2', progressbar_class=None, + stop_on_error: bool = False, ): # Ghostscript's compression is all or nothing. We can either force all images # to JPEG, force all to Flate/PNG, or let it decide how to encode the images. @@ -207,6 +210,7 @@ def generate_pdfa( "-dAutoRotatePages=/None", "-sColorConversionStrategy=" + strategy, ] + + (['-dPDFSTOPONERROR'] if stop_on_error else []) + compression_args + [ "-dJPEGQ=95", diff --git a/src/ocrmypdf/_pipeline.py b/src/ocrmypdf/_pipeline.py index 4f8ae31e..2ac173bc 100644 --- a/src/ocrmypdf/_pipeline.py +++ b/src/ocrmypdf/_pipeline.py @@ -343,6 +343,7 @@ def rasterize_preview(input_file: Path, page_context: PageContext) -> Path: page_dpi=page_dpi, rotation=0, filter_vector=False, + stop_on_soft_error=not page_context.options.continue_on_soft_render_error, ) return output_file @@ -455,6 +456,7 @@ def rasterize( pageno=pageinfo.pageno + 1, rotation=correction, filter_vector=remove_vectors, + stop_on_soft_error=not page_context.options.continue_on_soft_render_error, ) return output_file @@ -736,6 +738,7 @@ def convert_to_pdfa(input_pdf: Path, input_ps_stub: Path, context: PdfContext) - if options.progress_bar else None ), + stop_on_soft_error=not options.continue_on_soft_render_error, ) return output_file diff --git a/src/ocrmypdf/api.py b/src/ocrmypdf/api.py index d31bc545..1c6d01eb 100644 --- a/src/ocrmypdf/api.py +++ b/src/ocrmypdf/api.py @@ -248,6 +248,7 @@ def ocr( # noqa: ruff: disable=D417 user_words: os.PathLike | None = None, user_patterns: os.PathLike | None = None, fast_web_view: float | None = None, + continue_on_soft_render_error: bool | None = None, plugins: Iterable[StrPath] | None = None, plugin_manager=None, keep_temporary_files: bool | None = None, diff --git a/src/ocrmypdf/builtin_plugins/ghostscript.py b/src/ocrmypdf/builtin_plugins/ghostscript.py index aa3e5f44..5d6e1374 100644 --- a/src/ocrmypdf/builtin_plugins/ghostscript.py +++ b/src/ocrmypdf/builtin_plugins/ghostscript.py @@ -45,6 +45,7 @@ def rasterize_pdf_page( page_dpi, rotation, filter_vector, + stop_on_soft_error, ): """Rasterize a single page of a PDF file using Ghostscript.""" ghostscript.rasterize_pdf( @@ -56,6 +57,7 @@ def rasterize_pdf_page( page_dpi=page_dpi, rotation=rotation, filter_vector=filter_vector, + stop_on_error=stop_on_soft_error, ) return output_file @@ -69,6 +71,7 @@ def generate_pdfa( pdf_version, pdfa_part, progressbar_class, + stop_on_soft_error, ): """Generate a PDF/A from the list of PDF pages and PDF/A metadata.""" ghostscript.generate_pdfa( @@ -78,5 +81,6 @@ def generate_pdfa( pdf_version=pdf_version, pdfa_part=pdfa_part, progressbar_class=progressbar_class, + stop_on_error=stop_on_soft_error, ) return output_file diff --git a/src/ocrmypdf/cli.py b/src/ocrmypdf/cli.py index 81594911..5d73b22c 100644 --- a/src/ocrmypdf/cli.py +++ b/src/ocrmypdf/cli.py @@ -420,6 +420,14 @@ Online documentation is located at: "which do not benefit. If the threshold is 0 it will be apply to all files. " "Set the threshold very high to disable.", ) + advanced.add_argument( + '--continue-on-soft-render-error', + action='store_true', + help="Continue processing pages after a recoverable PDF rendering error. " + "A recoverable error is one that does not prevent the page from being " + "rendered, but may result in visual differences compared to the input " + "file. Missing fonts are a typical source of these errors.", + ) advanced.add_argument( '--plugin', dest='plugins', diff --git a/src/ocrmypdf/pluginspec.py b/src/ocrmypdf/pluginspec.py index eedceda6..46f2944e 100644 --- a/src/ocrmypdf/pluginspec.py +++ b/src/ocrmypdf/pluginspec.py @@ -203,6 +203,7 @@ def rasterize_pdf_page( page_dpi: Resolution | None, rotation: int | None, filter_vector: bool, + stop_on_soft_error: bool, ) -> Path: """Rasterize one page of a PDF at resolution raster_dpi in canvas units. @@ -213,19 +214,26 @@ def rasterize_pdf_page( Args: input_file: The PDF to rasterize. output_file: The desired name of the rasterized image. - raster_device: Type of image to produce at output_file - raster_dpi: Resolution at which to rasterize page - pageno: Page number to rasterize (beginning at page 1) - page_dpi: Resolution, overriding output image DPI - rotation: Cardinal angle, clockwise, to rotate page - filter_vector: If True, remove vector graphics objects + raster_device: Type of image to produce at output_file. + raster_dpi: Resolution in dots per inch at which to rasterize page. + pageno: Page number to rasterize (beginning at page 1). + page_dpi: Resolution, overriding output image DPI. + rotation: Cardinal angle, clockwise, to rotate page. + filter_vector: If True, remove vector graphics objects. + stop_on_soft_error: If there is an "soft error" such that PDF page image + generation can proceed, but may visually differ from the original, + the implementer of this hook should raise a detailed exception. If + ``False``, continue processing and report by logging it. If the hook + cannot proceed, it should always raise an exception, regardless of + this setting. One "soft error" would be a missing font that is + required to properly rasterize the PDF. + Returns: Path: output_file if successful Note: This hook will be called from child processes. Modifying global state will not affect the main process or other child processes. - Note: This is a :ref:`firstresult hook`. """ @@ -462,6 +470,7 @@ def generate_pdfa( pdf_version: str, pdfa_part: str, progressbar_class, + stop_on_soft_error: bool, ) -> Path: """Generate a PDF/A. @@ -492,6 +501,12 @@ def generate_pdfa( and the name of the work units ("page"). Then ``instance.update()`` will be called when a work unit is completed. If ``None``, no progress information is reported. + stop_on_soft_error: If there is an "soft error" such that PDF/A generation + can proceed and produce a valid PDF/A, but output may be invalid or + may not visually resemble the original, the implementer of this hook + should raise a detailed exception. If ``False``, continue processing + and report by logging it. If the hook cannot proceed, it should always + raise an exception, regardless of this setting. Returns: Path: If successful, the hook should return ``output_file``. diff --git a/tests/plugins/gs_feature_elision.py b/tests/plugins/gs_feature_elision.py index ce829f5f..dcd4c849 100644 --- a/tests/plugins/gs_feature_elision.py +++ b/tests/plugins/gs_feature_elision.py @@ -31,6 +31,7 @@ def generate_pdfa(pdf_pages, pdfmark, output_file, compression, pdf_version, pdf pdf_version=pdf_version, pdfa_part=pdfa_part, progressbar_class=None, + stop_on_soft_error=True, ) mock.assert_called_once() return output_file diff --git a/tests/plugins/gs_pdfa_failure.py b/tests/plugins/gs_pdfa_failure.py index a67aa872..e9ec9697 100644 --- a/tests/plugins/gs_pdfa_failure.py +++ b/tests/plugins/gs_pdfa_failure.py @@ -33,6 +33,7 @@ def generate_pdfa(pdf_pages, pdfmark, output_file, compression, pdf_version, pdf pdf_version=pdf_version, pdfa_part=pdfa_part, progressbar_class=None, + stop_on_soft_error=True, ) mock.assert_called() return output_file diff --git a/tests/plugins/gs_raster_failure.py b/tests/plugins/gs_raster_failure.py index 70066273..982ae359 100644 --- a/tests/plugins/gs_raster_failure.py +++ b/tests/plugins/gs_raster_failure.py @@ -39,6 +39,7 @@ def rasterize_pdf_page( page_dpi=page_dpi, rotation=rotation, filter_vector=filter_vector, + stop_on_soft_error=True, ) mock.assert_called() return output_file diff --git a/tests/plugins/gs_render_failure.py b/tests/plugins/gs_render_failure.py index 97ee9c77..56ec3905 100644 --- a/tests/plugins/gs_render_failure.py +++ b/tests/plugins/gs_render_failure.py @@ -28,6 +28,7 @@ def generate_pdfa(pdf_pages, pdfmark, output_file, compression, pdf_version, pdf pdf_version=pdf_version, pdfa_part=pdfa_part, progressbar_class=None, + stop_on_soft_error=True, ) mock.assert_called() return output_file diff --git a/tests/test_rotation.py b/tests/test_rotation.py index f71fe3a1..2fc9ba0c 100644 --- a/tests/test_rotation.py +++ b/tests/test_rotation.py @@ -275,6 +275,7 @@ def test_rasterize_rotates(resources, tmp_path): pageno=1, rotation=90, filter_vector=False, + stop_on_soft_error=True, ) with Image.open(img) as im: assert im.size == (83, 200), "Image not rotated" @@ -289,6 +290,7 @@ def test_rasterize_rotates(resources, tmp_path): pageno=1, rotation=180, filter_vector=False, + stop_on_soft_error=True, ) assert Image.open(img).size == (200, 83), "Image not rotated" From 417ee067a20ef0216d9f8d5ecfebd0f838a8bc5d Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Fri, 2 Jun 2023 02:47:41 -0700 Subject: [PATCH 2/5] Remove obsolete universal_newlines check --- src/ocrmypdf/subprocess/__init__.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/ocrmypdf/subprocess/__init__.py b/src/ocrmypdf/subprocess/__init__.py index c9aff4ab..d87d6fa3 100644 --- a/src/ocrmypdf/subprocess/__init__.py +++ b/src/ocrmypdf/subprocess/__init__.py @@ -118,8 +118,6 @@ def run_polling_stderr( def _fix_process_args( args: Args, env: OsEnviron | None, kwargs ) -> tuple[Args, OsEnviron, logging.Logger, bool]: - assert 'universal_newlines' not in kwargs, "Use text= instead of universal_newlines" - if not env: env = os.environ From 5f211ecf6fe3068cc149ef869f5988c89a167f7e Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Fri, 2 Jun 2023 02:47:41 -0700 Subject: [PATCH 3/5] Add test cases for soft errors --- tests/plugins/gs_raster_soft_error.py | 47 ++++++++++++++++++++++ tests/plugins/gs_render_soft_error.py | 44 ++++++++++++++++++++ tests/test_soft_error.py | 58 +++++++++++++++++++++++++++ 3 files changed, 149 insertions(+) create mode 100644 tests/plugins/gs_raster_soft_error.py create mode 100644 tests/plugins/gs_render_soft_error.py create mode 100644 tests/test_soft_error.py diff --git a/tests/plugins/gs_raster_soft_error.py b/tests/plugins/gs_raster_soft_error.py new file mode 100644 index 00000000..57a740b6 --- /dev/null +++ b/tests/plugins/gs_raster_soft_error.py @@ -0,0 +1,47 @@ +# SPDX-FileCopyrightText: 2022 James R. Barlow +# SPDX-License-Identifier: MIT + +from __future__ import annotations + +from pathlib import Path +from subprocess import CalledProcessError +from unittest.mock import patch + +from ocrmypdf import hookimpl +from ocrmypdf.builtin_plugins import ghostscript +from ocrmypdf.subprocess import run + + +def fail_if_stoponerror(args, **kwargs): + if '-dPDFSTOPONERROR' in args: + raise CalledProcessError(1, 'gs', output=b"", stderr=b"PDF STOP ON ERROR") + return run(args, **kwargs) + + +@hookimpl +def rasterize_pdf_page( + input_file, + output_file, + raster_device, + raster_dpi, + pageno, + page_dpi, + rotation, + filter_vector, + stop_on_soft_error, +) -> Path: + with patch('ocrmypdf._exec.ghostscript.run') as mock: + mock.side_effect = fail_if_stoponerror + ghostscript.rasterize_pdf_page( + input_file=input_file, + output_file=output_file, + raster_device=raster_device, + raster_dpi=raster_dpi, + pageno=pageno, + page_dpi=page_dpi, + rotation=rotation, + filter_vector=filter_vector, + stop_on_soft_error=stop_on_soft_error, + ) + mock.assert_called() + return output_file diff --git a/tests/plugins/gs_render_soft_error.py b/tests/plugins/gs_render_soft_error.py new file mode 100644 index 00000000..f80e0401 --- /dev/null +++ b/tests/plugins/gs_render_soft_error.py @@ -0,0 +1,44 @@ +# SPDX-FileCopyrightText: 2022 James R. Barlow +# SPDX-License-Identifier: MIT + +from __future__ import annotations + +from pathlib import Path +from subprocess import CalledProcessError +from unittest.mock import patch + +from ocrmypdf import hookimpl +from ocrmypdf.builtin_plugins import ghostscript +from ocrmypdf.subprocess import run_polling_stderr + + +def fail_if_stoponerror(args, **kwargs): + if '-dPDFSTOPONERROR' in args: + raise CalledProcessError(1, 'gs', output=b"", stderr=b"PDF STOP ON ERROR") + return run_polling_stderr(args, **kwargs) + + +@hookimpl +def generate_pdfa( + pdf_pages, + pdfmark, + output_file, + compression, + pdf_version, + pdfa_part, + stop_on_soft_error, +): + with patch('ocrmypdf._exec.ghostscript.run_polling_stderr') as mock: + mock.side_effect = fail_if_stoponerror + ghostscript.generate_pdfa( + pdf_pages=pdf_pages, + pdfmark=pdfmark, + output_file=output_file, + compression=compression, + pdf_version=pdf_version, + pdfa_part=pdfa_part, + progressbar_class=None, + stop_on_soft_error=stop_on_soft_error, + ) + mock.assert_called() + return output_file diff --git a/tests/test_soft_error.py b/tests/test_soft_error.py new file mode 100644 index 00000000..b4280d01 --- /dev/null +++ b/tests/test_soft_error.py @@ -0,0 +1,58 @@ +# SPDX-FileCopyrightText: 2023 James R. Barlow +# SPDX-License-Identifier: MPL-2.0 + +from __future__ import annotations + +from ocrmypdf.exceptions import ExitCode + +from .conftest import run_ocrmypdf + + +def test_raster_continue_on_soft_error(resources, outpdf): + p = run_ocrmypdf( + resources / 'francais.pdf', + outpdf, + '--continue-on-soft-render-error', + '--plugin', + 'tests/plugins/tesseract_noop.py', + '--plugin', + 'tests/plugins/gs_raster_soft_error.py', + ) + assert p.returncode == ExitCode.ok + + +def test_raster_stop_on_soft_error(resources, outpdf): + p = run_ocrmypdf( + resources / 'francais.pdf', + outpdf, + '--plugin', + 'tests/plugins/tesseract_noop.py', + '--plugin', + 'tests/plugins/gs_raster_soft_error.py', + ) + assert p.returncode == ExitCode.child_process_error + + +def test_render_continue_on_soft_error(resources, outpdf): + p = run_ocrmypdf( + resources / 'francais.pdf', + outpdf, + '--continue-on-soft-render-error', + '--plugin', + 'tests/plugins/tesseract_noop.py', + '--plugin', + 'tests/plugins/gs_render_soft_error.py', + ) + assert p.returncode == ExitCode.ok + + +def test_render_stop_on_soft_error(resources, outpdf): + p = run_ocrmypdf( + resources / 'francais.pdf', + outpdf, + '--plugin', + 'tests/plugins/tesseract_noop.py', + '--plugin', + 'tests/plugins/gs_render_soft_error.py', + ) + assert p.returncode == ExitCode.child_process_error From 0a7b60cda54440f6c9b0f003547502c231a1be13 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Fri, 2 Jun 2023 02:47:41 -0700 Subject: [PATCH 4/5] Fix error on metadata block --- tests/test_metadata.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tests/test_metadata.py b/tests/test_metadata.py index 3bc14bb5..2f1cc878 100644 --- a/tests/test_metadata.py +++ b/tests/test_metadata.py @@ -350,7 +350,15 @@ def test_malformed_docinfo(caplog, resources, outdir): pike.save(outdir / 'layers.rendered.pdf', fix_metadata_version=False) options = get_parser().parse_args( - args=['-j', '1', '--output-type', 'pdfa-2', 'a.pdf', 'b.pdf'] + args=[ + '-j', + '1', + '--continue-on-soft-error', + '--output-type', + 'pdfa-2', + 'a.pdf', + 'b.pdf', + ] ) pdfinfo = PdfInfo(outdir / 'layers.rendered.pdf') context = PdfContext( From 05ecb6ca461776b9b107d21f08c293ac0c323fd1 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Fri, 2 Jun 2023 02:47:41 -0700 Subject: [PATCH 5/5] Resolve Windows errors with -dPDFSTOPONERROR --- src/ocrmypdf/_exec/ghostscript.py | 4 ++++ tests/test_helpers.py | 5 +++-- tests/test_metadata.py | 2 +- tests/test_soft_error.py | 5 +++++ 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/ocrmypdf/_exec/ghostscript.py b/src/ocrmypdf/_exec/ghostscript.py index 56672d4a..5e5e4fb5 100644 --- a/src/ocrmypdf/_exec/ghostscript.py +++ b/src/ocrmypdf/_exec/ghostscript.py @@ -196,6 +196,10 @@ def generate_pdfa( # https://bugs.ghostscript.com/show_bug.cgi?id=705187 compression_args.append('-dNEWPDF=false') + if os.name == 'nt': + # Windows has lots of fatal "permission denied" errors + stop_on_error = False + # nb no need to specify ProcessColorModel when ColorConversionStrategy # is set; see: # https://bugs.ghostscript.com/show_bug.cgi?id=699392 diff --git a/tests/test_helpers.py b/tests/test_helpers.py index e1992b32..b4cb541d 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -17,6 +17,7 @@ from ocrmypdf import helpers from .conftest import running_in_docker needs_symlink = pytest.mark.skipif(os.name == 'nt', reason='needs posix symlink') +windows_only = pytest.mark.skipif(os.name != 'nt', reason="Windows test") class TestSafeSymlink: @@ -93,7 +94,7 @@ class TestFileIsWritable: assert not helpers.is_file_writable(pathmock) -@pytest.mark.skipif(os.name != 'nt', reason="Windows test") +@windows_only def test_gs_install_locations(): # pylint: disable=import-outside-toplevel from ocrmypdf.subprocess._windows import _gs_version_in_path_key @@ -104,7 +105,7 @@ def test_gs_install_locations(): ) -@pytest.mark.skipif(os.name != 'nt', reason="Windows test") +@windows_only def test_shim_paths(tmp_path): # pylint: disable=import-outside-toplevel from ocrmypdf.subprocess._windows import shim_env_path diff --git a/tests/test_metadata.py b/tests/test_metadata.py index 2f1cc878..786e3cf5 100644 --- a/tests/test_metadata.py +++ b/tests/test_metadata.py @@ -353,7 +353,7 @@ def test_malformed_docinfo(caplog, resources, outdir): args=[ '-j', '1', - '--continue-on-soft-error', + '--continue-on-soft-render-error', '--output-type', 'pdfa-2', 'a.pdf', diff --git a/tests/test_soft_error.py b/tests/test_soft_error.py index b4280d01..6d21fb04 100644 --- a/tests/test_soft_error.py +++ b/tests/test_soft_error.py @@ -3,6 +3,10 @@ from __future__ import annotations +import os + +import pytest + from ocrmypdf.exceptions import ExitCode from .conftest import run_ocrmypdf @@ -46,6 +50,7 @@ def test_render_continue_on_soft_error(resources, outpdf): assert p.returncode == ExitCode.ok +@pytest.mark.skipif(os.name == 'nt', reason='Ghostscript on Windows errors out') def test_render_stop_on_soft_error(resources, outpdf): p = run_ocrmypdf( resources / 'francais.pdf',