Move Ghostscript rasterize_pdf to plugin

This commit is contained in:
James R. Barlow
2020-06-08 17:10:27 -07:00
parent a9a473f2e5
commit b109445215
11 changed files with 154 additions and 86 deletions
+6 -6
View File
@@ -321,9 +321,9 @@ def rasterize_preview(input_file, page_context):
output_file = page_context.get_path('rasterize_preview.jpg')
canvas_dpi = get_canvas_square_dpi(page_context.pageinfo, page_context.options)
page_dpi = get_page_square_dpi(page_context.pageinfo, page_context.options)
ghostscript.rasterize_pdf(
input_file,
output_file,
page_context.plugin_manager.hook.rasterize_pdf_page(
input_file=input_file,
output_file=output_file,
raster_device='jpeggray',
raster_dpi=canvas_dpi,
page_dpi=page_dpi,
@@ -430,9 +430,9 @@ def rasterize(
canvas_dpi = get_canvas_square_dpi(pageinfo, page_context.options)
page_dpi = get_page_square_dpi(pageinfo, page_context.options)
ghostscript.rasterize_pdf(
input_file,
output_file,
page_context.plugin_manager.hook.rasterize_pdf_page(
input_file=input_file,
output_file=output_file,
raster_device=device,
raster_dpi=canvas_dpi,
page_dpi=page_dpi,
+4 -1
View File
@@ -33,7 +33,10 @@ def get_plugin_manager(plugins: List[str], builtins=True):
pm.add_hookspecs(pluginspec)
if builtins:
all_plugins = ['ocrmypdf.builtin_plugins'] + plugins
all_plugins = [
'ocrmypdf.builtin_plugins.ghostscript',
'ocrmypdf.builtin_plugins.tesseract_ocr',
] + plugins
else:
all_plugins = plugins
for name in all_plugins:
+1 -49
View File
@@ -27,7 +27,6 @@ from shutil import copyfileobj
import PIL
from ocrmypdf._plugin_manager import get_plugin_manager
from ocrmypdf._unicodefun import verify_python3_env
from ocrmypdf.exceptions import (
BadArgsError,
@@ -35,14 +34,7 @@ from ocrmypdf.exceptions import (
MissingDependencyError,
OutputFileAccessError,
)
from ocrmypdf.exec import (
check_external_program,
ghostscript,
jbig2enc,
pngquant,
tesseract,
unpaper,
)
from ocrmypdf.exec import check_external_program, jbig2enc, pngquant, unpaper
from ocrmypdf.helpers import (
is_file_writable,
is_iterable_notstr,
@@ -92,10 +84,6 @@ def check_options_languages(options, plugin_manager):
def check_options_output(options):
# We have these constraints to check for.
# 1. Ghostscript < 9.20 mangles multibyte Unicode
# 2. hocr doesn't work on non-Latin languages (so don't select it)
is_latin = options.languages.issubset(HOCR_OK_LANGS)
if options.pdf_renderer == 'hocr' and not is_latin:
@@ -106,25 +94,6 @@ def check_options_output(options):
)
log.warning(msg)
if ghostscript.version() < '9.20' and options.output_type != 'pdf' and not is_latin:
# https://bugs.ghostscript.com/show_bug.cgi?id=696874
# Ghostscript < 9.20 fails to encode multibyte characters properly
msg = (
"The installed version of Ghostscript does not work correctly "
"with the OCR languages you specified. Use --output-type pdf or "
"upgrade to Ghostscript 9.20 or later to avoid this issue."
)
msg += f"Found Ghostscript {ghostscript.version()}"
log.warning(msg)
if options.output_type == 'pdfa':
options.output_type = 'pdfa-2'
if options.output_type == 'pdfa-3' and ghostscript.version() < '9.19':
raise MissingDependencyError(
"--output-type pdfa-3 requires Ghostscript 9.19 or later"
)
lossless_reconstruction = False
if not any(
(
@@ -291,7 +260,6 @@ def check_options(options, plugin_manager):
check_options_optimizing(options)
check_options_advanced(options)
check_options_pillow(options)
check_dependency_versions(options)
plugin_manager.hook.check_options(options=options)
@@ -432,19 +400,3 @@ def report_output_file_size(options, input_file, output_file):
f"The output file size is {ratio:.2f}× larger than the input file.\n"
f"{explanation}"
)
def check_dependency_versions(options):
check_external_program(
program='gs',
package='ghostscript',
version_checker=ghostscript.version,
need_version='9.15', # limited by Travis CI / Ubuntu 14.04 backports
)
gs_version = ghostscript.version()
if gs_version in ('9.24', '9.51'):
raise MissingDependencyError(
f"Ghostscript {gs_version} contains serious regressions and is not "
"supported. Please upgrade to a newer version, or downgrade to the "
"previous version."
)
-2
View File
@@ -14,5 +14,3 @@
#
# You should have received a copy of the GNU General Public License
# along with OCRmyPDF. If not, see <http://www.gnu.org/licenses/>.
from ocrmypdf.builtin_plugins.tesseract_ocr import *
@@ -0,0 +1,90 @@
# © 2020 James R. Barlow: github.com/jbarlow83
#
# This file is part of OCRmyPDF.
#
# OCRmyPDF is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# OCRmyPDF is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with OCRmyPDF. If not, see <http://www.gnu.org/licenses/>.
import logging
from pathlib import Path
from ocrmypdf import hookimpl
from ocrmypdf._validation import HOCR_OK_LANGS
from ocrmypdf.exceptions import MissingDependencyError
from ocrmypdf.exec import check_external_program, ghostscript
from ocrmypdf.helpers import Resolution
log = logging.getLogger(__name__)
@hookimpl
def check_options(options):
gs_version = ghostscript.version()
check_external_program(
program='gs',
package='ghostscript',
version_checker=gs_version,
need_version='9.15', # limited by Travis CI / Ubuntu 14.04 backports
)
if gs_version in ('9.24', '9.51'):
raise MissingDependencyError(
f"Ghostscript {gs_version} contains serious regressions and is not "
"supported. Please upgrade to a newer version, or downgrade to the "
"previous version."
)
# We have these constraints to check for.
# 1. Ghostscript < 9.20 mangles multibyte Unicode
# 2. hocr doesn't work on non-Latin languages (so don't select it)
is_latin = options.languages.issubset(HOCR_OK_LANGS)
if gs_version < '9.20' and options.output_type != 'pdf' and not is_latin:
# https://bugs.ghostscript.com/show_bug.cgi?id=696874
# Ghostscript < 9.20 fails to encode multibyte characters properly
msg = (
"The installed version of Ghostscript does not work correctly "
"with the OCR languages you specified. Use --output-type pdf or "
"upgrade to Ghostscript 9.20 or later to avoid this issue."
)
msg += f"Found Ghostscript {gs_version}"
log.warning(msg)
if options.output_type == 'pdfa':
options.output_type = 'pdfa-2'
if options.output_type == 'pdfa-3' and ghostscript.version() < '9.19':
raise MissingDependencyError(
"--output-type pdfa-3 requires Ghostscript 9.19 or later"
)
@hookimpl
def rasterize_pdf_page(
input_file: Path,
output_file: Path,
raster_device: str,
raster_dpi: Resolution,
pageno: int,
page_dpi: Resolution = None,
rotation: int = None,
filter_vector: bool = False,
):
return ghostscript.rasterize_pdf(
input_file,
output_file,
raster_device=raster_device,
raster_dpi=raster_dpi,
pageno=pageno,
page_dpi=page_dpi,
rotation=rotation,
filter_vector=filter_vector,
)
+4 -1
View File
@@ -280,7 +280,10 @@ def check_external_program(
recommended=False,
):
try:
found_version = version_checker()
if callable(version_checker):
found_version = version_checker()
else:
found_version = version_checker
except (CalledProcessError, FileNotFoundError, MissingDependencyError):
_error_missing_program(program, package, required_for, recommended)
if not recommended:
+1 -17
View File
@@ -20,7 +20,6 @@
import logging
import os
import re
import warnings
from io import BytesIO
from os import fspath
from pathlib import Path
@@ -92,22 +91,7 @@ def rasterize_pdf(
rotation: int = None,
filter_vector: bool = False,
):
"""Rasterize one page of a PDF at resolution raster_dpi in canvas units.
The image is sized to match the integer pixels dimensions implied by
raster_dpi even if those numbers are noninteger. The image's DPI will
be overridden with the values in page_dpi.
:param input_file: pathlike
:param output_file: pathlike
:param raster_device:
:param raster_dpi: resolution at which to rasterize page
:param pageno: page number to rasterize (beginning at page 1)
:param page_dpi: resolution tuple (x, y) overriding output image DPI
:param rotation: 0, 90, 180, 270: clockwise angle to rotate page
:param filter_vector: if True, remove vector graphics objects
:return:
"""
"""Rasterize one page of a PDF at resolution raster_dpi in canvas units."""
raster_dpi = raster_dpi.round(6)
if not page_dpi:
page_dpi = raster_dpi
+31
View File
@@ -24,6 +24,8 @@ from typing import AbstractSet, Optional
import pluggy
from PIL import Image
from ocrmypdf.helpers import Resolution
hookspec = pluggy.HookspecMarker('ocrmypdf')
# pylint: disable=unused-argument
@@ -66,6 +68,35 @@ def validate(pdfinfo: 'PdfInfo', options: Namespace) -> None:
"""
@hookspec(firstresult=True)
def rasterize_pdf_page(
input_file: Path,
output_file: Path,
raster_device: str,
raster_dpi: Resolution,
pageno: int,
page_dpi: Resolution = None,
rotation: int = None,
filter_vector: bool = False,
) -> None:
"""Rasterize one page of a PDF at resolution raster_dpi in canvas units.
The image is sized to match the integer pixels dimensions implied by
raster_dpi even if those numbers are noninteger. The image's DPI will
be overridden with the values in page_dpi.
Args:
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
Returns:
None
"""
@hookspec(firstresult=True)
def filter_ocr_image(page: 'PageContext', image: Image) -> Image:
"""Called to filter the image before it is sent to OCR.
-1
View File
@@ -39,7 +39,6 @@ from ocrmypdf.pdfinfo import Colorspace, Encoding, PdfInfo
check_ocrmypdf = pytest.helpers.check_ocrmypdf
run_ocrmypdf = pytest.helpers.run_ocrmypdf
run_ocrmypdf_api = pytest.helpers.run_ocrmypdf_api
spoof = pytest.helpers.spoof
RENDERERS = ['hocr', 'sandwich']
-1
View File
@@ -47,7 +47,6 @@ pytestmark = pytest.mark.filterwarnings('ignore:.*XMLParser.*:DeprecationWarning
check_ocrmypdf = pytest.helpers.check_ocrmypdf
run_ocrmypdf = pytest.helpers.run_ocrmypdf
spoof = pytest.helpers.spoof
@pytest.mark.parametrize("output_type", ['pdfa', 'pdf'])
+17 -8
View File
@@ -29,19 +29,29 @@ from ocrmypdf.exceptions import BadArgsError, MissingDependencyError
from ocrmypdf.pdfinfo import PdfInfo
def make_opts(input_file='a.pdf', output_file='b.pdf', language='eng', **kwargs):
def make_opts_pm(input_file='a.pdf', output_file='b.pdf', language='eng', **kwargs):
if language is not None:
kwargs['language'] = language
parser = get_parser()
pm = get_plugin_manager(kwargs.get('plugins', []))
pm.hook.add_options(parser=parser)
return create_options(
input_file=input_file, output_file=output_file, parser=parser, **kwargs
return (
create_options(
input_file=input_file, output_file=output_file, parser=parser, **kwargs
),
pm,
)
def make_opts(*args, **kwargs):
opts, _pm = make_opts_pm(*args, **kwargs)
return opts
def test_hocr_notlatin_warning(caplog):
vd.check_options_output(make_opts(language='chi_sim', pdf_renderer='hocr'))
vd.check_options(
*make_opts_pm(language='chi_sim', pdf_renderer='hocr', output_type='pdfa')
)
assert 'PDF renderer is known to cause' in caplog.text
@@ -49,20 +59,20 @@ def test_old_ghostscript(caplog):
with patch('ocrmypdf.exec.ghostscript.version', return_value='9.19'), patch(
'ocrmypdf.exec.tesseract.has_textonly_pdf', return_value=True
):
vd.check_options_output(make_opts(language='chi_sim', output_type='pdfa'))
vd.check_options(*make_opts_pm(language='chi_sim', output_type='pdfa'))
assert 'Ghostscript does not work correctly' in caplog.text
with patch('ocrmypdf.exec.ghostscript.version', return_value='9.18'), patch(
'ocrmypdf.exec.tesseract.has_textonly_pdf', return_value=True
):
with pytest.raises(MissingDependencyError):
vd.check_options_output(make_opts(output_type='pdfa-3'))
vd.check_options(*make_opts_pm(output_type='pdfa-3'))
with patch('ocrmypdf.exec.ghostscript.version', return_value='9.24'), patch(
'ocrmypdf.exec.tesseract.has_textonly_pdf', return_value=True
):
with pytest.raises(MissingDependencyError):
vd.check_dependency_versions(make_opts())
vd.check_options(*make_opts_pm())
def test_old_tesseract_error():
@@ -97,7 +107,6 @@ def test_optimizing(caplog):
def test_user_words(caplog):
with patch('ocrmypdf.exec.tesseract.has_user_words', return_value=False):
opts = make_opts(user_words='foo')
plugin_manager = get_plugin_manager(opts.plugins)