From c87221a4e66140225b4dfba0d2e2b985b703f838 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Sun, 23 May 2021 01:14:15 -0700 Subject: [PATCH 001/106] validation: add proper list of languages supported by hocr Based on Latin-1 support in default PDF fonts. --- src/ocrmypdf/_validation.py | 55 ++++++++++++++++++++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/src/ocrmypdf/_validation.py b/src/ocrmypdf/_validation.py index ab48195e..cd55ef73 100644 --- a/src/ocrmypdf/_validation.py +++ b/src/ocrmypdf/_validation.py @@ -37,7 +37,60 @@ from ocrmypdf.subprocess import check_external_program # ------------- # External dependencies -HOCR_OK_LANGS = frozenset(['eng', 'deu', 'spa', 'ita', 'por']) +# According to Wikipedia these languages are supported in the ISO-8859-1 character +# set, meaning reportlab can generate them and they are compatible with hocr, +# assuming Tesseract has the necessary languages installed. Note that there may +# not be language packs for them. +HOCR_OK_LANGS = frozenset( + [ + # Languages fully covered by Latin-1: + 'afr', # Afrikaans + 'alb', # Albanian + 'ast', # Leonese + 'baq', # Basque + 'bre', # Breton + 'cos', # Corsican + 'eng', # English + 'eus', # Basque + 'fao', # Faoese + 'gla', # Scottish Gaelic + 'glg', # Galician + 'glv', # Manx + 'ice', # Icelandic + 'ind', # Indonesian + 'isl', # Icelandic + 'ita', # Italian + 'ltz', # Luxembourgish + 'mal', # Malay Rumi + 'mga', # Irish + 'nor', # Norwegian + 'oci', # Occitan + 'por', # Portugeuse + 'roh', # Romansh + 'sco', # Scots + 'sma', # Sami + 'spa', # Spanish + 'sqi', # Albanian + 'swa', # Swahili + 'swe', # Swedish + 'tgl', # Tagalog + 'wln', # Walloon + # Languages supported by Latin-1 except for a few rare characters that OCR + # is probably not trained to recognize anyway: + 'cat', # Catalan + 'cym', # Welsh + 'dan', # Danish + 'deu', # German + 'dut', # Dutch + 'est', # Estonian + 'fin', # Finnish + 'fra', # French + 'hun', # Hungarian + 'kur', # Kurdish + 'nld', # Dutch + 'wel', # Welsh + ] +) DEFAULT_LANGUAGE = 'eng' # Enforce English hegemony log = logging.getLogger(__name__) From f3715daf157e5bff3a2fae611f5d01fad9bae68b Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Sun, 23 May 2021 01:15:53 -0700 Subject: [PATCH 002/106] Move HOCR_OK_LANGS to hocrtransform.py --- src/ocrmypdf/_validation.py | 55 +--------------------------------- src/ocrmypdf/hocrtransform.py | 56 +++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 54 deletions(-) diff --git a/src/ocrmypdf/_validation.py b/src/ocrmypdf/_validation.py index cd55ef73..caabc513 100644 --- a/src/ocrmypdf/_validation.py +++ b/src/ocrmypdf/_validation.py @@ -32,65 +32,12 @@ from ocrmypdf.helpers import ( monotonic, safe_symlink, ) +from ocrmypdf.hocrtransform import HOCR_OK_LANGS from ocrmypdf.subprocess import check_external_program # ------------- # External dependencies -# According to Wikipedia these languages are supported in the ISO-8859-1 character -# set, meaning reportlab can generate them and they are compatible with hocr, -# assuming Tesseract has the necessary languages installed. Note that there may -# not be language packs for them. -HOCR_OK_LANGS = frozenset( - [ - # Languages fully covered by Latin-1: - 'afr', # Afrikaans - 'alb', # Albanian - 'ast', # Leonese - 'baq', # Basque - 'bre', # Breton - 'cos', # Corsican - 'eng', # English - 'eus', # Basque - 'fao', # Faoese - 'gla', # Scottish Gaelic - 'glg', # Galician - 'glv', # Manx - 'ice', # Icelandic - 'ind', # Indonesian - 'isl', # Icelandic - 'ita', # Italian - 'ltz', # Luxembourgish - 'mal', # Malay Rumi - 'mga', # Irish - 'nor', # Norwegian - 'oci', # Occitan - 'por', # Portugeuse - 'roh', # Romansh - 'sco', # Scots - 'sma', # Sami - 'spa', # Spanish - 'sqi', # Albanian - 'swa', # Swahili - 'swe', # Swedish - 'tgl', # Tagalog - 'wln', # Walloon - # Languages supported by Latin-1 except for a few rare characters that OCR - # is probably not trained to recognize anyway: - 'cat', # Catalan - 'cym', # Welsh - 'dan', # Danish - 'deu', # German - 'dut', # Dutch - 'est', # Estonian - 'fin', # Finnish - 'fra', # French - 'hun', # Hungarian - 'kur', # Kurdish - 'nld', # Dutch - 'wel', # Welsh - ] -) DEFAULT_LANGUAGE = 'eng' # Enforce English hegemony log = logging.getLogger(__name__) diff --git a/src/ocrmypdf/hocrtransform.py b/src/ocrmypdf/hocrtransform.py index 6c64bcff..799c0099 100755 --- a/src/ocrmypdf/hocrtransform.py +++ b/src/ocrmypdf/hocrtransform.py @@ -41,6 +41,62 @@ from reportlab.lib.colors import black, cyan, magenta, red from reportlab.lib.units import inch from reportlab.pdfgen.canvas import Canvas +# According to Wikipedia these languages are supported in the ISO-8859-1 character +# set, meaning reportlab can generate them and they are compatible with hocr, +# assuming Tesseract has the necessary languages installed. Note that there may +# not be language packs for them. +HOCR_OK_LANGS = frozenset( + [ + # Languages fully covered by Latin-1: + 'afr', # Afrikaans + 'alb', # Albanian + 'ast', # Leonese + 'baq', # Basque + 'bre', # Breton + 'cos', # Corsican + 'eng', # English + 'eus', # Basque + 'fao', # Faoese + 'gla', # Scottish Gaelic + 'glg', # Galician + 'glv', # Manx + 'ice', # Icelandic + 'ind', # Indonesian + 'isl', # Icelandic + 'ita', # Italian + 'ltz', # Luxembourgish + 'mal', # Malay Rumi + 'mga', # Irish + 'nor', # Norwegian + 'oci', # Occitan + 'por', # Portugeuse + 'roh', # Romansh + 'sco', # Scots + 'sma', # Sami + 'spa', # Spanish + 'sqi', # Albanian + 'swa', # Swahili + 'swe', # Swedish + 'tgl', # Tagalog + 'wln', # Walloon + # Languages supported by Latin-1 except for a few rare characters that OCR + # is probably not trained to recognize anyway: + 'cat', # Catalan + 'cym', # Welsh + 'dan', # Danish + 'deu', # German + 'dut', # Dutch + 'est', # Estonian + 'fin', # Finnish + 'fra', # French + 'hun', # Hungarian + 'kur', # Kurdish + 'nld', # Dutch + 'wel', # Welsh + ] +) + + Element = ElementTree.Element From c77cc7c83706b5dc0d4c9c80495affb62028cd67 Mon Sep 17 00:00:00 2001 From: Frank <50119297+FPille@users.noreply.github.com> Date: Wed, 26 May 2021 21:37:58 +0200 Subject: [PATCH 003/106] Update README.md (#785) readme: mention LinuxUser article --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index a0bf0a35..a7a7d913 100644 --- a/README.md +++ b/README.md @@ -115,6 +115,7 @@ In addition to the required Python version (3.6+), OCRmyPDF requires external pr - [heise Open Source, 09/2014: Texterkennung mit OCRmyPDF](https://heise.de/-2356670) - [heise Durchsuchbare PDF-Dokumente mit OCRmyPDF erstellen](https://www.heise.de/ratgeber/Durchsuchbare-PDF-Dokumente-mit-OCRmyPDF-erstellen-4607592.html) - [Excellent Utilities: OCRmyPDF](https://www.linuxlinks.com/excellent-utilities-ocrmypdf-add-ocr-text-layer-scanned-pdfs/) +- [LinuxUser Texterkennung mit OCRmyPDF und Scanbd automatisieren](https://www.linux-community.de/ausgaben/linuxuser/2021/06/texterkennung-mit-ocrmypdf-und-scanbd-automatisieren/) ## Business enquiries From b4f2582766bef0b86d2afcc82941cb0618253d9b Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Thu, 27 May 2021 01:26:25 -0700 Subject: [PATCH 004/106] Show ExitCodeException traceback in verbose mode --- src/ocrmypdf/_sync.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/ocrmypdf/_sync.py b/src/ocrmypdf/_sync.py index d9432cf4..53e5c7cf 100644 --- a/src/ocrmypdf/_sync.py +++ b/src/ocrmypdf/_sync.py @@ -406,7 +406,9 @@ def run_pipeline(options, *, plugin_manager, api=False): log.error("KeyboardInterrupt") return ExitCode.ctrl_c except (ExitCodeException if not api else NeverRaise) as e: - if str(e): + if options.verbose >= 1: + log.exception("ExitCodeException") + elif str(e): log.error("%s: %s", type(e).__name__, str(e)) else: log.error(type(e).__name__) From a964080f77b73e61f744fc9d4c8567f0be1bcbe8 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Thu, 27 May 2021 13:42:05 -0700 Subject: [PATCH 005/106] validation: a word --- src/ocrmypdf/_validation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ocrmypdf/_validation.py b/src/ocrmypdf/_validation.py index caabc513..14768481 100644 --- a/src/ocrmypdf/_validation.py +++ b/src/ocrmypdf/_validation.py @@ -279,7 +279,7 @@ def check_closed_streams(options): # pragma: no cover Attempting to a fork/exec a new Python process when any of std{in,out,err} are closed or not flushable for some reason may raise an exception. Fix this by opening devnull if the handle seems to be closed. Do this - globally to avoid tracking places all places that fork. + globally to avoid tracking all places that fork. Seems to be specific to multiprocessing.Process not all Python process forkers. From 684e5b4944b2ab28050b4abf9c8d401cdc91eef4 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Thu, 27 May 2021 13:42:17 -0700 Subject: [PATCH 006/106] docs: mention ISO Latin-1 --- docs/advanced.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/advanced.rst b/docs/advanced.rst index 09a7376a..fa5489f4 100644 --- a/docs/advanced.rst +++ b/docs/advanced.rst @@ -228,8 +228,8 @@ preprocessing is specified, then the image layer is a new PDF. Unlike ``sandwich`` this renderer is implemented within OCRmyPDF; anyone looking to customize how OCR is presented should look here. A major disadvantage of this renderer is it not capable of correctly handling -text outside the Latin alphabet. Pull requests to improve the situation -are welcome. +text outside the Latin alphabet (specifically, it supports the ISO 8859-1 +character). Pull requests to improve the situation are welcome. Currently, this renderer has the best compatibility with Mozilla's PDF.js viewer. From 701c3b371b3285a34c791adf8f4e5dfeeda54de3 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Thu, 27 May 2021 13:45:41 -0700 Subject: [PATCH 007/106] docs: take a more vender neutral position on commercial OCR --- docs/introduction.rst | 2 +- docs/pdfsecurity.rst | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/introduction.rst b/docs/introduction.rst index 360d57ec..b00b0649 100644 --- a/docs/introduction.rst +++ b/docs/introduction.rst @@ -139,7 +139,7 @@ Limitations OCRmyPDF is limited by the Tesseract OCR engine. As such it experiences these limitations, as do any other programs that rely on Tesseract: -- The OCR is not as accurate as commercial solutions such as Abbyy. +- The OCR is not as accurate as commercial OCR solutions. - It is not capable of recognizing handwriting. - It may find gibberish and report this as OCR output. - If a document contains languages outside of those given in the diff --git a/docs/pdfsecurity.rst b/docs/pdfsecurity.rst index 04ad4e90..4885ab03 100644 --- a/docs/pdfsecurity.rst +++ b/docs/pdfsecurity.rst @@ -128,8 +128,9 @@ Commercial alternatives The author also provides professional services that include OCR and building databases around PDFs, and is happy to provide consultation. -Abbyy Cloud OCR is a viable commercial alternative with a web services -API. +Abbyy Cloud OCR is viable commercial alternative with a web services +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 ========================================================= From 3d6907f7f697d679b5ed9441077d5686c4b5f0ee Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Thu, 27 May 2021 13:53:33 -0700 Subject: [PATCH 008/106] v12.0.3 release notes --- docs/release_notes.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/release_notes.rst b/docs/release_notes.rst index 2d171340..701e1e4d 100644 --- a/docs/release_notes.rst +++ b/docs/release_notes.rst @@ -12,6 +12,14 @@ may be unreliable. Use the API to depend on precise behavior. The public API may be useful in scripts that launch OCRmyPDF processes or that wish to use some of its features for working with PDFs. +v12.0.3 +======= + +- Expand the list of languages supported by the hocr PDF renderer. + Several languages were previously considered not supported, particularly those + non-European languages that use the Latin alphabet. +- Fixed a case where the exception stack trace was suppressed in verbose mode. +- Improved documentation around commercial OCR. v12.0.2 ======= From db388165a983576455ef95f4b6b9182b68031313 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 8 Jun 2021 13:27:17 -0700 Subject: [PATCH 009/106] Bump pillow from 8.1.2 to 8.2.0 in /requirements (#793) Bumps [pillow](https://github.com/python-pillow/Pillow) from 8.1.2 to 8.2.0. - [Release notes](https://github.com/python-pillow/Pillow/releases) - [Changelog](https://github.com/python-pillow/Pillow/blob/master/CHANGES.rst) - [Commits](https://github.com/python-pillow/Pillow/compare/8.1.2...8.2.0) --- updated-dependencies: - dependency-name: pillow dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements/main.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/main.txt b/requirements/main.txt index a49cf9fa..5f1b1f2e 100644 --- a/requirements/main.txt +++ b/requirements/main.txt @@ -7,6 +7,6 @@ img2pdf == 0.4.0 pdfminer.six == 20201018 pikepdf == 2.10.0 pluggy == 0.13.1 -Pillow == 8.1.2 +Pillow == 8.2.0 reportlab == 3.5.66 tqdm == 4.59.0 From 4030258bbcc05a5c4fdee10d3d4d00b4e4da7911 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Wed, 9 Jun 2021 00:47:39 -0700 Subject: [PATCH 010/106] Modernize build system to use setup.cfg For now, keep but deprecate the requirements/*.txt files. --- .docker/Dockerfile | 7 +-- .github/workflows/build.yml | 6 +- .gitignore | 1 + docs/batch.rst | 2 +- docs/installation.rst | 3 +- pyproject.toml | 5 +- requirements/main.txt | 4 +- requirements/test.txt | 1 + requirements/watcher.txt | 1 + requirements/webservice.txt | 1 + setup.cfg | 108 +++++++++++++++++++++++++++++++++--- setup.py | 74 +----------------------- src/ocrmypdf/RELEASE.md | 2 +- 13 files changed, 118 insertions(+), 97 deletions(-) diff --git a/.docker/Dockerfile b/.docker/Dockerfile index 4700004e..c55fcd7c 100644 --- a/.docker/Dockerfile +++ b/.docker/Dockerfile @@ -37,12 +37,7 @@ COPY . /app WORKDIR /app -RUN pip3 install --no-cache-dir \ - -r requirements/main.txt \ - -r requirements/webservice.txt \ - -r requirements/test.txt \ - -r requirements/watcher.txt \ - . +RUN pip3 install --no-cache-dir .[test,webservice,watcher] FROM base diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index cad70f3f..cba6c85e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -67,7 +67,7 @@ jobs: - name: Install Python packages run: | - python -m pip install -r requirements/main.txt -r requirements/test.txt . + python -m pip install .[test] - name: Report versions run: | @@ -124,7 +124,7 @@ jobs: - name: Install Python packages run: | python -m pip install --upgrade pip - python -m pip install -r requirements/main.txt -r requirements/test.txt . + python -m pip install .[test] - name: Report versions run: | @@ -174,7 +174,7 @@ jobs: - name: Install Python packages run: | python -m pip install --upgrade pip - python -m pip install -r requirements/main.txt -r requirements/test.txt . + python -m pip install .[test] - name: Test run: | diff --git a/.gitignore b/.gitignore index 60de406b..39ace52f 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ !.pre-commit-config.yaml !.readthedocs.yml !.github/ +!.docker/ # Dev scratch *.ipynb diff --git a/docs/batch.rst b/docs/batch.rst index e8e97374..e66388df 100644 --- a/docs/batch.rst +++ b/docs/batch.rst @@ -111,7 +111,7 @@ Users may need to customize the script to meet their requirements. .. code-block:: bash - pip3 install -r requirements/watcher.txt + pip3 install ocrmypdf[watcher] env OCR_INPUT_DIRECTORY=/mnt/input-pdfs \ OCR_OUTPUT_DIRECTORY=/mnt/output-pdfs \ diff --git a/docs/installation.rst b/docs/installation.rst index 6d096996..5b42f11f 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -812,8 +812,7 @@ To install all of the development and test requirements: python3 -m venv source venv/bin/activate cd OCRmyPDF - pip install -e . - pip install -r requirements/dev.txt -r requirements/test.txt + pip install -e .[test] To add JBIG2 encoding, see :ref:`jbig2`. diff --git a/pyproject.toml b/pyproject.toml index a28f55c0..8791923e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,11 +3,14 @@ requires = [ "setuptools >= 30.3.0", "wheel", "cffi", - "setuptools_scm", + "setuptools_scm[toml] >= 3.4", "setuptools_scm_git_archive" ] build-backend = "setuptools.build_meta" +[tool.setuptools_scm] +version_scheme = "post-release" + [tool.black] line-length = 88 target-version = ["py36", "py37", "py38"] diff --git a/requirements/main.txt b/requirements/main.txt index a49cf9fa..9d347f6f 100644 --- a/requirements/main.txt +++ b/requirements/main.txt @@ -1,6 +1,4 @@ -# requirements.txt can be used to replicate the developer's build environment -# setup.py lists a separate set of requirements that are looser to simplify -# installation +# Deprecated and not maintained; use "pip install ocrmypdf" instead cffi == 1.14.5 coloredlogs == 15.0 # technically optional img2pdf == 0.4.0 diff --git a/requirements/test.txt b/requirements/test.txt index 72225e2d..db7cb14e 100644 --- a/requirements/test.txt +++ b/requirements/test.txt @@ -1,3 +1,4 @@ +# Deprecated and not maintained; use "pip install ocrmypdf[test]" instead pytest >= 6.0.0 pytest-xdist >= 2.2.0 pytest-cov >= 2.11.1 diff --git a/requirements/watcher.txt b/requirements/watcher.txt index 660d7af4..b4a2b744 100644 --- a/requirements/watcher.txt +++ b/requirements/watcher.txt @@ -1 +1,2 @@ +# Deprecated and not maintained; use "pip install ocrmypdf[watcher]" instead watchdog == 1.0.2 diff --git a/requirements/webservice.txt b/requirements/webservice.txt index f6e3c4e6..7f8e08de 100644 --- a/requirements/webservice.txt +++ b/requirements/webservice.txt @@ -1 +1,2 @@ +# Deprecated and not maintained; use "pip install ocrmypdf[webservice]" instead Flask >= 1, < 2 diff --git a/setup.cfg b/setup.cfg index 36545d66..98bce9fa 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,8 +1,101 @@ +[metadata] +name = ocrmypdf +description = OCRmyPDF adds an OCR text layer to scanned PDF files, allowing them to be searched +long_description = file: README.md +long_description_content_type = text/markdown; charset=UTF-8 +url = https://github.com/jbarlow83/OCRmyPDF +author = James R. Barlow +author_email = james@purplerock.ca +license_files = + LICENSE +keywords = + PDF + OCR + optical character recognition + PDF/A + scanning +classifiers = + Programming Language :: Python :: 3.6 + Programming Language :: Python :: 3.7 + Programming Language :: Python :: 3.8 + Programming Language :: Python :: 3.9 + Development Status :: 5 - Production/Stable + Environment :: Console + Intended Audience :: End Users/Desktop + Intended Audience :: Science/Research + Intended Audience :: System Administrators + License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0) + Operating System :: MacOS :: MacOS X + Operating System :: Microsoft :: Windows :: Windows 10 + Operating System :: POSIX + Operating System :: POSIX :: BSD + Operating System :: POSIX :: Linux + Topic :: Scientific/Engineering :: Image Recognition + Topic :: Text Processing :: Indexing + Topic :: Text Processing :: Linguistic +project_urls = + Documentation = https://ocrmypdf.readthedocs.io/ + Source = https://github.com/jbarlow83/ocrmypdf + Tracker = https://github.com/jbarlow83/ocrmypdf/issues + +[options] +zip_safe = False +packages = find: +package_dir = + =src +platforms = any +include_package_data=True +install_requires = + cffi >= 1.9.1 # must be a setup and install requirement + coloredlogs >= 14.0 # strictly optional + img2pdf >= 0.3.0, < 0.5 # pure Python, so track HEAD closely + pdfminer.six >= 20191110, != 20200720, <= 20201018 + pikepdf >= 2.10.0 + Pillow >= 8.2.0 + pluggy >= 0.13.0, < 1.0 + reportlab >= 3.5.66 + setuptools + tqdm >= 4 +python_requires = >= 3.6 +setup_requires = # can be removed whenever we can drop pip 9 support + cffi >= 1.9.1 # to build the leptonica module + setuptools_scm # so that version will work + setuptools_scm_git_archive # enable version from github tarballs + +[options.package_data] +ocrmypdf = + data/sRGB.icc + py.typed + +[options.packages.find] +where = src + +[options.extras_require] +test = + pytest >= 6.0.0 + pytest-xdist >= 2.2.0 + pytest-cov >= 2.11.1 + python-xmp-toolkit == 2.0.1 # also requires apt-get install libexempi3 + # or brew install exempi +docs = + sphinx + sphinx_rtd_theme +extended_test = + PyMuPDF == 1.13.4 +watcher = + watchdog >= 1.0.2, < 2 +webservice = + Flask >= 1, < 2 + +[options.entry_points] +console_scripts = + ocrmypdf = ocrmypdf.__main__:run + [bdist_wheel] python-tag = py36 [aliases] -test=pytest +test = pytest [check-manifest] ignore = @@ -19,17 +112,14 @@ addopts = -n auto [isort] -multi_line_output=3 -include_trailing_comma=True -force_grid_wrap=0 -use_parentheses=True -line_length=88 +multi_line_output = 3 +include_trailing_comma = True +force_grid_wrap = 0 +use_parentheses = True +line_length = 88 known_first_party = ocrmypdf known_third_party = PIL,_cffi_backend,cffi,flask,img2pdf,pdfminer,pikepdf,pkg_resources,pluggy,pytest,reportlab,setuptools,sphinx_rtd_theme,tqdm,watchdog,werkzeug -[metadata] -license_file = LICENSE - [coverage:paths] source = src/ocrmypdf diff --git a/setup.py b/setup.py index e2342651..86f1ae36 100644 --- a/setup.py +++ b/setup.py @@ -1,62 +1,16 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -# © 2015 James R. Barlow: github.com/jbarlow83 +# © 2021 James R. Barlow: github.com/jbarlow83 # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. -from __future__ import print_function, unicode_literals - -import sys - -from setuptools import find_packages, setup - -if sys.version_info < (3, 6): - print("Python 3.6 or newer is required", file=sys.stderr) - sys.exit(1) - -tests_require = open('requirements/test.txt', encoding='utf-8').read().splitlines() - - -def readme(): - with open('README.md', encoding='utf-8') as f: - return f.read() - +from setuptools import setup +# Minimal setup to support older setuptools/setuptools_scm setup( - name='ocrmypdf', - description='OCRmyPDF adds an OCR text layer to scanned PDF files, allowing them to be searched', - long_description=readme(), - long_description_content_type='text/markdown', - url='https://github.com/jbarlow83/OCRmyPDF', - author='James R. Barlow', - author_email='james@purplerock.ca', - packages=find_packages('src', exclude=["tests", "tests.*"]), - package_dir={'': 'src'}, - keywords=['PDF', 'OCR', 'optical character recognition', 'PDF/A', 'scanning'], - classifiers=[ - "Programming Language :: Python :: 3.6", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Development Status :: 5 - Production/Stable", - "Environment :: Console", - "Intended Audience :: End Users/Desktop", - "Intended Audience :: Science/Research", - "Intended Audience :: System Administrators", - "License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)", - "Operating System :: MacOS :: MacOS X", - "Operating System :: Microsoft :: Windows :: Windows 10", - "Operating System :: POSIX", - "Operating System :: POSIX :: BSD", - "Operating System :: POSIX :: Linux", - "Topic :: Scientific/Engineering :: Image Recognition", - "Topic :: Text Processing :: Indexing", - "Topic :: Text Processing :: Linguistic", - ], - python_requires=' >= 3.6', setup_requires=[ # can be removed whenever we can drop pip 9 support 'cffi >= 1.9.1', # to build the leptonica module 'setuptools_scm', # so that version will work @@ -64,26 +18,4 @@ setup( ], use_scm_version={'version_scheme': 'post-release'}, cffi_modules=['src/ocrmypdf/lib/compile_leptonica.py:ffibuilder'], - install_requires=[ - 'cffi >= 1.9.1', # must be a setup and install requirement - 'coloredlogs >= 14.0', # strictly optional - 'img2pdf >= 0.3.0, < 0.5', # pure Python, so track HEAD closely - 'pdfminer.six >= 20191110, != 20200720, <= 20201018', - "pikepdf >= 2.10.0", - 'Pillow >= 8.1.2', - 'pluggy >= 0.13.0, < 1.0', - 'reportlab >= 3.5.66', - 'setuptools', - 'tqdm >= 4', - ], - tests_require=tests_require, - entry_points={'console_scripts': ['ocrmypdf = ocrmypdf.__main__:run']}, - package_data={'ocrmypdf': ['data/sRGB.icc', 'py.typed']}, - include_package_data=True, - zip_safe=False, - project_urls={ - 'Documentation': 'https://ocrmypdf.readthedocs.io/', - 'Source': 'https://github.com/jbarlow83/ocrmypdf', - 'Tracker': 'https://github.com/jbarlow83/ocrmypdf/issues', - }, ) diff --git a/src/ocrmypdf/RELEASE.md b/src/ocrmypdf/RELEASE.md index 41a40e97..2cb89b78 100644 --- a/src/ocrmypdf/RELEASE.md +++ b/src/ocrmypdf/RELEASE.md @@ -28,7 +28,7 @@ - Search for deprecation: search all files for deprec*, etc. -- Check requirements/* +- Check requirements in setup.cfg - Delete `tests/cache`, do `pytest --runslow`, and update cache. From d293e05946ce4c421196b7aee940ea964fa8ca55 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Thu, 10 Jun 2021 00:21:11 -0700 Subject: [PATCH 011/106] Adjust readthedocs to not use requirements files Remove .yml since RTD considers it deprecated. --- .gitignore | 2 +- .readthedocs.yaml | 22 ++++++++++++++++++++++ .readthedocs.yml | 10 ---------- 3 files changed, 23 insertions(+), 11 deletions(-) create mode 100644 .readthedocs.yaml delete mode 100644 .readthedocs.yml diff --git a/.gitignore b/.gitignore index 39ace52f..1481bb33 100644 --- a/.gitignore +++ b/.gitignore @@ -6,7 +6,7 @@ !.gitattributes !.gitignore !.pre-commit-config.yaml -!.readthedocs.yml +!.readthedocs.yaml !.github/ !.docker/ diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 00000000..3c67df11 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,22 @@ +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +# Required +version: 2 + +# Build documentation in the docs/ directory with Sphinx +sphinx: + configuration: docs/conf.py + +# Optionally build your docs in additional formats such as PDF +formats: + - pdf + +# Optionally set the version of Python and requirements required to build your docs +python: + version: 3.7 + install: + - method: pip + path: . + extra_requirements: + - docs diff --git a/.readthedocs.yml b/.readthedocs.yml deleted file mode 100644 index de4ec366..00000000 --- a/.readthedocs.yml +++ /dev/null @@ -1,10 +0,0 @@ -build: - image: latest - -python: - version: 3.6 - -formats: - - pdf - -requirements_file: requirements/main.txt From dc118028091d12d2b09d5fa5ea01c20ea3b1b888 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Sun, 13 Jun 2021 02:20:25 -0700 Subject: [PATCH 012/106] github templates: ask for more details on feature reqs --- .github/ISSUE_TEMPLATE/3-feature_request.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/3-feature_request.md b/.github/ISSUE_TEMPLATE/3-feature_request.md index bbcbbe7d..fc3ba503 100644 --- a/.github/ISSUE_TEMPLATE/3-feature_request.md +++ b/.github/ISSUE_TEMPLATE/3-feature_request.md @@ -14,7 +14,14 @@ A clear and concise description of what the problem is. Ex. I'm always frustrate A clear and concise description of what you want to happen. **Describe alternatives you've considered** -A clear and concise description of any alternative solutions or features you've considered. +A clear and concise description of any alternative solutions or features you've considered. Please include the versions of OCRmyPDF and other supporting programs (Tesseract OCR, Ghostscript) - maybe an alternative already exists in a newer version. + +**Example file** +If your issue concerns how OCRmyPDF processes certain files, and please provide an example file that helps illustrate how OCRmyPDF's output could be improve. + +Please provide an input file with no personal or confidential information. At your option you may [GPG-encrypt the file](https://github.com/jbarlow83/OCRmyPDF/wiki) for OCRmyPDF's author only. + +Links to files hosted elsewhere are perfectly acceptable. You could also look in ``tests/resources`` and see if any of those files reproduce your issue. **Additional context** Add any other context or screenshots about the feature request here. From f10a0f77071ed71cbf6b1a8332180db7af54331c Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Sun, 13 Jun 2021 02:20:36 -0700 Subject: [PATCH 013/106] readme: we Docker arm now --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a7a7d913..713ec619 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,7 @@ I searched the web for a free command line tool to OCR PDF files: I found many, ## Installation -Linux, Windows, macOS and FreeBSD are supported. Docker images are also available. +Linux, Windows, macOS and FreeBSD are supported. Docker images are also available, for both x64 and ARM. | Operating system | Install command | | ----------------------------- | ------------------------------| From 0a1216bf146783d0ae75aa5c92da98752e52566d Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Sun, 13 Jun 2021 02:26:10 -0700 Subject: [PATCH 014/106] v12.1.0 release notes --- docs/release_notes.rst | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/docs/release_notes.rst b/docs/release_notes.rst index 701e1e4d..eb0b4339 100644 --- a/docs/release_notes.rst +++ b/docs/release_notes.rst @@ -12,6 +12,17 @@ may be unreliable. Use the API to depend on precise behavior. The public API may be useful in scripts that launch OCRmyPDF processes or that wish to use some of its features for working with PDFs. +v12.1.0 +======= + +- For security reasons we now require Pillow >= 8.2.x. (Older versions will continue + to work if upgrading is not an option.) +- The build system was reorganized to rely on ``setup.cfg`` instead of ``setup.py``. + All changes should work with previously supported versions of setuptools. +- The files in ``requirements/*`` are now considered deprecated but will be retained for v12. + Instead use ``pip install ocrmypdf[test]`` instead of ``requirements/test.txt``, etc. + These files will be removed in v13. + v12.0.3 ======= @@ -27,7 +38,7 @@ v12.0.2 - Fix exception thrown when using ``--remove-background`` on files containing small images (#769). - Improve documentation for description of adding language packs to the Docker image - and corrected name of French language pack. + and corrected name of French language pack. v12.0.1 ======= From 7965b1f9300480b29213e5488768dfdb994108e4 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Mon, 14 Jun 2021 01:08:07 -0700 Subject: [PATCH 015/106] docs: don't suggest unmaintained alternatives, update on GS --- docs/introduction.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/introduction.rst b/docs/introduction.rst index b00b0649..6753735d 100644 --- a/docs/introduction.rst +++ b/docs/introduction.rst @@ -207,8 +207,9 @@ consider one of these similar open source programs: - pdf2pdfocr - pdfsandwich -- pypdfocr -- pdfbeads + +Ghostscript recently added three "pdfocr" output devices. They work by +rasterizing all content and converting all pages to a single colour space. Web front-ends ============== From 5d08303805668865d16df82772a1629b4c8fd066 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Mon, 14 Jun 2021 02:15:52 -0700 Subject: [PATCH 016/106] docs: plugins - show setup.cfg example --- docs/plugins.rst | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/docs/plugins.rst b/docs/plugins.rst index 952af3fd..c8d3e5ae 100644 --- a/docs/plugins.rst +++ b/docs/plugins.rst @@ -61,9 +61,10 @@ similar to ``pytest`` packages such as ``pytest-cov`` (the package) and .. note:: - We strongly recommend plugin authors name their plugins with the prefix + We recommend plugin authors name their plugins with the prefix ``ocrmypdf-`` (for the package name on PyPI) and ``ocrmypdf_`` (for the - module), just like pytest plugins. + module), just like pytest plugins. At the same time, please make it clear + that your package is not official. Setuptools plugins ================== @@ -86,6 +87,13 @@ named ``ocrmypdf-exampleplugin``: entry_points={"ocrmypdf": ["exampleplugin = exampleplugin.pluginmodule"]}, ) +.. code-block:: ini + + # equivalent setup.cfg + [options.entry_points] + ocrmypdf = + exampleplugin = exampleplugin.pluginmodule + Plugin requirements =================== From 5f01c5e330ce8e0249ff167ae4f79fbb648b05c6 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Wed, 16 Jun 2021 00:08:56 -0700 Subject: [PATCH 017/106] Fix another species of Tesseract version number breaking regex Fixes #795 --- src/ocrmypdf/_exec/tesseract.py | 3 ++- tests/test_validation.py | 7 +++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/ocrmypdf/_exec/tesseract.py b/src/ocrmypdf/_exec/tesseract.py index 119771ec..ac7b4fab 100644 --- a/src/ocrmypdf/_exec/tesseract.py +++ b/src/ocrmypdf/_exec/tesseract.py @@ -56,11 +56,12 @@ class TesseractLoggerAdapter(logging.LoggerAdapter): class TesseractVersion(StrictVersion): + version_re = re.compile( r''' ^(\d+) \. (\d+) (\. (\d+))? # groups: 1/major, 2/minor, 3/[skip], 4/patch [-]? # optional hyphen separator - (?:(alpha|beta|rc|dev)[.\-\ ]?(\d+)?)? # 5/prerelease, 6/prerelease_num + (?:(alpha|beta|rc|dev)?[.\-\ ]?(\d+)?)? # 5/prerelease, 6/prerelease_num (?:-(\d+)-g[0-9a-f]+)? # untagged git version $ ''', diff --git a/tests/test_validation.py b/tests/test_validation.py index fd4d6fc2..a027e17a 100644 --- a/tests/test_validation.py +++ b/tests/test_validation.py @@ -237,6 +237,13 @@ def test_version_comparison(): need_version='4.0.0', version_parser=TesseractVersion, ) + vd.check_external_program( + program="tesseract", + package="tesseract", + version_checker=lambda: 'v4.0.0.20181030', # Some Windows builds use this format + need_version='4.0.0', + version_parser=TesseractVersion, + ) vd.check_external_program( program="tesseract", package="tesseract", From 6b9b5cc5d5a11140779f97aee649d838bad6a0bf Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Wed, 16 Jun 2021 00:10:10 -0700 Subject: [PATCH 018/106] setup.py shouldn't have a shebang --- setup.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/setup.py b/setup.py index 86f1ae36..a3dd6458 100644 --- a/setup.py +++ b/setup.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- # © 2021 James R. Barlow: github.com/jbarlow83 # # This Source Code Form is subject to the terms of the Mozilla Public From 38280e77f841160e139ec2bf265a820a9fe491e4 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Wed, 16 Jun 2021 00:39:40 -0700 Subject: [PATCH 019/106] Use sphinx-issues to "refactor" release notes --- docs/conf.py | 4 +- docs/release_notes.rst | 255 +++++++++++++++++++---------------------- setup.cfg | 5 +- 3 files changed, 124 insertions(+), 140 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 4a6dc0ac..2df277f4 100755 --- a/docs/conf.py +++ b/docs/conf.py @@ -31,9 +31,11 @@ # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. -extensions = ['sphinx.ext.napoleon'] +extensions = ['sphinx.ext.napoleon', 'sphinx_issues'] +# Extension settings napoleon_use_rtype = False +issues_github_path = "jbarlow83/OCRmyPDF" # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] diff --git a/docs/release_notes.rst b/docs/release_notes.rst index eb0b4339..6988e2a1 100644 --- a/docs/release_notes.rst +++ b/docs/release_notes.rst @@ -12,6 +12,13 @@ may be unreliable. Use the API to depend on precise behavior. The public API may be useful in scripts that launch OCRmyPDF processes or that wish to use some of its features for working with PDFs. + +v12.1.1 +======= + +- Fixed invalid Tesseract version number on Windows (:issue:`795`). +- Documentation tweaks. + v12.1.0 ======= @@ -35,15 +42,15 @@ v12.0.3 v12.0.2 ======= -- Fix exception thrown when using ``--remove-background`` on files containing small - images (#769). +- Fixed exception thrown when using ``--remove-background`` on files containing small + images (:issue:`769`). - Improve documentation for description of adding language packs to the Docker image and corrected name of French language pack. v12.0.1 ======= -- Fix "invalid version number" for untagged tesseract versions (#770). +- Fixed "invalid version number" for untagged tesseract versions (:issue:`770`). v12.0.0 ======= @@ -103,7 +110,7 @@ v12.0.0 which ships with Ubuntu 18.04). - OCRmyPDF can now parse all of Tesseract version numbers, since several schemes have been in use. -- Fixed an issue with parsing PDFs that contain images drawn at a scale of 0. (#761) +- Fixed an issue with parsing PDFs that contain images drawn at a scale of 0. (:issue:`761`) - Removed a frequently repeated message about disabling mmap. v11.7.3 @@ -133,7 +140,7 @@ v11.7.0 ======= - We now support using ``--sidecar`` in conjunction with ``--pages``; these arguments - used to be mutually exclusive. (#735) + used to be mutually exclusive. (:issue:`735`) - Fixed a possible issue with PDF/A-1b generation. Acrobat complained that our PDFs use object streams. More robust PDF/A validators like veraPDF don't consider this a problem, but we'll honor Acrobat's objection from here on. This may increase file @@ -143,13 +150,13 @@ v11.6.2 ======= - Fixed a regression where the wrong page orientation would be produced when using - arguments such as ``--deskew --rotate-pages`` (#730). + arguments such as ``--deskew --rotate-pages`` (:issue:`730`). v11.6.1 ======= - Fixed an issue with attempting optimize unusually narrow-width images by excluding - these images from optimization (#732). + these images from optimization (:issue:`732`). - Remove an obsolete compatibility shim for a version of pikepdf that is no longer supported. @@ -187,7 +194,7 @@ v11.4.5 v11.4.4 ======= -- Fixed ``AttributeError: 'NoneType' object has no attribute 'userunit'``, issue #700, +- Fixed ``AttributeError: 'NoneType' object has no attribute 'userunit'`` (:issue:`700`), related to OCRmyPDF not properly forwarded an error message from pdfminer.six. - Adjusted typing of some arguments. - ``ocrmypdf.ocr`` now takes a ``threading.Lock`` for reasons outlined in the @@ -230,7 +237,7 @@ v11.4.0 ``com.github.ocrmypdf`` to ``ocrmypdf.io``. Scripts that chose to depend on this prefix may need to be adjusted. (This has always been an implementation detail so is not considered part of the semantic versioning "contract".) -- Fixed issue #692, where a particular file with malformed fonts would flood an +- Fixed :issue:`692`, where a particular file with malformed fonts would flood an internal message cue by generating so many debug messages. - Fixed an exception on processing hOCR files with no page record. Tesseract is not known to generate such files. @@ -249,7 +256,7 @@ v11.3.3 ======= - If unpaper outputs non-UTF-8 data, quietly fix this rather than choke on the - conversion. (Possibly addresses #671.) + conversion. (Possibly addresses :issue:`671`.) v11.3.2 ======= @@ -262,7 +269,7 @@ v11.3.2 as optimization candidates. - On some systems, unpaper seems to be unable to process the PNGs we offer it as input. We now convert the input to PNM format, which unpaper always accepts. - Fixes #665 and #667. + Fixes :issue:`665` and :issue:`667`. - DPI sent to unpaper is now rounded to a more reasonable number of decimal digits. - Debug and error messages from unpaper were being suppressed. - Some documentation tweaks. @@ -271,7 +278,7 @@ v11.3.1 ======= - Declare support for new versions: pdfminer.six 20201018 and pikepdf 2.x -- Fix warning related to ``--pdfa-image-compression`` that appears at the wrong +- Fixed warning related to ``--pdfa-image-compression`` that appears at the wrong time. v11.3.0 @@ -291,7 +298,7 @@ v11.3.0 macOS and Windows only where the parent process is not forked. - Fixed the hookspec of rasterize_pdf_page to remove default parameters that were not handled in an expected way by pluggy. -- Fixed another issue with automatic page rotation (#658) due to the issue above. +- Fixed another issue with automatic page rotation (:issue:`658`) due to the issue above. v11.2.1 ======= @@ -313,7 +320,7 @@ v11.1.2 - Fixed hOCR renderer writing the text in roughly reverse order. This should not affect reasonably smart PDF readers that properly locate the position of all text, but may confuse those that rely on the order of objects in the content - stream. (#642) + stream. (:issue:`642`) v11.1.1 ======= @@ -326,9 +333,9 @@ v11.1.1 v11.1.0 ======= -- Fixed page rotation issues: #634, #589. +- Fixed page rotation issues: :issue:`634,589`. - Fixed some cases where optimization created an invalid image such as a - 1-bit "RGB" image: #629, #620. + 1-bit "RGB" image: :issue:`629,620`. - Page numbers are now displayed in debug logs when pages are being grafted. - ocrmypdf.optimize.rewrite_png and ocrmypdf.optimize.rewrite_png_as_g4 were marked deprecated. Strictly speaking these should have been internal APIs, @@ -341,7 +348,7 @@ v11.1.0 v11.0.2 ======= -- Fixed issue #612, TypeError exception. Fixed by eliminating unnecessary repair of +- Fixed :issue:`612`, TypeError exception. Fixed by eliminating unnecessary repair of input PDF metadata in memory. v11.0.1 @@ -358,7 +365,7 @@ v11.0.0 - Project license changed to Mozilla Public License 2.0. Some miscellaneous code is now under MIT license and non-code content/media remains under CC-BY-SA 4.0. License changed with approval of all people who were found - to have contributed to GPLv3 licensed sections of the project. (#600) + to have contributed to GPLv3 licensed sections of the project. (:issue:`600`) - Because the license changed, this is being treated as a major version number change; however, there are no known breaking changes in functional behavior or API compared to v10.x. @@ -367,7 +374,7 @@ v10.3.3 ======= - Fixed a "KeyError: 'dpi'" error message when using ``--threshold`` on an image. - (#607) + (:issue:`607`) v10.3.2 ======= @@ -410,16 +417,16 @@ v10.2.0 ======= - Update Docker image to use Ubuntu 20.04. -- Fixed issue PDF/A acquires title "Untitled" after conversion. (#582) +- Fixed issue PDF/A acquires title "Untitled" after conversion. (:issue:`582`) - Fixed a problem where, when using ``--pdf-renderer hocr``, some text would be missing from the output when using a more recent version of Tesseract. Tesseract began adding more detailed markup about the semantics of text that our HOCR transform did not recognize, so it ignored them. This option is not the default. If necessary ``--redo-ocr`` also redoing OCR to fix such issues. - Fixed an error in Python 3.9 beta, due to removal of deprecated - ``Element.getchildren()``. (#584) + ``Element.getchildren()``. (:issue:`584`) - Implemented support using the API with ``BytesIO`` and other file stream objects. - (#545) + (:issue:`545`) v10.1.1 ======= @@ -512,7 +519,7 @@ v9.8.0 - Fixed issue where only the first PNG (FlateDecode) image in a file would be considered for optimization. File sizes should be improved from here on. -- Fixed a startup crash when the chosen language was Japanese (#543). +- Fixed a startup crash when the chosen language was Japanese (:issue:`543`). - Added options to configure polling and log level to watcher.py. v9.7.2 @@ -559,11 +566,11 @@ v9.6.1 they can be copied out as whole files, and to ensure syntax checking is maintained. -- Fixed an error that caused bash completions to fail on macOS. (#502, #504; +- Fixed an error that caused bash completions to fail on macOS. (:issue:`502,504`; @AlexanderWillner) - Fixed a rare case where OCRmyPDF threw an exception while processing a PDF with the wrong object type in its ``/Trailer /Info``. The error is now logged - and incorrect object is ignored. (#497) + and incorrect object is ignored. (:issue:`497`) - Removed potentially non-free file ``enron1.pdf`` and simplified the test that used it. - Removed potentially non-free file ``misc/media/logo.afdesign``. @@ -765,7 +772,7 @@ v8.3.1 ====== - Fixed an issue where PDFs with malformed metadata would be rendered as - blank pages. `#398 `_. + blank pages. :issue:`398`. v8.3.0 ====== @@ -848,7 +855,7 @@ v8.2.0 designed. However, quality would not be impacted. Lossless JBIG2 was entirely unaffected. - Updated dependencies, including pikepdf to 1.1.0. This fixes - `#358 `__. + :issue:`358`. - The install-time version checks for certain external programs have been removed from setup.py. These tests are now performed at run-time. @@ -869,7 +876,7 @@ v8.1.0 (specifically, those with invalid destination objects) - Fixed an issue when using ``--tesseract-timeout`` and image processing features on a file with more than 100 pages. - `#347 `__ + :issue:`347` - OCRmyPDF now always calls ``os.nice(5)`` to signal to operating systems that it is a background process. @@ -877,7 +884,7 @@ v8.0.1 ====== - Fixed an exception when parsing PDFs that are missing a required - field. `#325 `__ + field. :issue:`325` - pikepdf 1.0.5 is now required, to address some other PDF parsing issues. @@ -898,7 +905,7 @@ older versions of certain dependencies. **Other changes** - Fixed an unhandled exception when attempting to mask barcodes. - `#322 `__ + :issue:`322` - It is now possible to use ocrmypdf without pdfminer.six, to support distributions that do not have it or cannot currently use it (e.g. Homebrew). Downstream maintainers should include pdfminer.six if @@ -925,13 +932,13 @@ v7.4.0 - chardet >= 3.0.4 is temporarily listed as required. pdfminer.six depends on it, but the most recent release does not specify this requirement. - (`#326 `__) + (:issue:`326`) - python-xmp-toolkit and libexempi are no longer required. - A new Docker image is now being provided for users who wish to access OCRmyPDF over a simple HTTP interface, instead of the command line. - Increase tolerance of PDFs that overflow or underflow the PDF graphics stack. - (`#325 `__) + (:issue:`325`) v7.3.1 ====== @@ -1005,7 +1012,7 @@ v7.3.0 v7.2.1 ====== -- Fix compatibility with an API change in pikepdf 0.3.5. +- Fixed compatibility with an API change in pikepdf 0.3.5. - A kludge to support Leptonica versions older than 1.72 in the test suite was dropped. Older versions of Leptonica are likely still compatible. The only impact is that a portion of the test suite will @@ -1042,7 +1049,7 @@ Users who did not install an optional JBIG2 encoder are unaffected. will now attempt to further optimize that image as CCITT or JBIG2, instead of keeping it in the "flate" encoding which is not efficient for 1 bpp images. - (`#297 `__) + (:issue:`297`) - Images in PDFs that are used as soft masks (i.e. transparency masks or alpha channels) are now excluded from optimization. - Fixed handling of Tesseract 4.0-rc1 which now accepts invalid @@ -1054,15 +1061,14 @@ v7.1.0 - Improve the performance of initial text extraction, which is done to determine if a file contains existing text of some kind or not. On large files, this initial processing is now about 20x times faster. - (`#299 `__) + (:issue:`299`) - pikepdf 0.3.3 is now required. -- Fixed issue - `#231 `__, a +- Fixed :issue:`231`, a problem with JPEG2000 images where image metadata was only available inside the JPEG2000 file. - Fixed some additional Ghostscript 9.25 compatibility issues. - Improved handling of KeyboardInterrupt error messages. - (`#301 `__) + (:issue:`301`) - README.md is now served in GitHub markdown instead of reStructuredText. @@ -1094,35 +1100,34 @@ v7.0.5 v7.0.4 ====== -- Fix exception thrown when trying to optimize a certain type of PNG +- Fixed exception thrown when trying to optimize a certain type of PNG embedded in a PDF with the ``-O2`` - Update to pikepdf 0.3.2, to gain support for optimizing some additional image types that were previously excluded from optimization (CMYK and grayscale). Fixes - `#285 `__. + :issue:`285`. v7.0.3 ====== -- Fix issue - `#284 `__, an error +- Fixed :issue:`284`, an error when parsing inline images that have are also image masks, by upgrading pikepdf to 0.3.1 v7.0.2 ====== -- Fix a regression with ``--rotate-pages`` on pages that already had +- Fixed a regression with ``--rotate-pages`` on pages that already had rotations applied. - (`#279 `__) + (:issue:`279`) - Improve quality of page rotation in some cases by rasterizing a higher quality preview image. - (`#281 `__) + (:issue:`281`) v7.0.1 ====== -- Fix compatibility with img2pdf >= 0.3.0 by rejecting input images +- Fixed compatibility with img2pdf >= 0.3.0 by rejecting input images that have an alpha channel - Add forward compatibility for pikepdf 0.3.0 (unrelated to img2pdf) - Various documentation updates for v7.0.0 changes @@ -1225,7 +1230,7 @@ v6.2.4 v6.2.3 ====== -- Fix compatibility with img2pdf >= 0.3.0 by rejecting input images +- Fixed compatibility with img2pdf >= 0.3.0 by rejecting input images that have an alpha channel - This version will be included in Ubuntu 18.10 @@ -1242,9 +1247,8 @@ v6.2.2 v6.2.1 ====== -- Fix recent versions of Tesseract (after 4.0.0-beta1) not being - detected as supporting the ``sandwich`` renderer - (`#271 `__). +- Fixed recent versions of Tesseract (after 4.0.0-beta1) not being + detected as supporting the ``sandwich`` renderer (:issue:`271`). v6.2.0 ====== @@ -1257,21 +1261,19 @@ v6.2.0 - Creation of PDF/A-3 is now supported. However, there is no ability to attach files to PDF/A-3. - Lists more reasons why the file size might grow. -- Fix issue - `#262 `__, +- Fixed :issue:`262`, ``--remove-background`` error on PDFs contained colormapped (paletted) images. -- Fix another XMP metadata validation issue, in cases where the input +- Fixed another XMP metadata validation issue, in cases where the input file's creation date has no timezone and the creation date is not overridden. v6.1.5 ====== -- Fix issue - `#253 `__, a +- Fixed :issue:`253`, a possible division by zero when using the ``hocr`` renderer. -- Fix incorrectly formatted ```` field inside XMP +- Fixed incorrectly formatted ```` field inside XMP metadata for PDF/As. veraPDF flags this as a PDF/A validation failure. The error is caused the timezone and final digit of the seconds of modified time to be omitted, so at worst the modification @@ -1280,7 +1282,7 @@ v6.1.5 v6.1.4 ====== -- Fix issue `#248 `__ +- Fixed :issue:`248` ``--clean`` argument may remove OCR from left column of text on certain documents. We now set ``--layout none`` to suppress this. - The test cache was updated to reflect the change above. @@ -1305,8 +1307,7 @@ Notes v6.1.3 ====== -- Fix issue - `#247 `__, +- Fixed :issue:`247`, ``/CreationDate`` metadata not copied from input to output. - A warning is now issued when Python 3.5 is used on files with a large page count, as this case is known to regress to single core @@ -1316,13 +1317,13 @@ v6.1.2 ====== - Upgrade to PyMuPDF v1.12.5 which includes a more complete fix to - `#239 `__. + :issue:`239`. - Add ``defusedxml`` dependency. v6.1.1 ====== -- Fix text being reported as found on all pages if PyMuPDF is not +- Fixed text being reported as found on all pages if PyMuPDF is not installed. v6.1.0 @@ -1333,15 +1334,15 @@ v6.1.0 PyMuPDF than the author anticipated. (For version 6.x only) install OCRmyPDF with ``pip install ocrmypdf[fitz]`` to use it to its full potential. -- Fix ``FileExistsError`` that could occur if OCR timed out while it +- Fixed ``FileExistsError`` that could occur if OCR timed out while it was generating the output file. - (`#218 `__) -- Fix table of contents/bookmarks all being redirected to page 1 when + (:issue:`218`) +- Fixed table of contents/bookmarks all being redirected to page 1 when generating a PDF/A (with PyMuPDF). (Without PyMuPDF the table of contents is removed in PDF/A mode.) -- Fix "RuntimeError: invalid key in dict" when table of +- Fixed "RuntimeError: invalid key in dict" when table of contents/bookmarks titles contained the character ``)``. - (`#239 `__) + (:issue:`239`) - Added a new argument ``--skip-repair`` to skip the initial PDF repair step if the PDF is already well-formed (because another program repaired it). @@ -1368,35 +1369,29 @@ v6.0.0 - Fixed an issue where OCRmyPDF failed to detect existing text on pages, depending on how the text and fonts were encoded within the - PDF. (`#233 `__, - `#232 `__) + PDF. (:issue:`233,232`) - Fixed an issue that caused dramatic inflation of file sizes when ``--skip-text --output-type pdf`` was used. OCRmyPDF now removes duplicate resources such as fonts, images and other objects that it - generates. - (`#237 `__) + generates. (:issue:`237`) - Improved performance of the initial page splitting step. Originally this step was not believed to be expensive and ran in a process. Large file testing revealed it to be a bottleneck, so it is now parallelized. On a 700 page file with quad core machine, this change - saves about 2 minutes. - (`#234 `__) + saves about 2 minutes. (:issue:`234`) - The test suite now includes a cache that can be used to speed up test runs across platforms. This also does not require computing - checksums, so it's faster. - (`#217 `__) + checksums, so it's faster. (:issue:`217`) v5.7.0 ====== - Fixed an issue that caused poor CPU utilization on machines with more - than 4 cores when running Tesseract 4. (Related to issue - `#217 `__.) + than 4 cores when running Tesseract 4. (Related to :issue:`217`.) - The 'hocr' renderer has been improved. The 'sandwich' and 'tesseract' renderers are still better for most use cases, but 'hocr' may be useful for people who work with the PDF.js renderer in English/ASCII - languages. - (`#225 `__) + languages. (:issue:`225`) - It now formats text in a matter that is easier for certain PDF viewers to select and extract copy and paste text. This should @@ -1424,11 +1419,10 @@ v5.6.2 v5.6.1 ====== -- Fix issue - `#219 `__: change +- Fixed :issue:`219`: change how the final output file is created to avoid triggering permission errors when the output is a special file such as ``/dev/null`` -- Fix test suite failures due to a qpdf 8.0.0 regression and Python +- Fixed test suite failures due to a qpdf 8.0.0 regression and Python 3.5's handling of symlink - The "encrypted PDF" error message was different depending on the type of PDF encryption. Now a single clear message appears for all types @@ -1441,8 +1435,7 @@ v5.6.1 v5.6.0 ====== -- Fix issue - `#216 `__: preserve +- Fixed :issue:`216`: preserve "text as curves" PDFs without rasterizing file - Related to the above, messages about rasterizing are more consistent - For consistency versions minor releases will now get the trailing .0 @@ -1454,34 +1447,32 @@ v5.5 - Add new argument ``--max-image-mpixels``. Pillow 5.0 now raises an exception when images may be decompression bombs. This argument can be used to override the limit Pillow sets. -- Fix output page cropped when using the sandwich renderer and OCR is +- Fixed output page cropped when using the sandwich renderer and OCR is skipped on a rotated and image-processed page - A warning is now issued when old versions of Ghostscript are used in cases known to cause issues with non-Latin characters -- Fix a few parameter validation checks for ``-output-type pdfa-1`` and +- Fixed a few parameter validation checks for ``-output-type pdfa-1`` and ``pdfa-2`` v5.4.4 ====== -- Fix issue - `#181 `__: fix +- Fixed :issue:`181`: fix final merge failure for PDFs with more pages than the system file handle limit (``ulimit -n``) -- Fix issue - `#200 `__: an +- Fixed :issue:`200`: an uncommon syntax for formatting decimal numbers in a PDF would cause qpdf to issue a warning, which ocrmypdf treated as an error. Now this the warning is relayed. -- Fix an issue where intermediate PDFs would be created at version 1.3 +- Fixed an issue where intermediate PDFs would be created at version 1.3 instead of the version of the original file. It's possible but unlikely this had side effects. - A warning is now issued when older versions of qpdf are used since issues like - `#200 `__ cause + :issue:`200` cause qpdf to infinite-loop - Address issue - `#140 `__: if + :issue:`140`: if Tesseract outputs invalid UTF-8, escape it and print its message instead of aborting with a Unicode error - Adding previously unlisted setup requirement, pytest-runner @@ -1547,13 +1538,13 @@ v5.3 forwarded to Tesseract OCR as words and regular expressions respective to use to guide OCR. Supplying a list of subject-domain words should assist Tesseract with resolving words. - (`#165 `__) + (:issue:`165`) - Using a non Latin-1 language with the "hocr" renderer now warns about possible OCR quality and recommends workarounds - (`#176 `__) + (:issue:`176`) - Output file path added to error message when that location is not writable - (`#175 `__) + (:issue:`175`) - Otherwise valid PDFs with leading whitespace at the beginning of the file are now accepted @@ -1581,8 +1572,7 @@ v5.1 v5.0.1 ====== -- Fixed issue - `#169 `__, +- Fixed :issue:`169`, exception due to failure to create sidecar text files on some versions of Tesseract 3.04, including the jbarlow83/ocrmypdf Docker image @@ -1600,19 +1590,17 @@ v5.0 - Add a new feature, ``--sidecar``, which allows creating "sidecar" text files which contain the OCR results in plain text. These OCR text is more reliable than extracting text from PDFs. Closes - `#126 `__. + :issue:`126`. - New feature: ``--pdfa-image-compression``, which allows overriding Ghostscript's lossy-or-lossless image encoding heuristic and making all images JPEG encoded or lossless encoded as desired. Fixes - `#163 `__. + :issue:`163`. -- Fixed issue - `#143 `__, added +- Fixed :issue:`143`, added ``--quiet`` to suppress "INFO" messages -- Fixed issue - `#164 `__, a typo +- Fixed :issue:`164`, a typo - Removed the command line parameters ``-n`` and ``--just-print`` since they have not worked for some time (reported as Ubuntu bug @@ -1621,17 +1609,14 @@ v5.0 v4.5.6 ====== -- Fixed issue - `#156 `__, +- Fixed :issue:`156`, 'NoneType' object has no attribute 'getObject' on pages with no optional /Contents record. This should resolve all issues related to pages with no /Contents record. -- Fixed issue - `#158 `__, ocrmypdf +- Fixed :issue:`158`, ocrmypdf now stops and terminates if Ghostscript fails on an intermediate step, as it is not possible to proceed. -- Fixed issue - `#160 `__, +- Fixed :issue:`160`, exception thrown on certain invalid arguments instead of error message @@ -1639,20 +1624,19 @@ v4.5.5 ====== - Automated update of macOS homebrew tap -- Fixed issue - `#154 `__, KeyError +- Fixed :issue:`154`, KeyError '/Contents' when searching for text on blank pages that have no /Contents record. Note: incomplete fix for this issue. v4.5.4 ====== -- Fix ``--skip-big`` raising an exception if a page contains no images - (`#152 `__) (thanks +- Fixed ``--skip-big`` raising an exception if a page contains no images + (:issue:`152`) (thanks to @TomRaz) -- Fix an issue where pages with no images might trigger "cannot write +- Fixed an issue where pages with no images might trigger "cannot write mode P as JPEG" - (`#151 `__) + (:issue:`151`) v4.5.3 ====== @@ -1671,8 +1655,7 @@ v4.5.3 v4.5.2 ====== -- Fix issue - `#147 `__. +- Fixed :issue:`147`, ``--pdf-renderer tess4 --clean`` will produce an oversized page containing the original image in the bottom left corner, due to loss DPI information. @@ -1682,8 +1665,7 @@ v4.5.2 v4.5.1 ====== -- Fix issue - `#137 `__, +- Fixed :issue:`137`, proportions of images with a non-square pixel aspect ratio would be distorted in output for ``--force-ocr`` and some other combinations of flags @@ -1692,7 +1674,7 @@ v4.5 ==== - PDFs containing "Form XObjects" are now supported (issue - `#134 `__; PDF + :issue:`134`; PDF reference manual 8.10), and images they contain are taken into account when determining the resolution for rasterizing - The Tesseract 4 Docker image no longer includes all languages, @@ -1821,7 +1803,7 @@ v4.2.5 ====== - Fixed an issue - (`#100 `__) with + (:issue:`100`) with PDFs that omit the optional /BitsPerComponent parameter on images - Removed non-free file milk.pdf @@ -1829,7 +1811,7 @@ v4.2.4 ====== - Fixed an error - (`#90 `__) caused by + (:issue:`90`) caused by PDFs that use stencil masks properly - Fixed handling of PDFs that try to draw images or stencil masks without properly setting up the graphics state (such images are now @@ -1867,7 +1849,7 @@ v4.2 - ocrmypdf will now try to convert single image files to PDFs if they are provided as input - (`#15 `__) + (:issue:`15`) - This is a basic convenience feature. It only supports a single image and always makes the image fill the whole page. @@ -1895,11 +1877,11 @@ v4.2 - supports reinterpreting PDFs where text was rendered as curves for printing, and text needs to be recovered - fixes issue - `#82 `__ + :issue:`82` - Fixes an issue where, with certain settings, monochrome images in PDFs would be converted to 8-bit grayscale, increasing file size - (`#79 `__) + (:issue:`79`) - Support for Ubuntu 12.04 LTS "precise" has been dropped in favor of (roughly) Ubuntu 14.04 LTS "trusty" @@ -1927,7 +1909,7 @@ v4.1.3 - More helpful error message for PDFs with version 4 security handler - Update usage instructions for Windows/Docker users -- Fix order of operations for matrix multiplication (no effect on most +- Fixed order of operations for matrix multiplication (no effect on most users) - Add a few leptonica wrapper functions (no effect on most users) @@ -2020,7 +2002,7 @@ New features dominant orientation of detectable text. This feature is fairly reliable but some false positives occur especially if there is not much text to work with. - (`#4 `__) + (:issue:`4`) - Deskewing is now performed using Leptonica instead of unpaper. Leptonica is faster and more reliable at image deskewing than unpaper. @@ -2033,13 +2015,13 @@ Fixes - Fixed an issue where lossless reconstruction could misalign the graphics layer with respect to text layer if the page had been cropped such that its origin is not (0, 0) - (`#49 `__) + (:issue:`49`) Changes - Logging output is now much easier to read - ``--deskew`` is now performed by Leptonica instead of unpaper - (`#25 `__) + (:issue:`25`) - libffi is now required - Some changes were made to the Docker and Travis build environments to support libffi @@ -2054,7 +2036,7 @@ v3.2.1 Changes -- Fixed issue `#47 `__ +- Fixed :issue:`47` "convert() got and unexpected keyword argument 'dpi'" by upgrading to img2pdf 0.2 - Tweaked the Dockerfiles @@ -2099,8 +2081,7 @@ Changes - Python 3.5 and macOS El Capitan are now supported platforms - no changes were needed to implement support - Improved some error messages related to missing input files -- Fixed issue `#20 `__ - - uppercase .PDF extension not accepted +- Fixed :issue:`20`: uppercase .PDF extension not accepted - Fixed an issue where OCRmyPDF failed to text that certain pages contained previously OCR'ed text, such as OCR text produced by Tesseract 3.04 @@ -2177,19 +2158,19 @@ Release candidates^ - rc9: - - fix issue - `#118 `__: + - Fix + :issue:`118`: report error if ghostscript iccprofiles are missing - fixed another issue related to - `#111 `__: PDF + :issue:`111`: PDF rasterized to palette file - add support image files with a palette - don't try to validate PDF file after an exception occurs - rc8: - - fix issue - `#111 `__: + - Fix + :issue:`111`: exception thrown if PDF is missing DocumentInfo dictionary - rc7: diff --git a/setup.cfg b/setup.cfg index 98bce9fa..e4134433 100644 --- a/setup.cfg +++ b/setup.cfg @@ -6,7 +6,7 @@ long_description_content_type = text/markdown; charset=UTF-8 url = https://github.com/jbarlow83/OCRmyPDF author = James R. Barlow author_email = james@purplerock.ca -license_files = +license_files = LICENSE keywords = PDF @@ -79,7 +79,8 @@ test = # or brew install exempi docs = sphinx - sphinx_rtd_theme + sphinx_rtd_theme + sphinx-issues extended_test = PyMuPDF == 1.13.4 watcher = From e760be9e19f303de7d5cce3567fbfd7052920321 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Wed, 16 Jun 2021 00:39:51 -0700 Subject: [PATCH 020/106] Drop an obsolete documentation file --- docs/pipeline.svg | 392 ---------------------------------------------- 1 file changed, 392 deletions(-) delete mode 100644 docs/pipeline.svg diff --git a/docs/pipeline.svg b/docs/pipeline.svg deleted file mode 100644 index dc9e3011..00000000 --- a/docs/pipeline.svg +++ /dev/null @@ -1,392 +0,0 @@ - - - - - - -Pipeline: - - -clustertasks - -Pipeline: - - - -t0 - - - - -ocrmypdf.pipeline.triage - - - -t1 - - - - -ocrmypdf.pipeline.repair_and_parse_pdf - - - -t0->t1 - - - - - -t2 - - -ocrmypdf.pipeline.marker_pages - - - -t1->t2 - - - - - -t16 - - - - -ocrmypdf.pipeline.weave_layers - - - -t1->t16 - - - - - -t17 - - - - -ocrmypdf.pipeline.generate_postscript_stub - - - -t1->t17 - - - - - -t18 - - -ocrmypdf.pipeline.metadata_fixup - - - -t1->t18 - - - - - -t3 - - -ocrmypdf.pipeline.ocr_or_skip - - - -t2->t3 - - - - - -t4 - - - - -ocrmypdf.pipeline.rasterize_preview - - - -t3->t4 - - - - - -t5 - - - - -ocrmypdf.pipeline.orient_page - - - -t3->t5 - - - - - -t4->t5 - - - - - -t6 - - - - -ocrmypdf.pipeline.rasterize_with_ghostscript - - - -t5->t6 - - - - - -t13 - -ocrmypdf.pipeline.select_image_layer - - - -t5->t13 - - - - - -t7 - - - - -ocrmypdf.pipeline.preprocess_remove_background - - - -t6->t7 - - - - - -t12 - -ocrmypdf.pipeline.select_visible_page_image - - - -t6->t12 - - - - - -t8 - - - - -ocrmypdf.pipeline.preprocess_deskew - - - -t7->t8 - - - - - -t7->t12 - - - - - -t9 - - - - -ocrmypdf.pipeline.preprocess_clean - - - -t8->t9 - - - - - -t8->t12 - - - - - -t10 - - - - -ocrmypdf.pipeline.select_ocr_image - - - -t9->t10 - - - - - -t9->t12 - - - - - -t11 - - - - -ocrmypdf.pipeline.ocr_tesseract_hocr - - - -t10->t11 - - - - - -t15 - - - - -ocrmypdf.pipeline.ocr_tesseract_textonly_pdf - - - -t10->t15 - - - - - -t14 - - - - -ocrmypdf.pipeline.render_hocr_page - - - -t11->t14 - - - - - -t19 - - -ocrmypdf.pipeline.merge_sidecars - - - -t11->t19 - - - - - -t14->t16 - - - - - -t15->t16 - - - - - -t15->t19 - - - - - -t12->t13 - - - - - -t13->t16 - - - - - -t16->t18 - - - - - -t17->t18 - - - - - -t20 - - - - -ocrmypdf.pipeline.optimize_pdf - - - -t18->t20 - - - - - -t21 - - -ocrmypdf.pipeline.copy_final - - - -t20->t21 - - - - - From e30fffa8a4f179cb6106d10b63069f42534739a8 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Wed, 16 Jun 2021 00:40:47 -0700 Subject: [PATCH 021/106] v12.2.0 release notes --- docs/release_notes.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/release_notes.rst b/docs/release_notes.rst index 6988e2a1..94505288 100644 --- a/docs/release_notes.rst +++ b/docs/release_notes.rst @@ -13,11 +13,11 @@ The public API may be useful in scripts that launch OCRmyPDF processes or that wish to use some of its features for working with PDFs. -v12.1.1 +v12.2.0 ======= - Fixed invalid Tesseract version number on Windows (:issue:`795`). -- Documentation tweaks. +- Documentation tweaks. Documentation build now depends on sphinx-issues package. v12.1.0 ======= From c935ba070b8498ff450a890c100bee2ddf8c753f Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Wed, 23 Jun 2021 00:43:30 -0700 Subject: [PATCH 022/106] Fix name of sphinx-rtd-theme --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index e4134433..44435e3f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -79,7 +79,7 @@ test = # or brew install exempi docs = sphinx - sphinx_rtd_theme + sphinx-rtd-theme sphinx-issues extended_test = PyMuPDF == 1.13.4 From 0b834411fe5dcae61d79952984fe30dfd029ecd4 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Mon, 28 Jun 2021 15:19:59 -0700 Subject: [PATCH 023/106] validation: mention ISO 639-2 to give people a clue about how to find the appropriate code --- src/ocrmypdf/_validation.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/ocrmypdf/_validation.py b/src/ocrmypdf/_validation.py index 14768481..4c348f5b 100644 --- a/src/ocrmypdf/_validation.py +++ b/src/ocrmypdf/_validation.py @@ -65,13 +65,14 @@ def check_options_languages(options, ocr_engine_languages): log.debug("No language specified; assuming --language %s", DEFAULT_LANGUAGE) if not ocr_engine_languages: return - if not options.languages.issubset(ocr_engine_languages): + missing_languages = options.languages - ocr_engine_languages + if missing_languages: msg = ( f"OCR engine does not have language data for the following " "requested languages: \n" ) - for lang in options.languages - ocr_engine_languages: - msg += lang + '\n' + msg += '\n'.join(lang for lang in missing_languages) + msg += '\nNote: most languages are identified by a 3-digit ISO 639-2 Code' raise MissingDependencyError(msg) From d2d39de92f129a8cacabfd453584eddaac40477d Mon Sep 17 00:00:00 2001 From: mara004 <65915611+mara004@users.noreply.github.com> Date: Mon, 5 Jul 2021 23:12:51 +0200 Subject: [PATCH 024/106] Update api.rst (#797) fix excess 'no' --- docs/api.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api.rst b/docs/api.rst index 3459e8b8..5da9cf4d 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -44,7 +44,7 @@ execution. To do this, it will: The Python process that calls ``ocrmypdf.ocr()`` must be sufficiently privileged to perform these actions. -There is no currently no option to manage how jobs are scheduled other +There currently is no option to manage how jobs are scheduled other than the argument ``jobs=`` which will limit the number of worker processes. From 5cba68b93dbacc2318cbb56943224c3b66cb7267 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Wed, 14 Jul 2021 00:11:47 -0700 Subject: [PATCH 025/106] tests: Don't require symlink permissions on Windows Some of tests required symlink permissions, which CI workers have but typical Windows user accounts do not. Mostly these are just correctness tests. --- tests/test_helpers.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/test_helpers.py b/tests/test_helpers.py index 5af7f610..9ab133be 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -16,6 +16,8 @@ from ocrmypdf import helpers from .conftest import running_in_docker +needs_symlink = pytest.mark.skipif(os.name == 'nt', reason='needs posix symlink') + class TestSafeSymlink: def test_safe_symlink_link_self(self, tmp_path, caplog): @@ -27,6 +29,7 @@ class TestSafeSymlink: with pytest.raises(FileExistsError): helpers.safe_symlink(tmp_path / 'input', tmp_path / 'regular_file') + @needs_symlink def test_safe_symlink_relink(self, tmp_path): (tmp_path / 'regular_file_a').touch() (tmp_path / 'regular_file_b').write_bytes(b'ABC') @@ -77,6 +80,7 @@ class TestFileIsWritable: def test_plain(self, non_existent): assert helpers.is_file_writable(non_existent) + @needs_symlink def test_symlink_loop(self, tmp_path): loop = tmp_path / 'loop' loop.symlink_to(loop) From de74b80335a6e54eed1bddeb223a9c73a9dd05e3 Mon Sep 17 00:00:00 2001 From: mara004 <65915611+mara004@users.noreply.github.com> Date: Wed, 14 Jul 2021 09:48:00 +0200 Subject: [PATCH 026/106] docs: Fix two missing words (#805) * Fix two missing words --- docs/pdfsecurity.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/pdfsecurity.rst b/docs/pdfsecurity.rst index 4885ab03..9288e311 100644 --- a/docs/pdfsecurity.rst +++ b/docs/pdfsecurity.rst @@ -19,7 +19,7 @@ PDF is a rich, complex file format. The official PDF 1.7 specification, ISO 32000:2008, is hundreds of pages long and references several annexes each of which are similar in length. PDFs can contain video, audio, XML, JavaScript and other programming, and forms. In some cases, they can -open internet connections to pre-selected URLs. All of these possible +open internet connections to pre-selected URLs. All of these are possible attack vectors. In short, PDFs `may contain @@ -31,7 +31,7 @@ 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 restore searchability. +could be used to restore searchability. How OCRmyPDF processes PDFs =========================== @@ -66,8 +66,8 @@ service. OCRmyPDF relies on Ghostscript, and therefore, if deployed online one should be prepared to comply with Ghostscript's Affero GPL license, and any other licenses. -Setting aside these concerns, a side effect of OCRmyPDF is it may -incidentally sanitize PDFs that contain certain types of malware. It +Setting aside these concerns, a side effect of OCRmyPDF is that it may +incidentally sanitize PDFs containing certain types of malware. It repairs the PDF with pikepdf/libqpdf, which could correct malformed PDF structures that are part of an attack. When PDF/A output is selected (the default), the input PDF is partially reconstructed by Ghostscript. @@ -83,7 +83,7 @@ Limiting CPU usage OCRmyPDF will attempt to use all available CPUs and storage, so executing ``nice ocrmypdf`` or limiting the number of jobs with the ``-j`` argument may ensure the server remains available. Another option -would be run OCRmyPDF jobs inside a Docker container, a virtual machine, +would be to run OCRmyPDF jobs inside a Docker container, a virtual machine, or a cloud instance, which can impose its own limits on CPU usage and be terminated "from orbit" if it fails to complete. From 773e28478c668d28832144ff9fb42dc0b4cd13fb Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Wed, 14 Jul 2021 01:44:26 -0700 Subject: [PATCH 027/106] graft: don't use deprecated pikepdf APIs --- src/ocrmypdf/_graft.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/ocrmypdf/_graft.py b/src/ocrmypdf/_graft.py index ba4b050f..7b9a9a55 100644 --- a/src/ocrmypdf/_graft.py +++ b/src/ocrmypdf/_graft.py @@ -47,7 +47,8 @@ def strip_invisible_text(pdf, page): render_mode = 0 text_objects = [] - page.page_contents_coalesce() + rich_page = pikepdf.Page(page) + rich_page.contents_coalesce() for operands, operator in pikepdf.parse_content_stream(page, ''): if not in_text_obj: if operator == pikepdf.Operator('BT'): @@ -307,7 +308,14 @@ class OcrGrafter: if strip_old_text: strip_invisible_text(self.pdf_base, base_page) - base_page.page_contents_add(new_text_layer, prepend=True) + if hasattr(pikepdf.Page, 'contents_add'): + # pikepdf >= 2.14 adds this method and deprecates the one below + pikepdf.Page(base_page).contents_add(new_text_layer, prepend=True) + else: + # pikepdf < 2.14 + base_page.page_contents_add( + new_text_layer, prepend=True + ) # pragma: no cover _update_resources( obj=base_page, font=font, font_key=font_key, procset=procset From 37923ffe52be801077739effbd04dc5a59b849d9 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Wed, 14 Jul 2021 02:34:28 -0700 Subject: [PATCH 028/106] Work around Pillow 8.3.1 DPI changes Pillow decided against round-tripping DPI values. https://github.com/python-pillow/Pillow/pull/5476 Fixes #802 --- src/ocrmypdf/helpers.py | 23 +++++++++++++++++++++-- tests/test_ghostscript.py | 2 +- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/ocrmypdf/helpers.py b/src/ocrmypdf/helpers.py index d7080f34..916fb42c 100644 --- a/src/ocrmypdf/helpers.py +++ b/src/ocrmypdf/helpers.py @@ -25,19 +25,31 @@ log = logging.getLogger(__name__) class Resolution(namedtuple('Resolution', ('x', 'y'))): - """The number of pixels per inch in each 2D direction.""" + """The number of pixels per inch in each 2D direction. + + Resolution objects are considered "equal" for == purposes if they are + equal to a reasonable tolerance. + """ __slots__ = () + # rel_tol after converting from dpi to pixels per meter and saving + # as integer with rounding, as many file formats + CONVERSION_ERROR = 0.002 + def round(self, ndigits: int): return Resolution(round(self.x, ndigits), round(self.y, ndigits)) def to_int(self): return Resolution(int(round(self.x)), int(round(self.y))) + @classmethod + def _isclose(cls, a, b): + return isclose(a, b, rel_tol=cls.CONVERSION_ERROR) + @property def is_square(self) -> bool: - return isclose(self.x, self.y, rel_tol=1e-3) + return self._isclose(self.x, self.y) @property def is_finite(self) -> bool: @@ -61,6 +73,13 @@ class Resolution(namedtuple('Resolution', ('x', 'y'))): def __repr__(self): # pragma: no cover return f"Resolution({self.x}x{self.y} dpi)" + def __eq__(self, other): + if isinstance(other, tuple) and len(other) == 2: + other = Resolution(*other) + if not isinstance(other, Resolution): + return NotImplemented + return self._isclose(self.x, other.x) and self._isclose(self.y, other.y) + class NeverRaise(Exception): """An exception that is never raised""" diff --git a/tests/test_ghostscript.py b/tests/test_ghostscript.py index 0907b819..28ecfe26 100644 --- a/tests/test_ghostscript.py +++ b/tests/test_ghostscript.py @@ -72,7 +72,7 @@ def test_rasterize_rotated(francais, outdir, caplog): with Image.open(outdir / 'out.png') as im: assert im.size == (target_size[1], target_size[0]) - assert im.info['dpi'] == (forced_dpi[1], forced_dpi[0]) + assert im.info['dpi'] == forced_dpi.flip_axis() def test_gs_render_failure(resources, outpdf): From 1c2adc3d89fe1dae12d2a8bfce4427dd7cb2a809 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Wed, 14 Jul 2021 02:38:23 -0700 Subject: [PATCH 029/106] v12.3.0 release notes --- docs/release_notes.rst | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/release_notes.rst b/docs/release_notes.rst index 94505288..6d35ba65 100644 --- a/docs/release_notes.rst +++ b/docs/release_notes.rst @@ -13,6 +13,19 @@ The public API may be useful in scripts that launch OCRmyPDF processes or that wish to use some of its features for working with PDFs. +v12.3.0 +======= + +- Fixed a regression introduced in Pillow 8.3.0. Pillow no longer rounds DPI + for image resolutions. We now account for this. (:issue:`802`) +- We no longer use some API calls that are deprecated in the latest versions of + pikepdf. +- Improved error message when a language is requested that doesn't look like a + typical ISO 639-2 code. +- Fixed some tests that attempted to symlink on Windows, breaking tests on a + Windows desktop but not usually on CI. +- Documentation fixes (thanks to @mara004) + v12.2.0 ======= From 814ad36e512d7a068a221962138f669c636b2c25 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Thu, 15 Jul 2021 19:06:46 -0700 Subject: [PATCH 030/106] dockerfile: try newer ubuntu --- .docker/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.docker/Dockerfile b/.docker/Dockerfile index c55fcd7c..2956f489 100644 --- a/.docker/Dockerfile +++ b/.docker/Dockerfile @@ -1,6 +1,6 @@ # OCRmyPDF # -FROM ubuntu:20.04 as base +FROM ubuntu:21.04 as base FROM base as builder From 2366629774579b0e04faa01ba863e0c0ff65fe16 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Thu, 15 Jul 2021 23:12:38 -0700 Subject: [PATCH 031/106] docker: Fix timezone prompt --- .docker/Dockerfile | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.docker/Dockerfile b/.docker/Dockerfile index 2956f489..5fda63ea 100644 --- a/.docker/Dockerfile +++ b/.docker/Dockerfile @@ -1,10 +1,13 @@ # OCRmyPDF # + FROM ubuntu:21.04 as base -FROM base as builder - ENV LANG=C.UTF-8 +ENV TZ=UTC +RUN echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selections + +FROM base as builder RUN apt-get update && apt-get install -y --no-install-recommends \ build-essential autoconf automake libtool \ @@ -41,8 +44,6 @@ RUN pip3 install --no-cache-dir .[test,webservice,watcher] FROM base -ENV LANG=C.UTF-8 - RUN apt-get update && apt-get install -y --no-install-recommends \ ghostscript \ img2pdf \ From 4863a8e521eca0fbf8df7cee16fdaadde8609c23 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Thu, 15 Jul 2021 23:33:12 -0700 Subject: [PATCH 032/106] docker: make base install packages in common --- .docker/Dockerfile | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/.docker/Dockerfile b/.docker/Dockerfile index 5fda63ea..416a4af0 100644 --- a/.docker/Dockerfile +++ b/.docker/Dockerfile @@ -7,6 +7,12 @@ ENV LANG=C.UTF-8 ENV TZ=UTC RUN echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selections +RUN apt-get update && apt-get install -y --no-install-recommends \ + python3 \ + libqpdf-dev \ + zlib1g \ + liblept5 + FROM base as builder RUN apt-get update && apt-get install -y --no-install-recommends \ @@ -16,7 +22,6 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ python3-dev \ python3-distutils \ libffi-dev \ - libqpdf-dev \ ca-certificates \ curl \ git @@ -47,12 +52,8 @@ FROM base RUN apt-get update && apt-get install -y --no-install-recommends \ ghostscript \ img2pdf \ - liblept5 \ libsm6 libxext6 libxrender-dev \ - zlib1g \ pngquant \ - python3 \ - qpdf \ tesseract-ocr \ tesseract-ocr-chi-sim \ tesseract-ocr-deu \ From 73b8b88724aba1c71df04310e84b8f645b85d287 Mon Sep 17 00:00:00 2001 From: Kai Knoblich <43905002+knobix@users.noreply.github.com> Date: Tue, 20 Jul 2021 22:35:19 +0200 Subject: [PATCH 033/106] Update the FreeBSD specific parts of the documentation (#810) The Python default version was changed from 3.7 to 3.8 at the end of April. For the new quarterly branch, 2021Q3, Python 3.8 has also been the default version since July. --- docs/installation.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/installation.rst b/docs/installation.rst index 5b42f11f..8439cdfe 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -23,7 +23,7 @@ These platforms have one-liner installs: +-------------------------------+-------------------------------+ | LinuxBrew | ``brew install ocrmypdf`` | +-------------------------------+-------------------------------+ -| FreeBSD | ``pkg install py37-ocrmypdf`` | +| FreeBSD | ``pkg install py38-ocrmypdf`` | +-------------------------------+-------------------------------+ | Conda (WSL, macOS, Linux) | ``conda install ocrmypdf`` | +-------------------------------+-------------------------------+ @@ -635,7 +635,7 @@ versions likely work but have not been tested. .. code-block:: bash - pkg install py37-ocrmypdf + pkg install py38-ocrmypdf To install a more recent version, you could attempt to first install the system version with ``pkg``, then use ``pip install --user ocrmypdf``. From 22dd9314ea7100a998ed2fca59cb81a1057eaa44 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Tue, 20 Jul 2021 23:24:17 -0700 Subject: [PATCH 034/106] logo: add square version --- docs/images/logo-square.png | Bin 0 -> 14848 bytes docs/images/logo-square.svg | 233 ++++++++++++++++++++++++++++++++++++ 2 files changed, 233 insertions(+) create mode 100644 docs/images/logo-square.png create mode 100644 docs/images/logo-square.svg diff --git a/docs/images/logo-square.png b/docs/images/logo-square.png new file mode 100644 index 0000000000000000000000000000000000000000..0f4ace15c78844ecf3515a6cd3c77e8485413854 GIT binary patch literal 14848 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 literal 0 HcmV?d00001 diff --git a/docs/images/logo-square.svg b/docs/images/logo-square.svg new file mode 100644 index 00000000..b5ae241e --- /dev/null +++ b/docs/images/logo-square.svg @@ -0,0 +1,233 @@ + +image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From f99fd686deea2761042866dcf59a34a71338ce4d Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Sun, 1 Aug 2021 00:49:57 -0700 Subject: [PATCH 035/106] hocrtransform: ensure text is rendered in document order Previously we rendered text objects based on their vertical position, but this confuses some PDF viewers. Closes #813. --- src/ocrmypdf/hocrtransform.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/ocrmypdf/hocrtransform.py b/src/ocrmypdf/hocrtransform.py index 799c0099..ea91b673 100755 --- a/src/ocrmypdf/hocrtransform.py +++ b/src/ocrmypdf/hocrtransform.py @@ -31,7 +31,6 @@ import argparse import os import re -from itertools import chain from math import atan, cos, sin from pathlib import Path from typing import Any, NamedTuple, Optional, Tuple, Union @@ -297,13 +296,11 @@ class HocrTransform: ) found_lines = False - for line in sorted( - chain( - self.hocr.iterfind(self._child_xpath('span', 'ocr_header')), - self.hocr.iterfind(self._child_xpath('span', 'ocr_line')), - self.hocr.iterfind(self._child_xpath('span', 'ocr_textfloat')), - ), - key=self.topdown_position, + for line in ( + element + for element in self.hocr.iterfind(self._child_xpath('span')) + if 'class' in element.attrib + and element.attrib['class'] in {'ocr_header', 'ocr_line', 'ocr_textfloat'} ): found_lines = True self._do_line( From aa10a70d70554b621480a50ac9f751f4089d8d48 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Sun, 1 Aug 2021 01:00:05 -0700 Subject: [PATCH 036/106] Rebuild test cache due to hocr output change --- .../pdf.bin | Bin 4036 -> 4036 bytes .../pdf.bin | Bin 3501 -> 3501 bytes .../pdf.bin | Bin 2962 -> 2962 bytes .../pdf.bin | Bin 4068 -> 4068 bytes .../hocr.bin | 2 +- .../pdf.bin | Bin 2989 -> 2989 bytes .../hocr.bin | 2 +- .../pdf.bin | Bin 10251 -> 10251 bytes .../hocr.bin | 2 +- .../pdf.bin | Bin 11311 -> 10251 bytes .../txt.bin | 6 +- .../hocr.bin | 2 +- .../pdf.bin | Bin 11615 -> 10251 bytes .../txt.bin | 169 ++-- .../hocr.bin | 2 +- .../pdf.bin | Bin 12553 -> 10251 bytes .../txt.bin | 165 ++-- .../hocr.bin | 2 +- .../pdf.bin | Bin 10251 -> 10251 bytes .../pdf.bin | Bin 3626 -> 0 bytes .../stderr.bin | 1 - .../stdout.bin | 0 .../txt.bin | 13 - .../pdf.bin | Bin 3310 -> 4291 bytes .../txt.bin | 50 +- .../hocr.bin | 754 +++++++++--------- .../txt.bin | 18 +- .../pdf.bin | Bin 5972 -> 5977 bytes .../hocr.bin | 2 +- .../pdf.bin | Bin 2853 -> 2853 bytes tests/cache/manifest.jsonl | 87 +- .../hocr.bin | 160 ++-- .../stderr.bin | 2 +- .../txt.bin | 8 +- .../pdf.bin | Bin 5225 -> 5222 bytes .../stderr.bin | 2 +- .../txt.bin | 8 +- .../hocr.bin | 103 +-- .../txt.bin | 6 +- .../pdf.bin | Bin 4291 -> 4343 bytes .../txt.bin | 6 +- .../hocr.bin | 299 +++---- .../txt.bin | 28 +- .../pdf.bin | Bin 4042 -> 4334 bytes .../txt.bin | 28 +- .../hocr.bin | 2 +- .../pdf.bin | Bin 8641 -> 8641 bytes .../hocr.bin | 2 +- .../pdf.bin | Bin 8213 -> 8213 bytes .../hocr.bin | 2 +- .../pdf.bin | Bin 5766 -> 5766 bytes .../pdf.bin | Bin 10903 -> 10905 bytes .../txt.bin | 2 +- .../hocr.bin | 2 +- .../pdf.bin | Bin 2798 -> 2798 bytes .../hocr.bin | 2 +- .../pdf.bin | Bin 12736 -> 12736 bytes 57 files changed, 974 insertions(+), 965 deletions(-) delete mode 100644 tests/cache/francais/__-l__deu__000001_ocr.png__000001_ocr_tess__pdf__txt/pdf.bin delete mode 100644 tests/cache/francais/__-l__deu__000001_ocr.png__000001_ocr_tess__pdf__txt/stderr.bin delete mode 100644 tests/cache/francais/__-l__deu__000001_ocr.png__000001_ocr_tess__pdf__txt/stdout.bin delete mode 100644 tests/cache/francais/__-l__deu__000001_ocr.png__000001_ocr_tess__pdf__txt/txt.bin diff --git a/tests/cache/2400dpi/__-l__eng__000001_ocr.png__000001_ocr_tess__pdf__txt/pdf.bin b/tests/cache/2400dpi/__-l__eng__000001_ocr.png__000001_ocr_tess__pdf__txt/pdf.bin index 023d341fe7f1307cf8e660c74b5f5738d2911594..48b6ae30c5644f73bc0fda8a74b158d007bbae8b 100644 GIT binary patch delta 27 icmX>ie?)#mARnKhfrWvgfq|)!fr+kx`Q~`OR7L=9?*}6Q delta 27 icmX>ie?)#mARnKBp^<^Pk)f%vk%6v(#pZavR7L=A3 -
+

diff --git a/tests/cache/aspect/__-l__eng__000001_ocr.png__000001_ocr_tess__pdf__txt/pdf.bin b/tests/cache/aspect/__-l__eng__000001_ocr.png__000001_ocr_tess__pdf__txt/pdf.bin index a0e93b4114a87d718e276a7a65074e736c6f0b1f..ad465b3bea994b9540f04743eae00f1e0a0fe54e 100644 GIT binary patch delta 27 icmZ20zE*sL6&IhOfrWvgfq|*9p{cHc`DPEUR7L=18wRof delta 27 icmZ20zE*sL6&Ig@p^<^Pk)f%PskyF!#byt#R7L=1YzDgk diff --git a/tests/cache/cardinal/__-l__eng__000001_ocr.png__000001_ocr_hocr__hocr__txt/hocr.bin b/tests/cache/cardinal/__-l__eng__000001_ocr.png__000001_ocr_hocr__hocr__txt/hocr.bin index 51333eae..5b72892d 100644 --- a/tests/cache/cardinal/__-l__eng__000001_ocr.png__000001_ocr_hocr__hocr__txt/hocr.bin +++ b/tests/cache/cardinal/__-l__eng__000001_ocr.png__000001_ocr_hocr__hocr__txt/hocr.bin @@ -9,7 +9,7 @@ -

+

diff --git a/tests/cache/cardinal/__-l__eng__000001_ocr.png__000001_ocr_tess__pdf__txt/pdf.bin b/tests/cache/cardinal/__-l__eng__000001_ocr.png__000001_ocr_tess__pdf__txt/pdf.bin index 394e7bbdd0ca074128d8b9007ca16786301ff787..2f4f13b8af3ca0c06ab2c2423219ee758c773dfb 100644 GIT binary patch delta 27 icmeAU=nmL0LygbSz{0@Lz`)el$Vk_~eDgB3R7L=Ha0k)= delta 27 icmeAU=nmL0LygbC(8$2t$k5c-&_dV1V)HV!R7L=Hz6aX? diff --git a/tests/cache/cardinal/__-l__eng__000002_ocr.png__000002_ocr_hocr__hocr__txt/hocr.bin b/tests/cache/cardinal/__-l__eng__000002_ocr.png__000002_ocr_hocr__hocr__txt/hocr.bin index 23a18626..bcfa42c9 100644 --- a/tests/cache/cardinal/__-l__eng__000002_ocr.png__000002_ocr_hocr__hocr__txt/hocr.bin +++ b/tests/cache/cardinal/__-l__eng__000002_ocr.png__000002_ocr_hocr__hocr__txt/hocr.bin @@ -9,7 +9,7 @@ -

+

diff --git a/tests/cache/cardinal/__-l__eng__000002_ocr.png__000002_ocr_tess__pdf__txt/pdf.bin b/tests/cache/cardinal/__-l__eng__000002_ocr.png__000002_ocr_tess__pdf__txt/pdf.bin index dd3620450b6bd9011fd5ddc795757f5e23ba5235..2f4f13b8af3ca0c06ab2c2423219ee758c773dfb 100644 GIT binary patch delta 7636 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 CJL|>( delta 8686 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 -

+

diff --git a/tests/cache/cardinal/__-l__eng__000003_ocr.png__000003_ocr_tess__pdf__txt/pdf.bin b/tests/cache/cardinal/__-l__eng__000003_ocr.png__000003_ocr_tess__pdf__txt/pdf.bin index a9c86e15b00193034bcf2486275e3c6bfe323f43..2f4f13b8af3ca0c06ab2c2423219ee758c773dfb 100644 GIT binary patch delta 7623 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 delta 8979 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 diff --git a/tests/cache/cardinal/__-l__eng__000003_ocr.png__000003_ocr_tess__pdf__txt/txt.bin b/tests/cache/cardinal/__-l__eng__000003_ocr.png__000003_ocr_tess__pdf__txt/txt.bin index d80b111f..d3c2e860 100644 --- a/tests/cache/cardinal/__-l__eng__000003_ocr.png__000003_ocr_tess__pdf__txt/txt.bin +++ b/tests/cache/cardinal/__-l__eng__000003_ocr.png__000003_ocr_tess__pdf__txt/txt.bin @@ -1,128 +1,123 @@ -2A NNI‘I 6F6867# XATALL IE18-80L (818) +The LinnSequencer +32 Track MIDI Sequence Recorder -9SEI6 VO “BUBZIRY, “J0aNS PIPUXO OZLEI -“Uy ‘soTUOMOI,q UUrT +The LinnSequencer is a state-of-the-art composition and performance tool for the professional musician. It is -uut] +extremely powerful, yet amazingly simple to learn and use. It’s many remarkable features include: -“‘SUOS B UIJIM pasueyo oq ABU pue ‘posn oq AWW AYN IVNOIS AWLL AUV -“parlsop Jr SUOTIISUBI} YIOOUIS YIM “BoueNbas eB OJUI pourtueIZOId 9q ABU SFONWHO OdINAL e +¢ Operation is similar to multi-track tape recorder with PLAY, STOP, RECORD, FAST +FORWARD, REWIND, and LOCATE controls. -‘uonng OdNAL dV L 9) uO +e Each of the 100 sequences contains 32 simultaneous, polyphonic tracks. Each track may +be assigned to one of 16 MIDI channels. Simultaneously plays up to 16 polyphonic -sojou Jayienb Suiddy} Aq 10 ‘syUSTIOIOUI oINUTIAI-J8g-Jesg & JO sys} UL ofquisn(pe ‘ATTeouIAUINU paiajus oq ABU OdINALL e +synthesizers! -(jouer doup u3a9) +¢ Ultra-fast 3%” disk drive stores complex songs in seconds and holds over 110,000 notes -“puooes Jed souely O€ 10 “SZ “pz 18 [LVAG-MAd-SHN VU 10 ALOANIWAAd-SLVAd U! patyoeds aq kewl OAL « -‘uoTe1odo [SVx JO} Aj[eusoyUT JoyndUIOd 11g 9] 98108 ZHI 8g ‘poeds-ysry Bann soz] e +per disk! -"9U0} DUAS 0006 UUL] Jo wNIqUUr] prepue}s 0} OUAS [ITAA © +¢ One or all tracks may be TRANSPOSED at the touch of a key. +e Exclusive real-time ERASE function makes editing FAST. +* Exclusive REPEAT function automatically repeats any held notes at a pre-selected -“ONYBA 9}OU poloapes Aue Je sas—nd jndyno 07 pewureigold 3q ACW SL Ad LNO YADONAL OML +rhythmic value. -"ALVOOT 10 GOLS/AV 1d ‘LWddad “ASV +¢ TIMING CORRECTION works during playback and operates without ‘chopping’ notes. -SUIpNpoUr ‘suOTIOUN] posn A[UOUILUOS 94] JO AUBUT [O1]UOD AJ9]OWIAI 0} PousIsse oq ACUI ST AdNI HOLIMSLOO OME « -“SUIPIONAI I[IYM P2sesd JOU Iv $3}OU BUTISIXO—ZUIPIOIA SATON.ASOP-UON +¢ Optional SMPTE time code synchronization. -‘suoneurldxa peuoyippe sdeydsip uowng g1TqH +© Optional remote control. -oy] ‘pepsau JI ‘suoneiodo [ye yYsnosy] NOA sapins ApIespo Avfdsip QO] Joey Z7¢ 9y3—uoeIodo Urea] 0} Ased ‘aus « +Recording a Sequence -jUorel]suowtap & IO} Aepol Jayeap uur’] INOA dag ‘dISHUL -INOA 0} UONUS}]¥ PaplAIPUN INOA SUTJOASp ITY ps pue -p1osai ‘asoduod no Jay 0} pausisap st 1s0uenbesuur’] oy) -Aum Aposiooid $,Jeu], ‘SS9d0Id SATTBS1D OY} YIM SOIOJIOIUT -yey) xo]dwWI0d Os dq JOA9U P[NoUsS osn NOA AZopOuYdE} oy +To record a sequence, simply press RECORD and PLAY, +then play your MIDI keyboard in time to the Sequencer’s +click track. When the sequence loops back around to bar 1, +you’ ll hear what you played—only all timing errors will be -ISTUMOIAUIO?) NOAA UOHISOdWIO) +corrected! (Timing correction may be adjusted or defeated). -"NOSpr] B Oy ‘AONUTJUT yada 0} seq Maz Se] BY] Jas UdAd -uvd NOA ‘palisap JJ ‘souanbes Mou ¥B OVUT sjied ou] [Te Adoo -ATesrewO Ne WI) [IM ONOS ALVAAO JeyIe80} wey} -,deyd,, 0} UOTOUNJ ONOS ALVA ou] asn usy] ‘saouanbes -JENPIAIpUt UI (“949 ‘snJOYD ‘aS1OA) UOTIDIS JIseq Yes -Pl0da1 OF ST ABM JOuIOUY “(812g 666 01 dn) ysnory) ABM +Any additional notes played will be added into the track +— existing notes are not erased while recording! -dU} [fe YORI] YORs p10991 0} ST SUOS B 9789I9 0} ABM SUG, +FAST FORWARD, REWIND, and LOCATE controls +may be used at any time to quickly access any location in +your sequence for spot-recording. To overdub a new part, +select a different track and start recording—while you +record, the first track will play in perfect sync (unless you +MUTE it, or SOLO another track). In this way, up to 32 +tracks may be overdubbed! All MIDI effects are recorded +including pitch bend, modulation, velocity, aftertouch, +sustain pedal, and program changes! -SUOS & SUTVAID +Editing -*suoT}oes poJUBMUN +To erase a wrong note, simply hold ERASE and press +the note to be erased just before it plays in the sequence— +when played back, it will be gone. Notes may also be -SAOUIOI 0} ABM SWS dU} SoyeIodo SUV ALATAaG +added, erased, or changed using the SINGLE STEP func- +tion. To overdub notes at specific points within a sequence, -“OBPLIq dy} PUB SNIOY PUOdAS dT]] Ud9MIAQ SIDA ISI +Additional Features -ay) Jo Adoo B JJasuT WYSE NOAA ‘afdwexs 10.f ‘UO JUSIN]JIP +simply use LOCATE, FAST FORWARD, or REWIND to +find the desired bar number, then start recording. -B IO aouaNbas sues OY} UI—JOY OUP 0} UOTIEIO] 9UO WOT] -$1Bq JAOUI OF NOA sMOTIe WOTIOUNS AdOO/IMASNI OULL +The INSERT/COPY function allows you to move bars +from one location to another—in the same sequence or a +different one. For example, you might insert a copy of the +first verse between the second chorus and the bridge. +DELETE BARS operates the same way to remove +unwanted sections, -‘SUIPIONAI JIVIS Udy) “OQuINU eq porisop ay} puy +Creating a Song -0} CNIMAY 10 ‘CYVM Od LSWA “AEVOOT esn Apduns +One way to create a song is to record each track all the +way through (up to 999 bars). Another way is to record +each basic section (verse, chorus, etc.) in individual +sequences, then use the CREATE SONG function to “chain” +them together. CREATE SONG will then automatically +copy all the parts into a new sequence. If desired, you can +even set the last few bars to repeat infinitely, for a fadeout. -sainjeay [PUOHIPPY +Composition Without Compromise -‘gouanbas & UTYIIM s]UTOd a1y1dads 3¥ $9100 QnPIOAO OL "UOT} --ouns dALLS ATONIS 24) Suisn pasueyo Jo ‘pasesa ‘pappe -aq osye ABUT S9]ON ‘U0 9q ]IIM 1 “yoeq podeyd uayM -—aouanbas oy] ul skeyd 71 a10J9q Isnf posers oq 0} d]0U ayy -ssaid pue ASvwug ploy Aydunis ‘jou Suomm & aseso OL +The technology you use should never be so complex that +it interferes with the creative process. That’s precisely why +the LinnSequencer is designed to let you compose, record +and edit while devoting your undivided attention to your +music. See your Linn dealer today for a demonstration! -sunipa +* Simple, easy to learn operation—the 32 character LCD display clearly guides you through all operations. If needed, the -jsesdueyo ureisoid pue ‘fepod ureysns -‘yonoplalje ‘AWOOTOA ‘UOTyeTNpow ‘pusg youd Surpnyour -pep10del are $199JJ2 TCTIN [WV iPeqqnpseao aq Aeur syoen -Ze 07 dn ‘Kem sie Uy *(foeI} JOyOUR OJOS 10 ALLAN -NOA ssofum) duAS yOaysod ul Avy [[IM Yow] ISI 93 “prooar -NOA 3[IYM—SUIPIOIA LIBIS PU YORI) TUdIOTJIP B JOaTas -*y1ed MOU B QNPIsA0 OL, “SuIps0daJ-jods 10} aouanbes mno0k -UI UOHBIO] Aue ssad0e ATYOIND 0} owt} Aue ye pasn aq AvUE -SJONUOD FLIVOOT pur ‘ANIMA ‘CYVMaYOd LSVd -{SUIPIOSAI {IY posesa JOU se So]OU SuTsTXO— -yous} 3U} OUT poppe aq JIM poteyd sajou yeuonippe Auy +HELP button displays additional explanations. -*(povesjap 10 poysn{pe oq ABW UOTIIII0D BUTUTT]) j{paqoeLI09 +* Non-destructive recording—existing notes are not erased while recording. +¢ Two FOOTSWITCH INPUTS may be assigned to remotely control many of the commonly used functions, including -2q ][IM S1OLIe Sur [fe ATUO—patey]d nod Jey Jedy ]],NOA +ERASE, REPEAT, PLAY/STOP, or LOCATE. -‘] req 0] punose yoeq sdoo] sduanbas ay] Udy AA “YOu Yor +¢ Iwo TRIGGER OUTPUTS may be programmed to output pulses at any selected note value. -§,sa0uaNbas at} O] SUIT) UI preogday [IW] INO Avy usy3 -AV'1d pue (YOON ssoid Ayduus ‘aousnbes & p1o09es OF, +© Will sync to standard LinnDrum or Linn 9000 sync tone. -g0uaNbas & SUIP10I0y] +© Utilizes ultra high-speed, 8 MHz 80186 16 bit computer internally for FAST operation. +* TEMPO may be specified in BEATS-PER-MINUTE or FRAMES-PER-BEAT at 24, 25, or 30 frames per second, -‘JONWOD s}JouNaI TeuONdGO e +(even drop frame!) -"UOTJEZIUOIYUAS OPOS UIT} FLAWS [euondo e +¢ TEMPO may be entered numerically, adjustable in tenths of a Beat-Per-Minute increments, or by tapping quarter notes -‘sou .sulddoys, noyyM sayelodo pue yoegdvyd ZuLINp S¥IOM NOLLOANNYOO ONIWILL e +on the TAP TEMPO button. -‘onqea ory AY +¢ TEMPO CHANGES may be programmed into a sequence, with smooth transitions if desired. +¢ Any TIME SIGNATURE may be used, and may be changed within a song. -pojoojes-oid & ye sajou pyoy Aue syeadas ATTeONewWO Ne UOTOUNS [WAdAY OAISNOX e -‘LSVJ SUnIpS soyeu UOTOUN ASV UA OUlN-[eal SAISNIOXY e -‘Koy B JO YONO} 941 12 CASOdSNVALL 0g ABU Syde] [Te 10 9UC e +linn +Linn Electronics, Inc. -i ASIP Jed - -S9}0U OOO‘OTT JOA SpfOy puv SpUOdeS UT SBUOS Xa[AUIOD So10}S DALIP YSIP , 74 € ISCJ-CNIN - -jSIOZISOUJUAS - -stuoydAjod of 0} dn skeyd A[snoourynuls ‘spouueYd [IW 9T JO duo 0} pousisse oq -ABUL YORI] YOR ‘syous) oruoydAjod ‘snoouelnurs 7¢ SuTeJUOS ssouUaNbas QO] OY} JO YORA e - -‘SJONUOS ATWOOT pur ‘GNIMAY ‘GaVM OA -LSVd ‘GYOOde AOLS ‘AV Td YIM Jopsocas ade} Yowsj-N[NU O} eps st UOTLISdO @ -LOPNOUT SaINjeoy s[quyIeUlss AUB S.JJ ‘OSN pue UIes] 0} o[duns A[suIzeUe JOA ‘PnJsomod APOUIOITXO -St 1] “UeIOIsNUL feUOIssajoid oY} 10 JOO} soUBULIOJIJAd pue UOTIsOduIOS 11e-dY1-JO-9}e)s B SI IONUANbDaguUT] ay - -JOps1odady soUINbIS [GTI YVAL ZE -Jgouanbaguury oy +18720 Oxnard Street, Tarzana, CA 91356 +(818) 708-8131 TELEX #298949 LINN UR \ No newline at end of file diff --git a/tests/cache/cardinal/__-l__eng__000004_ocr.png__000004_ocr_hocr__hocr__txt/hocr.bin b/tests/cache/cardinal/__-l__eng__000004_ocr.png__000004_ocr_hocr__hocr__txt/hocr.bin index 518ac636..14333b45 100644 --- a/tests/cache/cardinal/__-l__eng__000004_ocr.png__000004_ocr_hocr__hocr__txt/hocr.bin +++ b/tests/cache/cardinal/__-l__eng__000004_ocr.png__000004_ocr_hocr__hocr__txt/hocr.bin @@ -9,7 +9,7 @@ -

+

diff --git a/tests/cache/cardinal/__-l__eng__000004_ocr.png__000004_ocr_tess__pdf__txt/pdf.bin b/tests/cache/cardinal/__-l__eng__000004_ocr.png__000004_ocr_tess__pdf__txt/pdf.bin index aa26441b2e506f838b4802fb43f79ce688f05324..2f4f13b8af3ca0c06ab2c2423219ee758c773dfb 100644 GIT binary patch delta 7636 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 C2ka#P delta 9941 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 -

+

diff --git a/tests/cache/ccitt/__-l__eng__000001_ocr.png__000001_ocr_tess__pdf__txt/pdf.bin b/tests/cache/ccitt/__-l__eng__000001_ocr.png__000001_ocr_tess__pdf__txt/pdf.bin index 88bb9fc93541fbb3ab6292c2f7a1d81e0fc4f120..a064b5abaae46dc50e1810000244198f031e0801 100644 GIT binary patch delta 27 icmeAU=nmL0LygbSz{0@Lz`)eN)JWIBeDgB3R7L=HZ3of- delta 27 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 JB_%~qMhbQsAVUBE delta 612 zcmV-q0-OEAA?_Kl1qcB+lLiQ=0yQ&}u?Qo7tyRm8)i4a*&sX>dYV6oa90?)8%$)^0 zr0x(a(ClUbOTHevO>fi7T;?HEMXl20`1l+<`3YGwCIj+ERWPUJ^ZO%wx%o8T2+Y5L zmnjA{P_|eyKf=3*i6)7vsl@t$ z0%J=nZe^?kg%l$_`>LBd@D`VUOdQZ-{2P?1AQ?!LaAQZc0L}h)m*QEdRDxO3 zf(L%Gl$Te=?nNSY6_nQZ8C49;x@dw)_27d(QxXROi{ggFm}FO3UHV*mxO906$1KXFnSXh7P_maALwQa6)icrYV)83!k-e%n%24qm(jN#`bz_sh4w4Qq yF)}bWGBGtWGBYhOIFnuvSOhXSF))+553mF>I5#zuZV*ZfGC4Rk3MC~)PeuwaRVUN{ diff --git a/tests/cache/graph_ocred/__-l__eng__000001_ocr.png__000001_ocr_tess__pdf__txt/txt.bin b/tests/cache/graph_ocred/__-l__eng__000001_ocr.png__000001_ocr_tess__pdf__txt/txt.bin index d951032a..12fa5df9 100644 --- a/tests/cache/graph_ocred/__-l__eng__000001_ocr.png__000001_ocr_tess__pdf__txt/txt.bin +++ b/tests/cache/graph_ocred/__-l__eng__000001_ocr.png__000001_ocr_tess__pdf__txt/txt.bin @@ -1,20 +1,38 @@ Replacement of "creationism" with "intelligent design" + + + + + + + + + + + + + + + 120 - - - -—@— "Creation" and "creationist" -—®@-— "Intelligent design" -and "design proponent" - -Word count - - - - - - - - +100 - +Cc 80 += +© +oO 60 —@— "Creation" and "creationist" +5 —@— "Intelligent design" += and "design proponent" +40 4 +20 - +—@ © +0 e- T T T | rE © +3) 6) Ay AN y 9) oN +a i +\ oe a) \ YL S S +3 ad Ss x x x. s? +3 Ri oe a? ace Nc) os +J we % si we oe e +eS SS S S oe eS is” +oe sO o Q Q Q Q \ No newline at end of file diff --git a/tests/cache/jbig2/__-l__eng__000001_ocr.png__000001_ocr_hocr__hocr__txt/hocr.bin b/tests/cache/jbig2/__-l__eng__000001_ocr.png__000001_ocr_hocr__hocr__txt/hocr.bin index 946585c5..fe88dea6 100644 --- a/tests/cache/jbig2/__-l__eng__000001_ocr.png__000001_ocr_hocr__hocr__txt/hocr.bin +++ b/tests/cache/jbig2/__-l__eng__000001_ocr.png__000001_ocr_hocr__hocr__txt/hocr.bin @@ -9,467 +9,459 @@ -

-
-

- - 4ist - ConGREss, - } - SENATE. - { - Ex. - Doc, +

+
+

+ + 41st + CONGRESS; + | + SENATE. - - 3d - Session. - No. - 25. + + 3d + Session. + }

-
-

- - MESSAGE +

+

+ + MESSAGE

-
-

- - OF - THE +

+

+ + OF + THE

-
-

- - PRESIDENT - OF - THE - UNITED - STATES, +

+

+ + PRESIDENT + OF + THE + UNITED + STATES,

-
-

- - COMMUNICATING +

+

+ + COMMUNICATING

-
-

- - A - copy - of - regulations - for - the - consular - courts - of - the - United - States - in - Japan, +

+

+ + A + copy + of + regulations + for + the + consular + courts + of + the + United + States + in + Japan, - - decreed - and - issued - by - the - minister - of - the - United - States - in - that - country. + + decreed + and + issued + by + the + minister + of + the + United + States + in + that + country.

-
-

- - JANUARY - 27, - 1871,—Read, - referred - to - the - Committee - on - Commerce, - and - ordered - to - be +

+

+ + January + 27, + 1871,—Read, + referred + to + the + Committee + on + Commerce, + and + ordered + to + be - - printed. + + printed.

-
-

- - To - the - Senate - and - House - of - Representatives - : +

+

+ + To + the + Senate + and + House + of + Representatives + :

-

- - I - transmit - herewith, - for - the - consideration - of - Congress, - a - report - from +

+ + I + transmit + herewith, + for + the + consideration + of + Congress, + a + report + from - - the - Secretary - of - State, - and - the - papers - which - accompanied - it, - concern- + + the + Secretary + of + State, + and + the + papers + which + accompanied + it, + concern- - - ing - regulations - for - the - consular - courts - of - the - United - States - in - Japan. + + ing + regulations + for + the + consular + courts + of + the + United + States + in + Japan.

-

- - U. - 8. - GRANT. +

+ + U. + 8. + GRANT.

-

- - ‘WASHINGTON, - January - 27, - 1871. +

+ + ‘WASHINGTON, + January + 27, + 1871.

-
-

- - DEPARTMENT - OF - STATE, +

+

+ + DEPARTMENT + OF + STATE, - - Washington, - January - 26, - 1870, + + . + Washington, + January + 26, + 1870,

-

- - The - Secretary - of - State - has - the - honor - to - submit - herewith, - for - revision +

+ + The + Secretary + of + State + has + the + honor + to + submit + herewith, + for + revision - - by - Congress, - in - conformity - with - the - provisions - of - section - 6 - of - the - act + + by + Congress, + in + conformity + with + the + provisions + of + section + 6 + of + the + act - - approved - 22d - of - June, - 1860, - a - copy - of - “regulations - for - the - consular + + approved + 22d + of + June, + 1860, + a + copy + of + “regulations + for + the + consular - - courts - of - the - United - States - in - Japan,” - decreed - and - issued - by - C. - BE. + + courts + of + the + United + States + in + Japan,” + decreed + and + issued + by + C. + E. - - De - Long, - the - minister - of - the - United - States - in - that - country, - in - Septem- + + De + Long, + the + miniater + of + the + United + States + in + that + country, + in + Septem- - - ber, - 1870; - and - also - the - papers - mentioned - in - the - subjoined - list, - which, + + ber, + 1870; + and + also + the + papers + mentioned + in + the + subjoined + list, + which, - - contain - suggestions - on - the - subject - thereof. + + contain + suggestions + on + the + subject + thereof.

-

- - A - copy - of - Article - XXVI - of - the - consular - regulations - is - also - submitted, +

+ + A + copy + of + Article + XXVI + of + the + consular + regulations + is + also + submitted, - - and - the - Secretary - of - State - respectfully - suggests, - for - the - consideration + + and + the + Secretary + of + State + respectfully + suggests, + for + the + consideration - - of - Congress, - the - propriety - of - limiting - the - power - of - ministers - to - make + + of + Congress, + the + propriety + of + limiting + the + power + of + ministers + to + make - - decrees - and - regulation, - in - the - sense - in - which - it - is - limited - by - paragraph + + decrees + and + regulation, + in + the + sense + in + which + it + is + limited + by + paragraph - - 431 - of - the - article - before - named—that - is, - “to - acts - necessary - to - organize + + 431 + of + the + article + before + named—that + is, + “to + acts + necessary + to + organize - - and - give - efficiency - to - the - courts - created - by - the - act.” + + and + give + efficiency + to + the + courts + created + by + the + act.”

-

- - Respectfully - submitted. +

+ + Respectfully + submitted. + :

-

- - HAMILTON - FISH. +

+ + HAMILTON + FISH.

-
-

- - The - PRESIDENT, +

+

+ + The + PRESIDENT,

-
-

- - List - of - accompanying - papers. +

+

+ + List + of + accompanying + papers.

-
-

- - 1, - Regulations - for - the - consular - courts - of - the - United - States - in - Japan. +

+

+ + 1, + Regulations + for + the + consular + courts + of + the + United + States + in + Japan. - - 2, - Mr. - Fish - to - Mr. - De - Long, - September - 10, - 1870, + + 2. + Mr. + Fish + to + Mr. + De + Long, + September + 10, + 1870. + .

- - + +

-
-

- - - -

-
-
-

- - +

+

+ +

diff --git a/tests/cache/jbig2/__-l__eng__000001_ocr.png__000001_ocr_hocr__hocr__txt/txt.bin b/tests/cache/jbig2/__-l__eng__000001_ocr.png__000001_ocr_hocr__hocr__txt/txt.bin index a450f78e..c8e99baa 100644 --- a/tests/cache/jbig2/__-l__eng__000001_ocr.png__000001_ocr_hocr__hocr__txt/txt.bin +++ b/tests/cache/jbig2/__-l__eng__000001_ocr.png__000001_ocr_hocr__hocr__txt/txt.bin @@ -1,5 +1,5 @@ -4ist ConGREss, } SENATE. { Ex. Doc, -3d Session. No. 25. +41st CONGRESS; | SENATE. +3d Session. } MESSAGE @@ -12,7 +12,7 @@ COMMUNICATING A copy of regulations for the consular courts of the United States in Japan, decreed and issued by the minister of the United States in that country. -JANUARY 27, 1871,—Read, referred to the Committee on Commerce, and ordered to be +January 27, 1871,—Read, referred to the Committee on Commerce, and ordered to be printed. To the Senate and House of Representatives : @@ -26,13 +26,13 @@ U. 8. GRANT. ‘WASHINGTON, January 27, 1871. DEPARTMENT OF STATE, -Washington, January 26, 1870, +. Washington, January 26, 1870, The Secretary of State has the honor to submit herewith, for revision by Congress, in conformity with the provisions of section 6 of the act approved 22d of June, 1860, a copy of “regulations for the consular -courts of the United States in Japan,” decreed and issued by C. BE. -De Long, the minister of the United States in that country, in Septem- +courts of the United States in Japan,” decreed and issued by C. E. +De Long, the miniater of the United States in that country, in Septem- ber, 1870; and also the papers mentioned in the subjoined list, which, contain suggestions on the subject thereof. @@ -43,7 +43,7 @@ decrees and regulation, in the sense in which it is limited by paragraph 431 of the article before named—that is, “to acts necessary to organize and give efficiency to the courts created by the act.” -Respectfully submitted. +Respectfully submitted. : HAMILTON FISH. @@ -52,9 +52,7 @@ The PRESIDENT, List of accompanying papers. 1, Regulations for the consular courts of the United States in Japan. -2, Mr. Fish to Mr. De Long, September 10, 1870, - - +2. Mr. Fish to Mr. De Long, September 10, 1870. . diff --git a/tests/cache/jbig2/__-l__eng__000001_ocr.png__000001_ocr_tess__pdf__txt/pdf.bin b/tests/cache/jbig2/__-l__eng__000001_ocr.png__000001_ocr_tess__pdf__txt/pdf.bin index 96c1415ab53c8f6d48704535f6f399229cdc9ddc..0e48a8e747f0db2d1b5358044c9b04395d375127 100644 GIT binary patch delta 2948 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 a0Wy -
+

diff --git a/tests/cache/lichtenstein/__-l__eng__000001_ocr.png__000001_ocr_tess__pdf__txt/pdf.bin b/tests/cache/lichtenstein/__-l__eng__000001_ocr.png__000001_ocr_tess__pdf__txt/pdf.bin index 2987783d198f8c4f937889ce253f0e5471459ad5..4a0061fc0541dec1c1cae0d2d8485df358cf2e15 100644 GIT binary patch delta 27 icmZ1~wp47xCQd#>0}BH~0|QedQzKmi^UVi1QyBqpV+W1^ delta 27 icmZ1~wp47xCQd#BLn8xoBSTXo6LVbyi_Hf(QyBqps0Wq+ diff --git a/tests/cache/manifest.jsonl b/tests/cache/manifest.jsonl index f482aaaa..a0124d50 100644 --- a/tests/cache/manifest.jsonl +++ b/tests/cache/manifest.jsonl @@ -1,44 +1,43 @@ -{"tesseract_version": "4.1.1", "platform": "macOS-10.14.6-x86_64-i386-64bit", "python": "3.9.0", "argv_slug": "__-l__deu__000001_ocr.png__000001_ocr_tess__pdf__txt", "sourcefile": "resources/francais.pdf", "args": ["-l", "deu", "-c", "textonly_pdf=1", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_tess", "pdf", "txt"]} -{"tesseract_version": "4.1.1", "platform": "macOS-10.14.6-x86_64-i386-64bit", "python": "3.9.0", "argv_slug": "__-l__eng__--psm__7__000001_ocr.png__000001_ocr_hocr__hocr__txt", "sourcefile": "resources/skew.pdf", "args": ["-l", "eng", "--psm", "7", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_hocr", "hocr", "txt"]} -{"tesseract_version": "4.1.1", "platform": "macOS-10.14.6-x86_64-i386-64bit", "python": "3.9.0", "argv_slug": "__-l__eng__--psm__7__000001_ocr.png__000001_ocr_tess__pdf__txt", "sourcefile": "resources/skew.pdf", "args": ["-l", "eng", "--psm", "7", "-c", "textonly_pdf=1", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_tess", "pdf", "txt"]} -{"tesseract_version": "4.1.1", "platform": "macOS-10.14.6-x86_64-i386-64bit", "python": "3.9.0", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_hocr__hocr__txt", "sourcefile": "resources/aspect.pdf", "args": ["-l", "eng", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_hocr", "hocr", "txt"]} -{"tesseract_version": "4.1.1", "platform": "macOS-10.14.6-x86_64-i386-64bit", "python": "3.9.0", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_hocr__hocr__txt", "sourcefile": "resources/cardinal.pdf", "args": ["-l", "eng", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_hocr", "hocr", "txt"]} -{"tesseract_version": "4.1.1", "platform": "macOS-10.14.6-x86_64-i386-64bit", "python": "3.9.0", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_hocr__hocr__txt", "sourcefile": "resources/ccitt.pdf", "args": ["-l", "eng", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_hocr", "hocr", "txt"]} -{"tesseract_version": "4.1.1", "platform": "macOS-10.14.6-x86_64-i386-64bit", "python": "3.9.0", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_hocr__hocr__txt", "sourcefile": "resources/jbig2.pdf", "args": ["-l", "eng", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_hocr", "hocr", "txt"]} -{"tesseract_version": "4.1.1", "platform": "macOS-10.14.6-x86_64-i386-64bit", "python": "3.9.0", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_hocr__hocr__txt", "sourcefile": "resources/lichtenstein.pdf", "args": ["-l", "eng", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_hocr", "hocr", "txt"]} -{"tesseract_version": "4.1.1", "platform": "macOS-10.14.6-x86_64-i386-64bit", "python": "3.9.0", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_hocr__hocr__txt", "sourcefile": "resources/multipage.pdf", "args": ["-l", "eng", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_hocr", "hocr", "txt"]} -{"tesseract_version": "4.1.1", "platform": "macOS-10.14.6-x86_64-i386-64bit", "python": "3.9.0", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_hocr__hocr__txt", "sourcefile": "resources/palette.pdf", "args": ["-l", "eng", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_hocr", "hocr", "txt"]} -{"tesseract_version": "4.1.1", "platform": "macOS-10.14.6-x86_64-i386-64bit", "python": "3.9.0", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_hocr__hocr__txt", "sourcefile": "resources/skew.pdf", "args": ["-l", "eng", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_hocr", "hocr", "txt"]} -{"tesseract_version": "4.1.1", "platform": "macOS-10.14.6-x86_64-i386-64bit", "python": "3.9.0", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_tess__pdf__txt", "sourcefile": "resources/2400dpi.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_tess", "pdf", "txt"]} -{"tesseract_version": "4.1.1", "platform": "macOS-10.14.6-x86_64-i386-64bit", "python": "3.9.0", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_tess__pdf__txt", "sourcefile": "resources/3small.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_tess", "pdf", "txt"]} -{"tesseract_version": "4.1.1", "platform": "macOS-10.14.6-x86_64-i386-64bit", "python": "3.9.0", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_tess__pdf__txt", "sourcefile": "resources/aspect.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_tess", "pdf", "txt"]} -{"tesseract_version": "4.1.1", "platform": "macOS-10.14.6-x86_64-i386-64bit", "python": "3.9.0", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_tess__pdf__txt", "sourcefile": "resources/cardinal.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_tess", "pdf", "txt"]} -{"tesseract_version": "4.1.1", "platform": "macOS-10.14.6-x86_64-i386-64bit", "python": "3.9.0", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_tess__pdf__txt", "sourcefile": "resources/ccitt.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_tess", "pdf", "txt"]} -{"tesseract_version": "4.1.1", "platform": "macOS-10.14.6-x86_64-i386-64bit", "python": "3.9.0", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_tess__pdf__txt", "sourcefile": "resources/graph_ocred.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_tess", "pdf", "txt"]} -{"tesseract_version": "4.1.1", "platform": "macOS-10.14.6-x86_64-i386-64bit", "python": "3.9.0", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_tess__pdf__txt", "sourcefile": "resources/jbig2.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_tess", "pdf", "txt"]} -{"tesseract_version": "4.1.1", "platform": "macOS-10.14.6-x86_64-i386-64bit", "python": "3.9.0", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_tess__pdf__txt", "sourcefile": "resources/lichtenstein.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_tess", "pdf", "txt"]} -{"tesseract_version": "4.1.1", "platform": "macOS-10.14.6-x86_64-i386-64bit", "python": "3.9.0", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_tess__pdf__txt", "sourcefile": "resources/multipage.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_tess", "pdf", "txt"]} -{"tesseract_version": "4.1.1", "platform": "macOS-10.14.6-x86_64-i386-64bit", "python": "3.9.0", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_tess__pdf__txt", "sourcefile": "resources/palette.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_tess", "pdf", "txt"]} -{"tesseract_version": "4.1.1", "platform": "macOS-10.14.6-x86_64-i386-64bit", "python": "3.9.0", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_tess__pdf__txt", "sourcefile": "resources/poster.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_tess", "pdf", "txt"]} -{"tesseract_version": "4.1.1", "platform": "macOS-10.14.6-x86_64-i386-64bit", "python": "3.9.0", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_tess__pdf__txt", "sourcefile": "resources/skew.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_tess", "pdf", "txt"]} -{"tesseract_version": "4.1.1", "platform": "macOS-10.14.6-x86_64-i386-64bit", "python": "3.9.0", "argv_slug": "__-l__eng__000002_ocr.png__000002_ocr_hocr__hocr__txt", "sourcefile": "resources/cardinal.pdf", "args": ["-l", "eng", "$TMPDIR/000002_ocr.png", "$TMPDIR/000002_ocr_hocr", "hocr", "txt"]} -{"tesseract_version": "4.1.1", "platform": "macOS-10.14.6-x86_64-i386-64bit", "python": "3.9.0", "argv_slug": "__-l__eng__000002_ocr.png__000002_ocr_tess__pdf__txt", "sourcefile": "resources/3small.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "$TMPDIR/000002_ocr.png", "$TMPDIR/000002_ocr_tess", "pdf", "txt"]} -{"tesseract_version": "4.1.1", "platform": "macOS-10.14.6-x86_64-i386-64bit", "python": "3.9.0", "argv_slug": "__-l__eng__000002_ocr.png__000002_ocr_tess__pdf__txt", "sourcefile": "resources/cardinal.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "$TMPDIR/000002_ocr.png", "$TMPDIR/000002_ocr_tess", "pdf", "txt"]} -{"tesseract_version": "4.1.1", "platform": "macOS-10.14.6-x86_64-i386-64bit", "python": "3.9.0", "argv_slug": "__-l__eng__000003_ocr.png__000003_ocr_hocr__hocr__txt", "sourcefile": "resources/cardinal.pdf", "args": ["-l", "eng", "$TMPDIR/000003_ocr.png", "$TMPDIR/000003_ocr_hocr", "hocr", "txt"]} -{"tesseract_version": "4.1.1", "platform": "macOS-10.14.6-x86_64-i386-64bit", "python": "3.9.0", "argv_slug": "__-l__eng__000003_ocr.png__000003_ocr_hocr__hocr__txt", "sourcefile": "resources/multipage.pdf", "args": ["-l", "eng", "$TMPDIR/000003_ocr.png", "$TMPDIR/000003_ocr_hocr", "hocr", "txt"]} -{"tesseract_version": "4.1.1", "platform": "macOS-10.14.6-x86_64-i386-64bit", "python": "3.9.0", "argv_slug": "__-l__eng__000003_ocr.png__000003_ocr_tess__pdf__txt", "sourcefile": "resources/3small.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "$TMPDIR/000003_ocr.png", "$TMPDIR/000003_ocr_tess", "pdf", "txt"]} -{"tesseract_version": "4.1.1", "platform": "macOS-10.14.6-x86_64-i386-64bit", "python": "3.9.0", "argv_slug": "__-l__eng__000003_ocr.png__000003_ocr_tess__pdf__txt", "sourcefile": "resources/cardinal.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "$TMPDIR/000003_ocr.png", "$TMPDIR/000003_ocr_tess", "pdf", "txt"]} -{"tesseract_version": "4.1.1", "platform": "macOS-10.14.6-x86_64-i386-64bit", "python": "3.9.0", "argv_slug": "__-l__eng__000003_ocr.png__000003_ocr_tess__pdf__txt", "sourcefile": "resources/multipage.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "$TMPDIR/000003_ocr.png", "$TMPDIR/000003_ocr_tess", "pdf", "txt"]} -{"tesseract_version": "4.1.1", "platform": "macOS-10.14.6-x86_64-i386-64bit", "python": "3.9.0", "argv_slug": "__-l__eng__000004_ocr.png__000004_ocr_hocr__hocr__txt", "sourcefile": "resources/cardinal.pdf", "args": ["-l", "eng", "$TMPDIR/000004_ocr.png", "$TMPDIR/000004_ocr_hocr", "hocr", "txt"]} -{"tesseract_version": "4.1.1", "platform": "macOS-10.14.6-x86_64-i386-64bit", "python": "3.9.0", "argv_slug": "__-l__eng__000004_ocr.png__000004_ocr_hocr__hocr__txt", "sourcefile": "resources/multipage.pdf", "args": ["-l", "eng", "$TMPDIR/000004_ocr.png", "$TMPDIR/000004_ocr_hocr", "hocr", "txt"]} -{"tesseract_version": "4.1.1", "platform": "macOS-10.14.6-x86_64-i386-64bit", "python": "3.9.0", "argv_slug": "__-l__eng__000004_ocr.png__000004_ocr_tess__pdf__txt", "sourcefile": "resources/cardinal.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "$TMPDIR/000004_ocr.png", "$TMPDIR/000004_ocr_tess", "pdf", "txt"]} -{"tesseract_version": "4.1.1", "platform": "macOS-10.14.6-x86_64-i386-64bit", "python": "3.9.0", "argv_slug": "__-l__eng__000004_ocr.png__000004_ocr_tess__pdf__txt", "sourcefile": "resources/multipage.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "$TMPDIR/000004_ocr.png", "$TMPDIR/000004_ocr_tess", "pdf", "txt"]} -{"tesseract_version": "4.1.1", "platform": "macOS-10.14.6-x86_64-i386-64bit", "python": "3.9.0", "argv_slug": "__-l__eng__000005_ocr.png__000005_ocr_hocr__hocr__txt", "sourcefile": "resources/multipage.pdf", "args": ["-l", "eng", "$TMPDIR/000005_ocr.png", "$TMPDIR/000005_ocr_hocr", "hocr", "txt"]} -{"tesseract_version": "4.1.1", "platform": "macOS-10.14.6-x86_64-i386-64bit", "python": "3.9.0", "argv_slug": "__-l__eng__000005_ocr.png__000005_ocr_tess__pdf__txt", "sourcefile": "resources/multipage.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "$TMPDIR/000005_ocr.png", "$TMPDIR/000005_ocr_tess", "pdf", "txt"]} -{"tesseract_version": "4.1.1", "platform": "macOS-10.14.6-x86_64-i386-64bit", "python": "3.9.0", "argv_slug": "__-l__eng__000006_ocr.png__000006_ocr_hocr__hocr__txt", "sourcefile": "resources/multipage.pdf", "args": ["-l", "eng", "$TMPDIR/000006_ocr.png", "$TMPDIR/000006_ocr_hocr", "hocr", "txt"]} -{"tesseract_version": "4.1.1", "platform": "macOS-10.14.6-x86_64-i386-64bit", "python": "3.9.0", "argv_slug": "__-l__eng__000006_ocr.png__000006_ocr_tess__pdf__txt", "sourcefile": "resources/multipage.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "$TMPDIR/000006_ocr.png", "$TMPDIR/000006_ocr_tess", "pdf", "txt"]} -{"tesseract_version": "4.1.1", "platform": "macOS-10.14.6-x86_64-i386-64bit", "python": "3.9.0", "argv_slug": "__-l__osd__--psm__0__000001_rasterize_preview.jpg__stdout", "sourcefile": "resources/cardinal.pdf", "args": ["-l", "osd", "--psm", "0", "$TMPDIR/000001_rasterize_preview.jpg", "stdout"]} -{"tesseract_version": "4.1.1", "platform": "macOS-10.14.6-x86_64-i386-64bit", "python": "3.9.0", "argv_slug": "__-l__osd__--psm__0__000001_rasterize_preview.jpg__stdout", "sourcefile": "resources/poster.pdf", "args": ["-l", "osd", "--psm", "0", "$TMPDIR/000001_rasterize_preview.jpg", "stdout"]} -{"tesseract_version": "4.1.1", "platform": "macOS-10.14.6-x86_64-i386-64bit", "python": "3.9.0", "argv_slug": "__-l__osd__--psm__0__000002_rasterize_preview.jpg__stdout", "sourcefile": "resources/cardinal.pdf", "args": ["-l", "osd", "--psm", "0", "$TMPDIR/000002_rasterize_preview.jpg", "stdout"]} -{"tesseract_version": "4.1.1", "platform": "macOS-10.14.6-x86_64-i386-64bit", "python": "3.9.0", "argv_slug": "__-l__osd__--psm__0__000003_rasterize_preview.jpg__stdout", "sourcefile": "resources/cardinal.pdf", "args": ["-l", "osd", "--psm", "0", "$TMPDIR/000003_rasterize_preview.jpg", "stdout"]} -{"tesseract_version": "4.1.1", "platform": "macOS-10.14.6-x86_64-i386-64bit", "python": "3.9.0", "argv_slug": "__-l__osd__--psm__0__000004_rasterize_preview.jpg__stdout", "sourcefile": "resources/cardinal.pdf", "args": ["-l", "osd", "--psm", "0", "$TMPDIR/000004_rasterize_preview.jpg", "stdout"]} \ No newline at end of file +{"tesseract_version": "4.1.1", "platform": "Linux-5.11.0-25-generic-x86_64-with-glibc2.33", "python": "3.9.5", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_tess__pdf__txt", "sourcefile": "resources/ccitt.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_tess", "pdf", "txt"]} +{"tesseract_version": "4.1.1", "platform": "Linux-5.11.0-25-generic-x86_64-with-glibc2.33", "python": "3.9.5", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_hocr__hocr__txt", "sourcefile": "resources/skew.pdf", "args": ["-l", "eng", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_hocr", "hocr", "txt"]} +{"tesseract_version": "4.1.1", "platform": "Linux-5.11.0-25-generic-x86_64-with-glibc2.33", "python": "3.9.5", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_tess__pdf__txt", "sourcefile": "resources/skew.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_tess", "pdf", "txt"]} +{"tesseract_version": "4.1.1", "platform": "Linux-5.11.0-25-generic-x86_64-with-glibc2.33", "python": "3.9.5", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_tess__pdf__txt", "sourcefile": "resources/graph_ocred.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_tess", "pdf", "txt"]} +{"tesseract_version": "4.1.1", "platform": "Linux-5.11.0-25-generic-x86_64-with-glibc2.33", "python": "3.9.5", "argv_slug": "__-l__eng__000003_ocr.png__000003_ocr_hocr__hocr__txt", "sourcefile": "resources/multipage.pdf", "args": ["-l", "eng", "$TMPDIR/000003_ocr.png", "$TMPDIR/000003_ocr_hocr", "hocr", "txt"]} +{"tesseract_version": "4.1.1", "platform": "Linux-5.11.0-25-generic-x86_64-with-glibc2.33", "python": "3.9.5", "argv_slug": "__-l__eng__000004_ocr.png__000004_ocr_hocr__hocr__txt", "sourcefile": "resources/multipage.pdf", "args": ["-l", "eng", "$TMPDIR/000004_ocr.png", "$TMPDIR/000004_ocr_hocr", "hocr", "txt"]} +{"tesseract_version": "4.1.1", "platform": "Linux-5.11.0-25-generic-x86_64-with-glibc2.33", "python": "3.9.5", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_hocr__hocr__txt", "sourcefile": "resources/multipage.pdf", "args": ["-l", "eng", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_hocr", "hocr", "txt"]} +{"tesseract_version": "4.1.1", "platform": "Linux-5.11.0-25-generic-x86_64-with-glibc2.33", "python": "3.9.5", "argv_slug": "__-l__eng__000005_ocr.png__000005_ocr_hocr__hocr__txt", "sourcefile": "resources/multipage.pdf", "args": ["-l", "eng", "$TMPDIR/000005_ocr.png", "$TMPDIR/000005_ocr_hocr", "hocr", "txt"]} +{"tesseract_version": "4.1.1", "platform": "Linux-5.11.0-25-generic-x86_64-with-glibc2.33", "python": "3.9.5", "argv_slug": "__-l__eng__000006_ocr.png__000006_ocr_hocr__hocr__txt", "sourcefile": "resources/multipage.pdf", "args": ["-l", "eng", "$TMPDIR/000006_ocr.png", "$TMPDIR/000006_ocr_hocr", "hocr", "txt"]} +{"tesseract_version": "4.1.1", "platform": "Linux-5.11.0-25-generic-x86_64-with-glibc2.33", "python": "3.9.5", "argv_slug": "__-l__eng__000003_ocr.png__000003_ocr_tess__pdf__txt", "sourcefile": "resources/multipage.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "$TMPDIR/000003_ocr.png", "$TMPDIR/000003_ocr_tess", "pdf", "txt"]} +{"tesseract_version": "4.1.1", "platform": "Linux-5.11.0-25-generic-x86_64-with-glibc2.33", "python": "3.9.5", "argv_slug": "__-l__eng__000004_ocr.png__000004_ocr_tess__pdf__txt", "sourcefile": "resources/multipage.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "$TMPDIR/000004_ocr.png", "$TMPDIR/000004_ocr_tess", "pdf", "txt"]} +{"tesseract_version": "4.1.1", "platform": "Linux-5.11.0-25-generic-x86_64-with-glibc2.33", "python": "3.9.5", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_tess__pdf__txt", "sourcefile": "resources/multipage.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_tess", "pdf", "txt"]} +{"tesseract_version": "4.1.1", "platform": "Linux-5.11.0-25-generic-x86_64-with-glibc2.33", "python": "3.9.5", "argv_slug": "__-l__eng__000005_ocr.png__000005_ocr_tess__pdf__txt", "sourcefile": "resources/multipage.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "$TMPDIR/000005_ocr.png", "$TMPDIR/000005_ocr_tess", "pdf", "txt"]} +{"tesseract_version": "4.1.1", "platform": "Linux-5.11.0-25-generic-x86_64-with-glibc2.33", "python": "3.9.5", "argv_slug": "__-l__eng__000006_ocr.png__000006_ocr_tess__pdf__txt", "sourcefile": "resources/multipage.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "$TMPDIR/000006_ocr.png", "$TMPDIR/000006_ocr_tess", "pdf", "txt"]} +{"tesseract_version": "4.1.1", "platform": "Linux-5.11.0-25-generic-x86_64-with-glibc2.33", "python": "3.9.5", "argv_slug": "__-l__eng__--psm__7__000001_ocr.png__000001_ocr_hocr__hocr__txt", "sourcefile": "resources/skew.pdf", "args": ["-l", "eng", "--psm", "7", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_hocr", "hocr", "txt"]} +{"tesseract_version": "4.1.1", "platform": "Linux-5.11.0-25-generic-x86_64-with-glibc2.33", "python": "3.9.5", "argv_slug": "__-l__eng__--psm__7__000001_ocr.png__000001_ocr_tess__pdf__txt", "sourcefile": "resources/skew.pdf", "args": ["-l", "eng", "--psm", "7", "-c", "textonly_pdf=1", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_tess", "pdf", "txt"]} +{"tesseract_version": "4.1.1", "platform": "Linux-5.11.0-25-generic-x86_64-with-glibc2.33", "python": "3.9.5", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_hocr__hocr__txt", "sourcefile": "resources/jbig2.pdf", "args": ["-l", "eng", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_hocr", "hocr", "txt"]} +{"tesseract_version": "4.1.1", "platform": "Linux-5.11.0-25-generic-x86_64-with-glibc2.33", "python": "3.9.5", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_tess__pdf__txt", "sourcefile": "resources/2400dpi.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_tess", "pdf", "txt"]} +{"tesseract_version": "4.1.1", "platform": "Linux-5.11.0-25-generic-x86_64-with-glibc2.33", "python": "3.9.5", "argv_slug": "__-l__eng__000002_ocr.png__000002_ocr_tess__pdf__txt", "sourcefile": "resources/3small.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "$TMPDIR/000002_ocr.png", "$TMPDIR/000002_ocr_tess", "pdf", "txt"]} +{"tesseract_version": "4.1.1", "platform": "Linux-5.11.0-25-generic-x86_64-with-glibc2.33", "python": "3.9.5", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_tess__pdf__txt", "sourcefile": "resources/3small.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_tess", "pdf", "txt"]} +{"tesseract_version": "4.1.1", "platform": "Linux-5.11.0-25-generic-x86_64-with-glibc2.33", "python": "3.9.5", "argv_slug": "__-l__eng__000003_ocr.png__000003_ocr_tess__pdf__txt", "sourcefile": "resources/3small.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "$TMPDIR/000003_ocr.png", "$TMPDIR/000003_ocr_tess", "pdf", "txt"]} +{"tesseract_version": "4.1.1", "platform": "Linux-5.11.0-25-generic-x86_64-with-glibc2.33", "python": "3.9.5", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_tess__pdf__txt", "sourcefile": "resources/palette.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_tess", "pdf", "txt"]} +{"tesseract_version": "4.1.1", "platform": "Linux-5.11.0-25-generic-x86_64-with-glibc2.33", "python": "3.9.5", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_tess__pdf__txt", "sourcefile": "resources/jbig2.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_tess", "pdf", "txt"]} +{"tesseract_version": "4.1.1", "platform": "Linux-5.11.0-25-generic-x86_64-with-glibc2.33", "python": "3.9.5", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_tess__pdf__txt", "sourcefile": "resources/lichtenstein.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_tess", "pdf", "txt"]} +{"tesseract_version": "4.1.1", "platform": "Linux-5.11.0-25-generic-x86_64-with-glibc2.33", "python": "3.9.5", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_hocr__hocr__txt", "sourcefile": "resources/palette.pdf", "args": ["-l", "eng", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_hocr", "hocr", "txt"]} +{"tesseract_version": "4.1.1", "platform": "Linux-5.11.0-25-generic-x86_64-with-glibc2.33", "python": "3.9.5", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_hocr__hocr__txt", "sourcefile": "resources/ccitt.pdf", "args": ["-l", "eng", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_hocr", "hocr", "txt"]} +{"tesseract_version": "4.1.1", "platform": "Linux-5.11.0-25-generic-x86_64-with-glibc2.33", "python": "3.9.5", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_hocr__hocr__txt", "sourcefile": "resources/lichtenstein.pdf", "args": ["-l", "eng", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_hocr", "hocr", "txt"]} +{"tesseract_version": "4.1.1", "platform": "Linux-5.11.0-25-generic-x86_64-with-glibc2.33", "python": "3.9.5", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_hocr__hocr__txt", "sourcefile": "resources/aspect.pdf", "args": ["-l", "eng", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_hocr", "hocr", "txt"]} +{"tesseract_version": "4.1.1", "platform": "Linux-5.11.0-25-generic-x86_64-with-glibc2.33", "python": "3.9.5", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_tess__pdf__txt", "sourcefile": "resources/aspect.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_tess", "pdf", "txt"]} +{"tesseract_version": "4.1.1", "platform": "Linux-5.11.0-25-generic-x86_64-with-glibc2.33", "python": "3.9.5", "argv_slug": "__-l__osd__--psm__0__000004_rasterize_preview.jpg__stdout", "sourcefile": "resources/cardinal.pdf", "args": ["-l", "osd", "--psm", "0", "$TMPDIR/000004_rasterize_preview.jpg", "stdout"]} +{"tesseract_version": "4.1.1", "platform": "Linux-5.11.0-25-generic-x86_64-with-glibc2.33", "python": "3.9.5", "argv_slug": "__-l__osd__--psm__0__000001_rasterize_preview.jpg__stdout", "sourcefile": "resources/cardinal.pdf", "args": ["-l", "osd", "--psm", "0", "$TMPDIR/000001_rasterize_preview.jpg", "stdout"]} +{"tesseract_version": "4.1.1", "platform": "Linux-5.11.0-25-generic-x86_64-with-glibc2.33", "python": "3.9.5", "argv_slug": "__-l__osd__--psm__0__000002_rasterize_preview.jpg__stdout", "sourcefile": "resources/cardinal.pdf", "args": ["-l", "osd", "--psm", "0", "$TMPDIR/000002_rasterize_preview.jpg", "stdout"]} +{"tesseract_version": "4.1.1", "platform": "Linux-5.11.0-25-generic-x86_64-with-glibc2.33", "python": "3.9.5", "argv_slug": "__-l__osd__--psm__0__000003_rasterize_preview.jpg__stdout", "sourcefile": "resources/cardinal.pdf", "args": ["-l", "osd", "--psm", "0", "$TMPDIR/000003_rasterize_preview.jpg", "stdout"]} +{"tesseract_version": "4.1.1", "platform": "Linux-5.11.0-25-generic-x86_64-with-glibc2.33", "python": "3.9.5", "argv_slug": "__-l__eng__000004_ocr.png__000004_ocr_tess__pdf__txt", "sourcefile": "resources/cardinal.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "$TMPDIR/000004_ocr.png", "$TMPDIR/000004_ocr_tess", "pdf", "txt"]} +{"tesseract_version": "4.1.1", "platform": "Linux-5.11.0-25-generic-x86_64-with-glibc2.33", "python": "3.9.5", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_tess__pdf__txt", "sourcefile": "resources/cardinal.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_tess", "pdf", "txt"]} +{"tesseract_version": "4.1.1", "platform": "Linux-5.11.0-25-generic-x86_64-with-glibc2.33", "python": "3.9.5", "argv_slug": "__-l__eng__000003_ocr.png__000003_ocr_tess__pdf__txt", "sourcefile": "resources/cardinal.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "$TMPDIR/000003_ocr.png", "$TMPDIR/000003_ocr_tess", "pdf", "txt"]} +{"tesseract_version": "4.1.1", "platform": "Linux-5.11.0-25-generic-x86_64-with-glibc2.33", "python": "3.9.5", "argv_slug": "__-l__eng__000002_ocr.png__000002_ocr_tess__pdf__txt", "sourcefile": "resources/cardinal.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "$TMPDIR/000002_ocr.png", "$TMPDIR/000002_ocr_tess", "pdf", "txt"]} +{"tesseract_version": "4.1.1", "platform": "Linux-5.11.0-25-generic-x86_64-with-glibc2.33", "python": "3.9.5", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_tess__pdf__txt", "sourcefile": "resources/poster.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_tess", "pdf", "txt"]} +{"tesseract_version": "4.1.1", "platform": "Linux-5.11.0-25-generic-x86_64-with-glibc2.33", "python": "3.9.5", "argv_slug": "__-l__osd__--psm__0__000001_rasterize_preview.jpg__stdout", "sourcefile": "resources/poster.pdf", "args": ["-l", "osd", "--psm", "0", "$TMPDIR/000001_rasterize_preview.jpg", "stdout"]} +{"tesseract_version": "4.1.1", "platform": "Linux-5.11.0-25-generic-x86_64-with-glibc2.33", "python": "3.9.5", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_hocr__hocr__txt", "sourcefile": "resources/cardinal.pdf", "args": ["-l", "eng", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_hocr", "hocr", "txt"]} +{"tesseract_version": "4.1.1", "platform": "Linux-5.11.0-25-generic-x86_64-with-glibc2.33", "python": "3.9.5", "argv_slug": "__-l__eng__000002_ocr.png__000002_ocr_hocr__hocr__txt", "sourcefile": "resources/cardinal.pdf", "args": ["-l", "eng", "$TMPDIR/000002_ocr.png", "$TMPDIR/000002_ocr_hocr", "hocr", "txt"]} +{"tesseract_version": "4.1.1", "platform": "Linux-5.11.0-25-generic-x86_64-with-glibc2.33", "python": "3.9.5", "argv_slug": "__-l__eng__000004_ocr.png__000004_ocr_hocr__hocr__txt", "sourcefile": "resources/cardinal.pdf", "args": ["-l", "eng", "$TMPDIR/000004_ocr.png", "$TMPDIR/000004_ocr_hocr", "hocr", "txt"]} +{"tesseract_version": "4.1.1", "platform": "Linux-5.11.0-25-generic-x86_64-with-glibc2.33", "python": "3.9.5", "argv_slug": "__-l__eng__000003_ocr.png__000003_ocr_hocr__hocr__txt", "sourcefile": "resources/cardinal.pdf", "args": ["-l", "eng", "$TMPDIR/000003_ocr.png", "$TMPDIR/000003_ocr_hocr", "hocr", "txt"]} diff --git a/tests/cache/multipage/__-l__eng__000001_ocr.png__000001_ocr_hocr__hocr__txt/hocr.bin b/tests/cache/multipage/__-l__eng__000001_ocr.png__000001_ocr_hocr__hocr__txt/hocr.bin index 38166827..59efd297 100644 --- a/tests/cache/multipage/__-l__eng__000001_ocr.png__000001_ocr_hocr__hocr__txt/hocr.bin +++ b/tests/cache/multipage/__-l__eng__000001_ocr.png__000001_ocr_hocr__hocr__txt/hocr.bin @@ -9,7 +9,7 @@ -

+

@@ -19,22 +19,22 @@

- + THEY - TIP-TOED - ALONG. + TIP-TOED + ALONG.

-
+

- - ee + + al . - - Se - We + + SE + We went tip-toeing along @@ -46,23 +46,23 @@

- + the trees back towards the - end + end of the - + widow’s garden, stooping down so - as + as the @@ -70,82 +70,82 @@ wouldn’t scrape our - heads. + heads. When we - was - passing + was + passing by the kitchen - + I fell over - a - root - and - made - a + a + root + and + made + a noise. - - We - scrouched + + We + crouched down - and - laid - still. + and + laid + still. - Miss - Watson’s - big - nigger, + Miss + Watson’s + big + nigger, named - + Jim, was setting in the - kitchen - door - ; + kitchen + door + ; we - could + could see him pretty clear, because - + there was - a - light + a + light behind - him. - He + him. + He - got + got up - and - stretched + and + stretched his neck out - about + about a minute, listening. @@ -158,8 +158,8 @@

- - “Who + + “Who dah?”

@@ -170,15 +170,15 @@ listened some more; - then - he + then + he - come - tip-toeing - down - and_ - stood + come + tip-toeing + down + and_ + stood right @@ -195,10 +195,10 @@ Well, likely it - was - min- + was + min- - + utes and minutes @@ -207,22 +207,22 @@ warn’t a - - sound, + + sound, and we - all - there - so - close + all + there + so + close - together. - There + together. + There was - a + a place - on + on my @@ -240,14 +240,14 @@

dasn’t - scratch + scratch it; and then - my - ear + my + ear begun - to + to itch; and next @@ -262,14 +262,14 @@ shoulders. Seemed like - I’d + I’d die if - I + I couldn’t scratch. Well, - I’ve + I’ve

@@ -289,13 +289,13 @@

- Tf + If you are with the - quality, - or + quality, + or at a @@ -317,7 +317,7 @@ sleepy—if you are - anywheres + anywheres

diff --git a/tests/cache/multipage/__-l__eng__000001_ocr.png__000001_ocr_hocr__hocr__txt/stderr.bin b/tests/cache/multipage/__-l__eng__000001_ocr.png__000001_ocr_hocr__hocr__txt/stderr.bin index 25cb8633..e2b14846 100644 --- a/tests/cache/multipage/__-l__eng__000001_ocr.png__000001_ocr_hocr__hocr__txt/stderr.bin +++ b/tests/cache/multipage/__-l__eng__000001_ocr.png__000001_ocr_hocr__hocr__txt/stderr.bin @@ -1,2 +1,2 @@ Tesseract Open Source OCR Engine v4.1.1 with Leptonica -Detected 60 diacritics +Detected 98 diacritics diff --git a/tests/cache/multipage/__-l__eng__000001_ocr.png__000001_ocr_hocr__hocr__txt/txt.bin b/tests/cache/multipage/__-l__eng__000001_ocr.png__000001_ocr_hocr__hocr__txt/txt.bin index 1f008c8e..90514d84 100644 --- a/tests/cache/multipage/__-l__eng__000001_ocr.png__000001_ocr_hocr__hocr__txt/txt.bin +++ b/tests/cache/multipage/__-l__eng__000001_ocr.png__000001_ocr_hocr__hocr__txt/txt.bin @@ -2,15 +2,15 @@ THEY TIP-TOED ALONG. -ee . -Se We went tip-toeing along a path amongst +al . +SE We went tip-toeing along a path amongst the trees back towards the end of the widow’s garden, stooping down so as the branches wouldn’t scrape our heads. When we was passing by the kitchen I fell over a root and made a noise. -We scrouched down and laid still. +We crouched down and laid still. Miss Watson’s big nigger, named Jim, was setting in the kitchen door ; we could see him pretty clear, because @@ -35,7 +35,7 @@ tween my shoulders. Seemed like I’d die if I couldn’t scratch. Well, I’ve noticed that thing plenty of times since. -Tf you are with the quality, or at a +If you are with the quality, or at a funeral, or trying to go to sleep when you ain’t sleepy—if you are anywheres \ No newline at end of file diff --git a/tests/cache/multipage/__-l__eng__000001_ocr.png__000001_ocr_tess__pdf__txt/pdf.bin b/tests/cache/multipage/__-l__eng__000001_ocr.png__000001_ocr_tess__pdf__txt/pdf.bin index 66a303254a50005811f54500d345c73b7a249bb7..e5061977d171c3ffbf115b7b185127735fcdbcf0 100644 GIT binary patch delta 2526 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 diff --git a/tests/cache/multipage/__-l__eng__000001_ocr.png__000001_ocr_tess__pdf__txt/stderr.bin b/tests/cache/multipage/__-l__eng__000001_ocr.png__000001_ocr_tess__pdf__txt/stderr.bin index 25cb8633..e2b14846 100644 --- a/tests/cache/multipage/__-l__eng__000001_ocr.png__000001_ocr_tess__pdf__txt/stderr.bin +++ b/tests/cache/multipage/__-l__eng__000001_ocr.png__000001_ocr_tess__pdf__txt/stderr.bin @@ -1,2 +1,2 @@ Tesseract Open Source OCR Engine v4.1.1 with Leptonica -Detected 60 diacritics +Detected 98 diacritics diff --git a/tests/cache/multipage/__-l__eng__000001_ocr.png__000001_ocr_tess__pdf__txt/txt.bin b/tests/cache/multipage/__-l__eng__000001_ocr.png__000001_ocr_tess__pdf__txt/txt.bin index 1f008c8e..90514d84 100644 --- a/tests/cache/multipage/__-l__eng__000001_ocr.png__000001_ocr_tess__pdf__txt/txt.bin +++ b/tests/cache/multipage/__-l__eng__000001_ocr.png__000001_ocr_tess__pdf__txt/txt.bin @@ -2,15 +2,15 @@ THEY TIP-TOED ALONG. -ee . -Se We went tip-toeing along a path amongst +al . +SE We went tip-toeing along a path amongst the trees back towards the end of the widow’s garden, stooping down so as the branches wouldn’t scrape our heads. When we was passing by the kitchen I fell over a root and made a noise. -We scrouched down and laid still. +We crouched down and laid still. Miss Watson’s big nigger, named Jim, was setting in the kitchen door ; we could see him pretty clear, because @@ -35,7 +35,7 @@ tween my shoulders. Seemed like I’d die if I couldn’t scratch. Well, I’ve noticed that thing plenty of times since. -Tf you are with the quality, or at a +If you are with the quality, or at a funeral, or trying to go to sleep when you ain’t sleepy—if you are anywheres \ No newline at end of file diff --git a/tests/cache/multipage/__-l__eng__000003_ocr.png__000003_ocr_hocr__hocr__txt/hocr.bin b/tests/cache/multipage/__-l__eng__000003_ocr.png__000003_ocr_hocr__hocr__txt/hocr.bin index 2c989a38..1a2376ad 100644 --- a/tests/cache/multipage/__-l__eng__000003_ocr.png__000003_ocr_hocr__hocr__txt/hocr.bin +++ b/tests/cache/multipage/__-l__eng__000003_ocr.png__000003_ocr_hocr__hocr__txt/hocr.bin @@ -9,16 +9,16 @@ -
+

- + Replacement - of - "creationism" + of + "creationism" with - "intelligent - design" + "intelligent + design"

@@ -76,21 +76,21 @@

- + 120 - + 100 - - + Cc 80 - + > - + 5 @@ -113,15 +113,15 @@ "design proponent" - + 40 - - + 20 - - + —@— —@® @@ -135,7 +135,7 @@ w ° - + gp) ee) 0 @@ -172,48 +172,49 @@ & s? - - ge - ee - Oo - ss - Ss - Ne - qs + + 30 + Ce + o + Ss + Ss + ° + se - - G - % - S - S - © - S + + © + os + S + S + 2 + S - - Ros - % - se - se - oe - AN + + Ro + % + & + NS + se + oe + AN - Ss - 3s - S - Ss - Ss - Ss - Ss + Ss + 3s + S + Ss + Ss + Ss + Ss - ow? - \O - Xo) - R - g - g - Q + ow? + \O + Xo) + R + g + g + Q

diff --git a/tests/cache/multipage/__-l__eng__000003_ocr.png__000003_ocr_hocr__hocr__txt/txt.bin b/tests/cache/multipage/__-l__eng__000003_ocr.png__000003_ocr_hocr__hocr__txt/txt.bin index 97c52199..c5445aab 100644 --- a/tests/cache/multipage/__-l__eng__000003_ocr.png__000003_ocr_hocr__hocr__txt/txt.bin +++ b/tests/cache/multipage/__-l__eng__000003_ocr.png__000003_ocr_hocr__hocr__txt/txt.bin @@ -31,9 +31,9 @@ gp) ee) 0 oN g\ gD) op) oO NC) NC) LN eo N N S os o* vs ws os os cs) Re ss & ow x & s? -ge ee Oo ss Ss Ne qs -G % S S © S -Ros % se se oe AN +30 Ce o Ss Ss ° se +© os S S 2 S +Ro % & NS se oe AN Ss 3s S Ss Ss Ss Ss ow? \O Xo) R g g Q \ No newline at end of file diff --git a/tests/cache/multipage/__-l__eng__000003_ocr.png__000003_ocr_tess__pdf__txt/pdf.bin b/tests/cache/multipage/__-l__eng__000003_ocr.png__000003_ocr_tess__pdf__txt/pdf.bin index 82d5f1418bb33cb05dd4f240e0d0970383075efa..9d2babe665c74f2920abc8df0c63c18619a67eb8 100644 GIT binary patch delta 1644 zcmV-y29x>2A@?D$um}P+IFsQ4B7dz|$&MU1488j+>IYh(L{bY71hB_Fhui}6EyyW` zZ$2c)e0@ktRa&aMJ>vv}MlVBATpl0E`fFoi7xD)3M}&@pef;y=_S=U~w-318{@%R( zf6L9fe~%ipmcrcvqU<Gw?b>-mI1lVHZGTvT^I=F{(A)(e?2#5#-_u1-YzhQw<_Ls_1d_~zMc=A- z^FBJ-E=Z1}dK$sSKzB|(v4lOUJ6^DmCri{FvBYuC@A0d+|4*4`7qh%&2ctH&fK!U@ z9m+71D^Zjs2YFD2s*L=*k|!aPVpm`3j01>k2A-(OrU?tU5m6pgm4D(Ex`!wwh@uc@ z48V4wq)HmnXL(rUB{AH{L7+l0q?SV|W!-k<+VL7j}juN?_IuVneFPC;>2woV+3!+8s-=aQPoTuPSh#7AJtfnc4Tpax5p_J7fYDq^dr3>N)YzU z`gisL^)PF=FP<9={Rp73Y`hQ3E$>WBfjKFU%sM)y)lNMs!GABkw?a$jYa1E%G>$P@ zo#UV#L5^DqmbFt7;&ysr%Zy}Q^*f>RlvnXC!?X8LxD>6D=gq=!CgZ){}NA?a5 z$UlHvNQO7%43Cyw*5Y4J0eoDz2}5y!a=9>A!QpOe;4*o|ICwaECzF;ME!QOL+FsI# zI1DJf*qGQMqko>r?Ck_h+Ia%I4n9GJQ#1n159O*9m;azLq!TZxSTLPh`nmg+7TFBu zQ^MK_f_NEwwSQVV2g$4xE#55p$7Fk(EuxIo++&z(4TLhrQD)J-`OzfJ^|eZey_o4i zCh_1Z?scejN7u#Ivy_;}FEdf*MhQ%kmPwLmu`c%cc7Kt6z2mvXPvDc~U1qSb!j+0k zDbF6fu9@|ktyn8KO0tF)rtWGBTTP7FQXCQAe29f3+D#Qi!G&QGrk5qSx-ek21KCi4 zFdeYmzRpQQw)*2*w<$~}jrRLuc#aj%NdoQZzOY4DK&7Tg8$iGlLW_*QzTd^;05!BI zo)@-+vwunZVkL0_m{S%$&KlDoiv8jgSFKSe<0={_ty;3PXrFqxoD5iP-S5Zek`_Aw zi|hv)@CBPiHl;Mg1l#E{dm!I5Ra7cNN=Hk>?69xW9X7D@4_7jgd4>lhBwXELktDmK z;!HN{i84c+rfO= zE5iU&4b54Sz1gQ)+=e8~kA;olE7wFrP1FkQ2c{Mz2`tgf;K&P?r`17?r^LL*Q<}R! zZGR%UJMpp1?3642<=A%m$Au$X>wZVBHqirmQFvKcN`)g!!3--ekME1v6JWj-ONrr>EbfRAx z(HgG3xe6rUNuniIyP&jhKa<=U^KJOZGJls2O~>EqC6KitT)aYb+GFeTI}gz3GLZiV z9($Y=5rwIOPo)s+>;`Sgk$I}*`x#YjwX>&ywInCZxxheKZ2Fbizu;*k5+qGSd1LH}HWM6+xCGFT&HZJapel}T zxU2@88{o^V_;@fIX7O+zr;AujcR~78ijF&doSkPsJi_`Hl_S?KlfDj;4lyt|FflMN qH8C+cEigBeUJqFVI5abpx(~4fIWRbrZV*ZfG%ztZ3MC~)Peux2>=&{C delta 1601 zcmV-H2EO_CA;TfCum}P%Hk082B7dz|xsDt+5Y6=!{R2a@E*^kkKw8ZN&J0XuII^Hj zfgSqmT}85+N6)NwWn(dzV~cgZdR6SdHeqp?Hm`roNA4a!fj zadOBTL*DM+wx7OU{6&A&FJCwC=m^4g|7ZJs``LRz?}K^=Hlwm-J}uNxpa3To;8Ws~H+QTZEf{QsFqKMU0Y%Z8-GZS)CbFH5v zRaZ=YZ~L;_#_QEI_4IdmL_n|LQ_** z-&I_f5I$V*r^103mw2O$4u5bc9$M|tGxbcXs_*Gyr#>Njb8`ejLjr4MaM8Ew-2j5) z?LzD%Sfs(bfc)UpE1n3Dg#(I(GV zyBt(mbUqEr$doa>t9Y6+ZFY;5&p7a5t-vd)Qqe@&yAe^~O_kx+_kRdUTaZ*C%^2|8 zg`+8HNT22XBCmy!Mh*(S6uYONC#|MabAI8H<1ZUwDM1rvs_bwaAb)gk&m*Oivljxx z`$a1n(GCE3*Y(nMmiES!7iALz)0ZR9TuRn`Cs~ZB6PIWd)DJblmO$sfOag&zP@+)Y;Cry7Y-B0@prg(BB#Jd5r=jv)8ShA zt=HQKqDm^U;*Sx0Ay%AotVC5SS#V-O@#Cz9e2k-#C;#?5#q2`HIZr<_3xXzLuPnTC z4p@ZA(tQDGFoY36W7`0ramzakll@$jXKo#nvh+?ps>#oNwtxCo&Rbg<_A-t+S%VX_ z38I9?V~aHc@^iEB($89Z!MBh}6>OYh$ypSytoByLh810%F3L_h#=-w&?vsG?2jri? z4P(KZaRyMSu2}Q0r@a5Ta1&v6p47N9Sf;^t8Q{8kg)l@oMyH#WIjv@5ncARbBpMbJ zS$vG*NXSkkfq!zsPugVyyDdIw!Z8#AOi$&il;r=Q661-NObm-oYyH&Yii>RWC8kJq z5}-yIdbfXCCI{=T6H2~C^^e7dvRedQtEJ~K)*iq-=23UiefaStE%miZ^-S zmGnNezT@i>>sd4!gsCmF}?dJO4-}6WwQIKtiN798pOwCMwSK zW<60RhB;3u6^Ba6%hhbySW61xL##=W9$0g{C4ZNi+T@T0xRY_MjDQqZnI5@nfzfTP zK&m~idtImj>W(sXQe1NgHqYy7hdKp4o*DH4yA4n@l9`(`?Q4Ee`H?0mpaDC6j_ zfqyY%Nm1aFl8d_wEeXp(R zd@pol&k7{HY9O~IBEW6{UNKORm&=B#`+26*+LB~moExi7)TTtgUzRn2Fb?3Cb<61; zHLvfI8cpR{@$v8!XK{Rt(?u-Ahai1bs<$hIIe3BLG1I>^Lc~R7Ze(+Ga%Ev{3X}W} zhz>9@GB7tXF*PzWIV~_aladcv12;J_lkN|(12{D{lcEqx3o|)3HVP#rMNdWw)PWKi diff --git a/tests/cache/multipage/__-l__eng__000003_ocr.png__000003_ocr_tess__pdf__txt/txt.bin b/tests/cache/multipage/__-l__eng__000003_ocr.png__000003_ocr_tess__pdf__txt/txt.bin index 97c52199..c5445aab 100644 --- a/tests/cache/multipage/__-l__eng__000003_ocr.png__000003_ocr_tess__pdf__txt/txt.bin +++ b/tests/cache/multipage/__-l__eng__000003_ocr.png__000003_ocr_tess__pdf__txt/txt.bin @@ -31,9 +31,9 @@ gp) ee) 0 oN g\ gD) op) oO NC) NC) LN eo N N S os o* vs ws os os cs) Re ss & ow x & s? -ge ee Oo ss Ss Ne qs -G % S S © S -Ros % se se oe AN +30 Ce o Ss Ss ° se +© os S S 2 S +Ro % & NS se oe AN Ss 3s S Ss Ss Ss Ss ow? \O Xo) R g g Q \ No newline at end of file diff --git a/tests/cache/multipage/__-l__eng__000004_ocr.png__000004_ocr_hocr__hocr__txt/hocr.bin b/tests/cache/multipage/__-l__eng__000004_ocr.png__000004_ocr_hocr__hocr__txt/hocr.bin index 182a705a..c4f23a68 100644 --- a/tests/cache/multipage/__-l__eng__000004_ocr.png__000004_ocr_hocr__hocr__txt/hocr.bin +++ b/tests/cache/multipage/__-l__eng__000004_ocr.png__000004_ocr_hocr__hocr__txt/hocr.bin @@ -9,189 +9,206 @@ -
-
-

- - Replacement - of - "creationism" - with - "intelligent - design" +

+
+

+ + Replacement + of + "creationism" + with + "intelligent + design"

-
-

- - +

+

+ +

-
-

- - +

+

+ + - - + +

-
-

- - +

+

+ +

-
-

- - +

+

+ +

-
-

- - +

+

+ +

-
-

- - +

+

+ +

-
-

- - +

+

+ +

-
-

- - 120 +

+

+ + 120 - - 100 - 4 + + 100 + - - - = - 80 + + Cc + 80 - - + + - - S + + © - - _ - 6047 - —@— - "Creation" - and - "creationist" + + oO + 607 + —@— + "Creation" + and + "creationist" - - 5 - —@— - "Intelligent - design" + + 5 + —@— + "Intelligent + design" - - and - "design - proponent" + + and + "design + proponent" - - S - «4 + + S + 40- - - 20 - - + + 20 + - - - 0 - oe - I - T - T - T - T - © + + 0 + e- + T + T + T + I + Tv + ® - - 3) - ©) - Ay - Ay - Ay - 9 - o>) + + 3) + SG) + AY + AY + Ay + 9) + 2) - - ee - ow - oe - oe - Cs - Cs - eS + + ee + oP + ce + oe + oP + oP + - - RQ - Q - R - R - XR - Q - R + + XR + XR + R + XR + XY + R + XR - - & - es - o - a - al - & - eo + + ~\ + oe + @ + \ + wl + ae + eo - - 3 - e - oe - we - a? - i) - as? + + oe + ee + ~) + se? + a? + N? + & - - 3 - 4? - 3 - & - oe - & - & + + & + cf + O + S + % + ed + ol - - oe - ww - 6 - e - Qe - Qe - Qe + + S + &- + % + os + es + se + ae + + + ~ + os + ss + Ss + eS + eS + Ss + + + cx? + sO + oS + Q + Q + Q

diff --git a/tests/cache/multipage/__-l__eng__000004_ocr.png__000004_ocr_hocr__hocr__txt/txt.bin b/tests/cache/multipage/__-l__eng__000004_ocr.png__000004_ocr_hocr__hocr__txt/txt.bin index 39cbe785..d74d5ad9 100644 --- a/tests/cache/multipage/__-l__eng__000004_ocr.png__000004_ocr_hocr__hocr__txt/txt.bin +++ b/tests/cache/multipage/__-l__eng__000004_ocr.png__000004_ocr_hocr__hocr__txt/txt.bin @@ -16,21 +16,23 @@ Replacement of "creationism" with "intelligent design" 120 -100 4 -= 80 +100 - +Cc 80 — -S -_ 6047 —@— "Creation" and "creationist" +© +oO 607 —@— "Creation" and "creationist" 5 —@— "Intelligent design" and "design proponent" -S «4 +S 40- 20 - -0 oe I T T T T © -3) ©) Ay Ay Ay 9 o>) -ee ow oe oe Cs Cs eS -RQ Q R R XR Q R -& es o a al & eo -3 e oe we a? i) as? -3 4? 3 & oe & & -oe ww 6 e Qe Qe Qe +0 e- T T T I Tv ® +3) SG) AY AY Ay 9) 2) +ee oP ce oe oP oP ” +XR XR R XR XY R XR +~\ oe @ \ wl ae eo +oe ee ~) se? a? N? & +& cf O S % ed ol +S &- % os es se ae +~ os ss Ss eS eS Ss +cx? sO oS Q Q Q \ No newline at end of file diff --git a/tests/cache/multipage/__-l__eng__000004_ocr.png__000004_ocr_tess__pdf__txt/pdf.bin b/tests/cache/multipage/__-l__eng__000004_ocr.png__000004_ocr_tess__pdf__txt/pdf.bin index a5591ccec9a6ce25ccb58d2c39346ab7cd01d5bb..46f157996b2d387df046e48a73dde7988c363dbd 100644 GIT binary patch delta 1642 zcmV-w29^2BAMPQr1qcB$lLiQ>12r`{li>j(f1Oy%jw3e=yzf`^4_J{z>IDP=HnW~X zZUKA?a>`W4z%;eJe`q%gE_fNmx zKGAmjXAAOE*tj~MHx9L1-M()>eY^Mz{?WGEw=MYSDCF(-@Ak*`v-c@_pOg2+Znsk%a^yZnkzR;Dq!G%Raz9w*o>V;mcm z89?8-Z<5>eavk3bjX=7+3LrYq!*2;LE5D`8b=_B0YdE-)=Mr*-s|}@{=ag_>=BNK& z9^lZ5qdHHQTq}IyFx^P>yNl~`icid8#xB(@ zQgbaEDoif0BbB`2Z}qzcFsW`AVi)^Kvwvg3(;%>Wm6VmLgXlYd+q}y;{`iqn0Qvce zx6=3n0EGl5-~m`QK;kPRi$Cs)zyKt!gB6q~>Yg~n`jMbeqTQ2sl zqffQDV$>jXZv`5`CjkgGNbpFO0Dil0?p~1n1Ma>&XU8gevfMArAr*V2l6TxxuD_UF z<{tN3`Mwp_`go>;X{g7!4bC@SXJbA1lq`1=6z>Ky`reJlVb_wHVzd-ca$!W#r9@c~PciuNuDlI(#E?OXE2}bsVR@G}bZwpgMcN?cB5g%R zvSXVfyP!uGC8Bx<5Da4*e^dux3oTKjv8r(^Eqh_IiK&QAr6wz&JF2*nAzKk4gl&+h zz!{E@Z3$DB7z?qYH({^FxenX>MiAVrYIw2bDgmc(7evX{o+W5*BBcRYA|;BSuNHd7 zK*aS`eCScXW0RM=aGpnA!GB{l4q!^+N|@A%en?-AS+0Ke@Yz;b$AqBYD*Sq zq%6`hV7+O~k8R5m#|)h$c6-vcMt$Tg3e^(1?6V<%Tv{5VHd|{#@7j1)92<6KBd{{N z4Ruq>1~EvQ^;!g>X5rn;mB%8=xtDkNy_)fFM-=i3NuTWifooaj) zK~8-PS``M}H|ip)f8#=Ct8OaH`=<50-ej^@JRNt-KkRYG;w{sKYR7f#+2*H5ZqB#I zdked8JISF>PPw$Zfv{KE+0F{8KE15@7WQ)%_SZF?mL7Xu@z7bQ+j$1oi7%t1Jk7Sx zYAQ7mV62-ieK}~NDoQS@dI2u7Qcb6KKp7NS!}FkXTx;)je?=5?8D@Y>$QX3!P70Cb z)SiJ<`)yM88-jv?PY37XHzNe^P54- zn(uPRx$OdXrue#AHy4I=pJYKF#IDX7^9n8QqdgepAR4mDh+p~bl{j-5LCGcfYd@2@ z3()d91=Zy%fAs}LAXQ_wxijQGQwuhE%mFDmHB6d$sVjexyq3W`Ya15|2;)ka?a*f5 z3;}5DrHr%X`HIB*CKV4L{}x8XUl>-5_*`u*h= zraD^=h3?8>7^1X;fx=q0<8@h3J-DphY|J)(=HN-?e@kCMwe|}o7wIcSx4&|=LNq!% z`!;5VyF8d^p+l%wnXqIg&Osq1x30x{=&Y1UsLoDD76+VwknW;BN{{j2!x|@^54U!HqFgP$VFfcVS oGB7PLH&|cIg@n{N((eFFgXe(B}Gq03J!H6M*si- delta 1352 zcmV-O1-JU{A<7@H1qcB+lLiQ>12Q%@li>j(f2~-(jvO}(?(Zq|0;7l&B~d_N!1mo0 z=>lvQq)OnhNN;`Y&vxCE67jqPUKYpb4AD1Y3WEbR*DEfr$>tA2p?@ymD zPtjfebDt(_MaZfYHY+mM(w0#J-BTLtlQCCmUHrpSmb#U&|0h+)b74f|^z{ zxpp-#`}Y!eS5)4(0KVd`w}3Ckg;M%C)y3uviClN3M9-9GSL%qgx%0U{`o7%BL@l1E zN0&TT8j0v?n)io_mnDj)m&;Aodf5jU0W7Lned`bR0ybcdlkUE$<%5IC)9FmfYIzBn|rYBX)1%d`9>xsZfJPE$MC$0q1=l@F?K=PU8qj;O{ zq^gig_G_qb=!#65Y1K8=E~+vDe1|HK7*X8{+G;Bd0=y$a79^4QR(+WW0aR6yf8v@> zh|+{nG{zYN!EIDji-L6C37c=v4N z1&;@_2qGsjz6oH9;L&XYWt@U&IjF}`46t|uj3n)N`zT#b9yucMQ-5B8%nfAZ3Rur3a* zkgt7-I0|UvUkk{k6;K$?Hq}ljGP3HO#xfm}(dRJ2{rKsIOwK54XFT+U4Oy&gC%8J+ z1fdw)EYC|Apz761vT>w+w#rl)HYWYaLvM_%d6-tGG^3>+c&$cIp*Mt7#jQ-DQyFA4 z&Q_Vs4;AUcaBvQJx+~Q+e_;$p=wqGpN1@ecgFLhA-pIn~mNcRQq0Ux9H&aNMAkTK{ z-1mIoMs`3LE|F5tc{=mYORG{PIXS>YJMGTS-7B|73P$(Lv{7kBD8u|Dlze>ZZn#vki9+I~~J z>qsV{8|mvUopFLB9@v0hI6+qJQ-^4l{5~l>&w~OJ_>S0^6(lRIX@$hA=m6T!yQitb zpv<%71zvIAUxXgwAVquG6KXveOX^{7SjTi3qTxy$Ih z(gm}Wj%DLwA7gS4e>MYT3L}qO9l=c)lYcE+ji)!+)F&D%?FPXMg#M}NF+f53x-Iy6&?zdu-P4nUqU(dm%H$l5&tUQ~tF1))a3(+q7h}J*eBo8## z8@5Z>N@aTcA;9wtW?ir4jq7hx-HB%fi(Gjbz-TPe?iKdx>+_SGd5) -ee ow oe oe Cs Cs eS -RQ Q R R XR Q R -& es o a al & eo -3 e oe we a? i) as? -3 4? 3 & oe & & -oe ww 6 e Qe Qe Qe +0 e- T T T I Tv ® +3) SG) AY AY Ay 9) 2) +ee oP ce oe oP oP ” +XR XR R XR XY R XR +~\ oe @ \ wl ae eo +oe ee ~) se? a? N? & +& cf O S % ed ol +S &- % os es se ae +~ os ss Ss eS eS Ss +cx? sO oS Q Q Q \ No newline at end of file diff --git a/tests/cache/multipage/__-l__eng__000005_ocr.png__000005_ocr_hocr__hocr__txt/hocr.bin b/tests/cache/multipage/__-l__eng__000005_ocr.png__000005_ocr_hocr__hocr__txt/hocr.bin index 19e890e9..2850399b 100644 --- a/tests/cache/multipage/__-l__eng__000005_ocr.png__000005_ocr_hocr__hocr__txt/hocr.bin +++ b/tests/cache/multipage/__-l__eng__000005_ocr.png__000005_ocr_hocr__hocr__txt/hocr.bin @@ -9,7 +9,7 @@ -
+

diff --git a/tests/cache/multipage/__-l__eng__000005_ocr.png__000005_ocr_tess__pdf__txt/pdf.bin b/tests/cache/multipage/__-l__eng__000005_ocr.png__000005_ocr_tess__pdf__txt/pdf.bin index 99d3c4eb1a7fd73a4761a69b7e63a66572ee0ac6..aac063e63fbec4c7c14031d21982b6616dad80e6 100644 GIT binary patch delta 27 icmX@;e9(D=p8}tufrWvgfq|)^k)f`E`Q{jfR7L=Hs|SGq delta 27 icmX@;e9(D=p8}tOp^<^Pk)f%Pv4yUI#pW1=R7L=IAqS2C diff --git a/tests/cache/multipage/__-l__eng__000006_ocr.png__000006_ocr_hocr__hocr__txt/hocr.bin b/tests/cache/multipage/__-l__eng__000006_ocr.png__000006_ocr_hocr__hocr__txt/hocr.bin index 3e3c4012..e47a20ce 100644 --- a/tests/cache/multipage/__-l__eng__000006_ocr.png__000006_ocr_hocr__hocr__txt/hocr.bin +++ b/tests/cache/multipage/__-l__eng__000006_ocr.png__000006_ocr_hocr__hocr__txt/hocr.bin @@ -9,7 +9,7 @@ -

+

diff --git a/tests/cache/multipage/__-l__eng__000006_ocr.png__000006_ocr_tess__pdf__txt/pdf.bin b/tests/cache/multipage/__-l__eng__000006_ocr.png__000006_ocr_tess__pdf__txt/pdf.bin index 4a339f17ead4e515779e94c2b738ab6dd7fd3955..5e6242364ecea04ba3487189b45a5921763e6b7f 100644 GIT binary patch delta 27 icmbR0Fx6qhA~`-o0}BH~0|Qe-BNJT%^UWLNQW*htm -

+

diff --git a/tests/cache/palette/__-l__eng__000001_ocr.png__000001_ocr_tess__pdf__txt/pdf.bin b/tests/cache/palette/__-l__eng__000001_ocr.png__000001_ocr_tess__pdf__txt/pdf.bin index 55a7fbdf369c91ee73e672e253fc779464481d13..3d7abbbf612f9d3e28b8e1cd3adce9b96ec56e6f 100644 GIT binary patch delta 27 icmZqEZPVQ#EXrqSU}0crU|?!wVySCjzFAQ;l@S16#0Fjf delta 27 icmZqEZPVQ#EXrqKXk=h+WN2z+YN~5su~|_xl@S16%m!Zo diff --git a/tests/cache/poster/__-l__eng__000001_ocr.png__000001_ocr_tess__pdf__txt/pdf.bin b/tests/cache/poster/__-l__eng__000001_ocr.png__000001_ocr_tess__pdf__txt/pdf.bin index 33a5425605a9f1bba7d1068a6b412b4fc121aa29..1b2b8e7249bafa39480a31661fd91db3a031c175 100644 GIT binary patch delta 1903 zcmV-#2ax!eRhd<=vj_n

  • j(v(^Zk7=M!FHVnM`74w01P%EXQBdYr){{I_HQd}~@ zT`?2k)6Ai}x{5^si9{k%%Dsw~hCVJ^_%J!a;9mIP2K$g7PKbq@sv9xT0zqN7xfKq) zDM@B=xvN?Zj>B@e8M_4pzPkD&Tx&GlUON^V*eQ~5h#D5!NYuYm1&~TRz{JErPJd3} zZjqfga0K{tD9&!Az6I^u2$CQ1 zpYK|bH<=dYAm4D(AAUGPRtmG{)qlcm(dYtdDpzEk2mN}YRPj!KyoeUp1!tm9+N0bX z(@CVl|Jl?quh2)pw*l?TK6CE!*8TcAeFYArmc$OkJmJI4h3*klg_z3^l&{$o2~S@b z265{5-A|JEF%8#++McKacY4K6lsP}_I|hxtO2#-Oilc$JTV&|&4CRb1sehdzda>#j zb;C0l$riOs;TDMP6(!Ol-6YL3Y#=_0K=nriL$vk2c?32r8vB;`8|1v^B5U~m+LGIW zB!>M6Ydf+Q9GBf210bIJSg#9B%OUuxn?GXpBRqRM&FMnFpEJx|m?0l>UMc7F=PO$l z{iu8)qa48k9ku62!7qV;27jITMiXY$6>9u(fpoM@kPAwuLoSy1fzeezGQ+QSp!c)N z_ls7i#uSSftJ87AIeYOI4^xF123OAQwcrXu3cJ9Lf5CkNx1SHz>FXp=nr4Jo8v}Tg zP7I{gpt|7_jM>5TiJ=7g z$pRM;lDn!rUqX`|I*M5xNP{_}_JMQeyXmHBnuH_kra*26)GF9>m=Q2oywL~P5#WC) z79dvc_~sMY!Tei`?|)0_E4!O9v%WcfZ6Z>M8!U}rHLsh}dZ+}eVP$jo@md}S zKmmVXaqT;P7*a%XS2RWvAQH=vWFr_8D~rI8e4vO1WQla^DKYvzYrMZTi+<)L98zU%%v74P5Eubhc z%Xz$udW$#8pi1pr;2UyS8v03cp>mJ3eun%St9ava!qmQ$#SZXY**52OZ7zQkVY&)~ z>|!$oX)pk>ur!gJMi<(@IpRMFtaN9w3q?E=VtK(^)x#r0AxhP|mzh*qqipl01;x9N zY&ul-Nod>Qynm0PeXB*k*!2Y2!HjW^E$2};Xs=dWi2m{KqV}%Dy9eeICui84y@h~; zWXdx0n~Y&3Sfg*-{kW`c=*N4*#&A2tOK&2j5mO+B9#6$MHp7P(O$EqRfmsY98PEB5 zc^ZI5GrMLiffZI>T_y3C$&+BlvIa$RNuY?MJOU7$z<)W=&A6PQ-TO>F z$=|-Q)q#}lScQ8Lr()GLQ#j2y@(Bl3>}rRAo&o*P+!qRGbp8mxTN6^Vlj;d5mnR}3 z2QT+JE&hXLM($`z)7jr3yu;NlkT@R3)hNbh#=}R|m~3La3$;{;n(>+Tu{=Ttz#T-8 zCS`^>bAOa%0ahl85Y8ZHqt<)eRgJ9bh3~;CZZoyl3aYwA74}4PDVi>nC})Gh33TsS zWTYOpFgj~B6k4p>?X6DRAu;Q5p@Z4bMgYNcV&Bw-oJBO?uojraQ4u1$wVY-2Z{i0qAm$u|8|*klidM(@Bew>FV?S_5++n^l7r$T9gybQy!3m zv4K-wW@XUJ5hgr5jR_rm{9}^P>giLNi-&=&MmqlW>*1}w{L)^@L%bp{O;$ut!vb|q zF6tg3=iq8f#GV$N?8s{NkLrHTU3>mk=6_S0w^QL4sWk1YUIYs0uPo){HmHWtloAJx zvv6Y5#es+7GlP~78%7eLB0qrlb|fhXYZ~3i(^Q8%@U!D}rA=~-Z5$oF_}cvtT%vUd z^kV~GVe~0q0)~ApTLil^_h-+e>jy3OQ%GWHPk(xnHonH@cU#BL4G7X&xSXm0i!g%2 z&{g@xn+@bvuWxi`aru9DVH1HKK@PZV+<1tFn-ovNJ3Rz;b%_D4`hPxCte%sRD3T5_ pFgP$VFfcVUG&U_TH#uP6#;)B_%~qMhYi~p8Eg* delta 1901 zcmV-z2a@=iRhLz;vj_n-li>j(v(^Zk7=MoBI1IecEBXU9ki5x04vHoi>C0R=Z zwOBGRK%cq~Rau!znG6Pl!CLNBv^4Z~*}{j(3kJ8!2e;UV{BU9{TvgkMfffh~yUn$7 z;9W^Fi_3l0vU41k!_C+&An^65Kf<*})9tlg)4)!ZghSMzX(Q3|ovMIT+5sje27gL& z3U`Zayn!Rar(JP&BlRt4-$sypePzXs>+}*lL1c)z#o_rwoLS1U(@cqJ){;kT1Z_DC z@caSRk_St)I@g6EKM@0~e+eA#8GehNwZzjITE15?Q@?j)cegi9) zqyK%kg1pVNC^nw{y~@ToB#NVfxJzW{#~I2QTYpkFL-b$X_?G-iBBHtv#Gi)F}N?dbii z%Kf6vsWHVO#^!X~aL(4j;$f;V!{Exfy%k(RNZ}UP@h|vq;0p7>CV!m-O7o2H;$i@A z(n*1|8B|wXf-yUoJ`q&?&4jzr^Noo*V`hDDqHrL*W)4JPR_HbPH-91dMlvn2@owe! z93wFberi3%$EjkLr^>c(dJ!g?V?8ZndXiKOLH}zg$ljpD%TZ^>jCF zZnMCNgyi0MzJw+_b`*;`kOp%`?E~k`ck@j%Gzmx6O_AJ;s8z7%Fe6~Gc&87rA;AAo zEI_RM;G0im2lH<|zJD)|#|bj!YV2;t%=+f^jg3e(Zm>3j)xB=Yn4uD^g_Z5y$7{Lo z00qK za*NFrq`?Tp;?hKN8r8IabHslVSm}qwE)?->h~))uO%IO(g(%hTUKUbigR;$w78Gwn zvguLTC!=kT^M5{y_N`X^VwVGC2Q$VwuAE2Rpu1XWA^Io0i`Kgm?;e;>oSb2E_8tNf zk}1c`uQG;_V2!?Q|KqZ@p&x%5Hip|FUV0TNjhF&4^mr)7u?0RnX(~Xj3M^s}$#{-` z>(c-5{ewjLm5KUU#h#HmX&&}B2=8#U2_%k(adwKanep&ZH71)FZ$d2+df|Jph}%rX<37Di{ihC+{3yMoneKO`1CE_5&(+6W+cPVAfdkfTKd4r_r)JcaqAbYiZy?nXCl&l?|v8eZg}<^H^G{ zj*ND^wApySFIld#ZR%5TLx`>7R`JIpum4l|U3}i1tFxG-N~apVNGi=>*4JvV*^nnz zwDW;^oS8KdHPsd5o?p?e`uFg|B63ZQAf!&6O|b`12L9Q)GOW)za>MqX!tI91pwfh= zDt{I#v8O=q6^Z-*VRHbwoMWsnS~_Gmi^+7-Vr07de82qwXAwi1?6wx=MDvsfqasqDqWz}6xi|M_+ER$pOhujCW4?#?h1-2aU6E zV$;QrhY~V_kq;Y25}_jBf%o<#DF|yi-N?gMhdl7J;dP}=a*S;}9j!y{z6&nVIRu8W zfv-6FR4xI-zLqP3{h5ce=h5|@mis9rF-)L814)}+V-35l)8_^R=`36hRe?p(VJ_%w z{Nlw1a!b%R`m;F0znid$K#w2~Tvl#8B*RULC-I%`0=xRe09XAF@^Y-DlanZt4lpq? nFgG$WH8V3ZEigEfGbvjEGLw%fvjI1gLn=-PHwq;sMNdWw6cVQs diff --git a/tests/cache/poster/__-l__eng__000001_ocr.png__000001_ocr_tess__pdf__txt/txt.bin b/tests/cache/poster/__-l__eng__000001_ocr.png__000001_ocr_tess__pdf__txt/txt.bin index b3381922..8a68189b 100644 --- a/tests/cache/poster/__-l__eng__000001_ocr.png__000001_ocr_tess__pdf__txt/txt.bin +++ b/tests/cache/poster/__-l__eng__000001_ocr.png__000001_ocr_tess__pdf__txt/txt.bin @@ -97,7 +97,7 @@ ERASE, REPEAT, PLAY/STOP, or LOCATE. ¢ Two TRIGGER OUTPUTS may be programmed to output pulses at any selected note value. -e Will sync to standard LinnDrum or Linn 9000 sync tone. +@ Will sync to standard LinnDrum or Linn 9000 sync tone. © Utilizes ultra high-speed, 8 MHz 80186 16 bit computer internally for FAST operation. ¢ TEMPO may be specified in BEATS-PER-MINUTE or FRAMES-PER-BEAT at 24, 25, or 30 frames per second, diff --git a/tests/cache/skew/__-l__eng__--psm__7__000001_ocr.png__000001_ocr_hocr__hocr__txt/hocr.bin b/tests/cache/skew/__-l__eng__--psm__7__000001_ocr.png__000001_ocr_hocr__hocr__txt/hocr.bin index 43520b39..58eb4e7a 100644 --- a/tests/cache/skew/__-l__eng__--psm__7__000001_ocr.png__000001_ocr_hocr__hocr__txt/hocr.bin +++ b/tests/cache/skew/__-l__eng__--psm__7__000001_ocr.png__000001_ocr_hocr__hocr__txt/hocr.bin @@ -9,7 +9,7 @@ -
    +
    diff --git a/tests/cache/skew/__-l__eng__--psm__7__000001_ocr.png__000001_ocr_tess__pdf__txt/pdf.bin b/tests/cache/skew/__-l__eng__--psm__7__000001_ocr.png__000001_ocr_tess__pdf__txt/pdf.bin index 31627deb2cd877f56eb8490bd58feb4de2163324..89aa8241f89c3fc7c4f2655723b9001a7db41c66 100644 GIT binary patch delta 27 icmaDS`c8C1H7B2;frWvgfq|)^skyF!`Q}c}R7L=Kkq5K@ delta 27 icmaDS`c8C1H7B2ep^<^Pk)f%Pv9Yd!#pX`VR7L=Kh6l3% diff --git a/tests/cache/skew/__-l__eng__000001_ocr.png__000001_ocr_hocr__hocr__txt/hocr.bin b/tests/cache/skew/__-l__eng__000001_ocr.png__000001_ocr_hocr__hocr__txt/hocr.bin index 17df1412..9e241235 100644 --- a/tests/cache/skew/__-l__eng__000001_ocr.png__000001_ocr_hocr__hocr__txt/hocr.bin +++ b/tests/cache/skew/__-l__eng__000001_ocr.png__000001_ocr_hocr__hocr__txt/hocr.bin @@ -9,7 +9,7 @@ -
    +

    diff --git a/tests/cache/skew/__-l__eng__000001_ocr.png__000001_ocr_tess__pdf__txt/pdf.bin b/tests/cache/skew/__-l__eng__000001_ocr.png__000001_ocr_tess__pdf__txt/pdf.bin index b143cc9bc98c28128f9f9292443391ccce24ce87..1499489e0807d5e186bf90ce28f4dfd9ca1995b5 100644 GIT binary patch delta 27 icmX?*d?0y)j{%>dfrWvgfq|)ksim%g`Q|8tG)4e^U7p^<^Pk)f%Pv6-%c#pWo3G)4e^X$R*3 From 5eb5b5ba732962e7a56ddbc5ddf54e04358f8ad8 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Sun, 1 Aug 2021 01:00:14 -0700 Subject: [PATCH 037/106] v12.3.1 release notes --- docs/release_notes.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/release_notes.rst b/docs/release_notes.rst index 6d35ba65..a31937ff 100644 --- a/docs/release_notes.rst +++ b/docs/release_notes.rst @@ -13,6 +13,13 @@ The public API may be useful in scripts that launch OCRmyPDF processes or that wish to use some of its features for working with PDFs. +v12.3.1 +======= + +- Fixed issue with selection of text when using the hOCR renderer. (:issue:`813`) +- Fixed build errors with the Docker image by upgrading to a newer Ubuntu. + Also set the timezone of this image to UTC. + v12.3.0 ======= From ae49e3b6db2ac89bde775fdb9b073b1c86501dbb Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Wed, 4 Aug 2021 02:47:03 -0700 Subject: [PATCH 038/106] build: don't try to docker build for third party users --- .github/workflows/build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index cba6c85e..c742c504 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -232,6 +232,7 @@ jobs: name: Build Docker images needs: [wheel_sdist_linux, test_linux, test_macos, test_windows] runs-on: ubuntu-latest + if: secrets.DOCKERHUB_TOKEN steps: - name: Set image tag to release or branch run: echo "DOCKER_IMAGE_TAG=${GITHUB_REF##*/}" >> $GITHUB_ENV From dc2b161306b3cdbe14a1a53fbeaa3ef315307c12 Mon Sep 17 00:00:00 2001 From: mara004 <65915611+mara004@users.noreply.github.com> Date: Wed, 4 Aug 2021 11:47:34 +0200 Subject: [PATCH 039/106] docs: Add two commas (#809) --- docs/pdfsecurity.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/pdfsecurity.rst b/docs/pdfsecurity.rst index 9288e311..a11c84c3 100644 --- a/docs/pdfsecurity.rst +++ b/docs/pdfsecurity.rst @@ -41,8 +41,8 @@ layer. First, it runs all PDFs through `pikepdf `__, a library based on `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 +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 used to split the PDF into single page PDFs. From b923612323b91360aa06ae15eab104f9635b4372 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Wed, 4 Aug 2021 05:48:25 -0400 Subject: [PATCH 040/106] Allow watchdog 2. (#815) * Allow watchdog 2. The breaking change was dropping support for macOS 10.12 and earlier, which doesn't affect us. * Add shebang to watcher script. --- misc/watcher.py | 1 + setup.cfg | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/misc/watcher.py b/misc/watcher.py index 68437878..991ee9ac 100644 --- a/misc/watcher.py +++ b/misc/watcher.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python3 # Copyright (C) 2019 Ian Alexander: https://github.com/ianalexander # Copyright (C) 2020 James R Barlow: https://github.com/jbarlow83 # diff --git a/setup.cfg b/setup.cfg index 44435e3f..8b769ba1 100644 --- a/setup.cfg +++ b/setup.cfg @@ -84,7 +84,7 @@ docs = extended_test = PyMuPDF == 1.13.4 watcher = - watchdog >= 1.0.2, < 2 + watchdog >= 1.0.2, < 3 webservice = Flask >= 1, < 2 From 969e54f0e30d53dd2a4692123d9d556d907590c5 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Wed, 4 Aug 2021 05:49:13 -0400 Subject: [PATCH 041/106] Allow flask 2 for webservice (#816) * Allow flask 2 for webservice. The breaking changes do not appear to affect it. * Add shebang to webservice script. --- misc/webservice.py | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/misc/webservice.py b/misc/webservice.py index ed2a0374..bfc4a69e 100644 --- a/misc/webservice.py +++ b/misc/webservice.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python3 # webservice.py wrapper for OCRmyPDF # Copyright (C) 2019 James R. Barlow: github.com/jbarlow83 # @@ -36,7 +37,6 @@ from flask import ( redirect, request, send_from_directory, - url_for, ) from werkzeug.utils import secure_filename diff --git a/setup.cfg b/setup.cfg index 8b769ba1..277b577c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -86,7 +86,7 @@ extended_test = watcher = watchdog >= 1.0.2, < 3 webservice = - Flask >= 1, < 2 + Flask >= 1, < 3 [options.entry_points] console_scripts = From 87ff6c8301eb8b76c55fe5a76b9b5c7895a2a142 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Wed, 4 Aug 2021 02:49:48 -0700 Subject: [PATCH 042/106] webservice: tidy flask apis --- misc/webservice.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/misc/webservice.py b/misc/webservice.py index bfc4a69e..a8a87ad3 100644 --- a/misc/webservice.py +++ b/misc/webservice.py @@ -29,15 +29,7 @@ import shlex from subprocess import PIPE, run from tempfile import TemporaryDirectory -from flask import ( - Flask, - Response, - abort, - flash, - redirect, - request, - send_from_directory, -) +from flask import Flask, Response, request, send_from_directory from werkzeug.utils import secure_filename app = Flask(__name__) From 2c579700d64f980d7187d0f2174a6507352bb310 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Wed, 4 Aug 2021 02:52:45 -0700 Subject: [PATCH 043/106] v12.3.2 release notes --- docs/release_notes.rst | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/release_notes.rst b/docs/release_notes.rst index a31937ff..ebe4fa59 100644 --- a/docs/release_notes.rst +++ b/docs/release_notes.rst @@ -12,11 +12,15 @@ may be unreliable. Use the API to depend on precise behavior. The public API may be useful in scripts that launch OCRmyPDF processes or that wish to use some of its features for working with PDFs. +v12.3.2 +======= + +- Indicate support for flask 2.x, watcher 2.x (:issue:`815, 816`). v12.3.1 ======= -- Fixed issue with selection of text when using the hOCR renderer. (:issue:`813`) +- Fixed issue with selection of text when using the hOCR renderer (:issue:`813`). - Fixed build errors with the Docker image by upgrading to a newer Ubuntu. Also set the timezone of this image to UTC. @@ -24,7 +28,7 @@ v12.3.0 ======= - Fixed a regression introduced in Pillow 8.3.0. Pillow no longer rounds DPI - for image resolutions. We now account for this. (:issue:`802`) + for image resolutions. We now account for this (:issue:`802`). - We no longer use some API calls that are deprecated in the latest versions of pikepdf. - Improved error message when a language is requested that doesn't look like a From 8a1cb7047965d5d41a3fe9033b83b2eab48bc256 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Wed, 4 Aug 2021 03:06:28 -0700 Subject: [PATCH 044/106] build: adjust pull request again --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c742c504..56d6404d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -232,7 +232,7 @@ jobs: name: Build Docker images needs: [wheel_sdist_linux, test_linux, test_macos, test_windows] runs-on: ubuntu-latest - if: secrets.DOCKERHUB_TOKEN + if: github.event_name != 'pull_request' steps: - name: Set image tag to release or branch run: echo "DOCKER_IMAGE_TAG=${GITHUB_REF##*/}" >> $GITHUB_ENV From 8bb244df24af8af3a84c42f3da8ca000edbdc6e4 Mon Sep 17 00:00:00 2001 From: mara004 <65915611+mara004@users.noreply.github.com> Date: Sun, 15 Aug 2021 05:05:27 +0200 Subject: [PATCH 045/106] [ci skip] Update jbig2.rst (#817) Co-authored-by: jbarlow83 --- docs/jbig2.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/jbig2.rst b/docs/jbig2.rst index 81789b6f..c49f0e9c 100644 --- a/docs/jbig2.rst +++ b/docs/jbig2.rst @@ -9,11 +9,11 @@ encoding was patented for a long time. All known JBIG2 US patents have expired as of 2017, but it is possible that unknown patents exist. JBIG2 encoding is recommended for OCRmyPDF and is used to losslessly -create smaller PDFs. If JBIG2 encoding not available, lower quality +create smaller PDFs. If JBIG2 encoding is not available, lower quality encodings will be used. JBIG2 decoding is not patented and is performed automatically by most -PDF viewers. It is widely supported has been part of the PDF +PDF viewers. It is widely supported and has been part of the PDF specification since 2001. On macOS, Homebrew packages jbig2enc and OCRmyPDF includes it by @@ -37,7 +37,7 @@ Lossy mode JBIG2 OCRmyPDF provides lossy mode JBIG2 as an advanced feature. Users should `review the technical concerns with JBIG2 in lossy -mode `__ +mode `__ and decide if this feature is acceptable for their use case. JBIG2 lossy mode does achieve higher compression ratios than any other From fcfc78b7ee745bd2af78e5df9f04a0202c4cafb9 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Tue, 17 Aug 2021 01:43:02 -0700 Subject: [PATCH 046/106] Move tool config to pyproject --- pyproject.toml | 38 ++++++++++++++++++++++++++++++++++++++ setup.cfg | 45 +-------------------------------------------- 2 files changed, 39 insertions(+), 44 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 8791923e..6a96810d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,3 +34,41 @@ exclude = ''' | src/ocrmypdf/lib/_leptonica.py )/ ''' + +[tool.coverage.run] +branch = true +parallel = true +concurrency = ["multiprocessing"] + +[tool.coverage.paths] +source = ["src/ocrmypdf"] + +[tool.coverage.report] +# Regexes for lines to exclude from consideration +exclude_lines = [ + # Have to re-enable the standard pragma + "pragma: no cover", + + # Don't complain if tests don't hit defensive assertion code: + "raise AssertionError", + "raise NotImplementedError", + + # Don't complain if non-runnable code isn't run: + "if 0:", + "if False:", + "if __name__ == .__main__.:", + "if TYPE_CHECKING:" +] + +[tool.isort] +profile = "black" +known_first_party = "ocrmypdf" +known_third_party = ["PIL","_cffi_backend","cffi","flask","img2pdf","pdfminer","pikepdf","pkg_resources","pluggy","pytest","reportlab","setuptools","sphinx_rtd_theme","tqdm","watchdog","werkzeug"] + +[tool.pytest.ini_options] +minversion = "6.0" +norecursedirs = ["lib", ".pc", ".git", "venv", "output", "cache", "resources"] +testpaths = ["tests"] +addopts = "-n auto" +markers = ["slow"] +filterwarnings = ["ignore:.*XMLParser.*:DeprecationWarning"] \ No newline at end of file diff --git a/setup.cfg b/setup.cfg index 277b577c..d0ecf331 100644 --- a/setup.cfg +++ b/setup.cfg @@ -72,6 +72,7 @@ where = src [options.extras_require] test = + coverage[toml] >= 5 pytest >= 6.0.0 pytest-xdist >= 2.2.0 pytest-cov >= 2.11.1 @@ -101,47 +102,3 @@ test = pytest [check-manifest] ignore = .github - -[tool:pytest] -norecursedirs = lib .pc .git output cache resources -testpaths = tests -filterwarnings = - ignore:.*XMLParser.*:DeprecationWarning -markers = - slow -addopts = - -n auto - -[isort] -multi_line_output = 3 -include_trailing_comma = True -force_grid_wrap = 0 -use_parentheses = True -line_length = 88 -known_first_party = ocrmypdf -known_third_party = PIL,_cffi_backend,cffi,flask,img2pdf,pdfminer,pikepdf,pkg_resources,pluggy,pytest,reportlab,setuptools,sphinx_rtd_theme,tqdm,watchdog,werkzeug - -[coverage:paths] -source = - src/ocrmypdf - -[coverage:run] -branch = true -parallel = true -concurrency = multiprocessing - -[coverage:report] -# Regexes for lines to exclude from consideration -exclude_lines = - # Have to re-enable the standard pragma - pragma: no cover - - # Don't complain if tests don't hit defensive assertion code: - raise AssertionError - raise NotImplementedError - - # Don't complain if non-runnable code isn't run: - if 0: - if False: - if __name__ == .__main__.: - if TYPE_CHECKING: From f8970ad86219e2d1a84a2a06fa7934977b7e7b5a Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Tue, 17 Aug 2021 02:30:24 -0700 Subject: [PATCH 047/106] Update CI to test Tesseract 5 and more Linux versions --- .github/workflows/build.yml | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 56d6404d..2130dfe7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -18,8 +18,20 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-18.04] #, ubuntu-20.04] - python: ["3.6"] #, "3.7", "3.8", "3.9"] + include: + - os: ubuntu-18.04 + python: 3.6 + - os: ubuntu-18.04 + python: 3.7 + - os: ubuntu-20.04 + python: 3.8 + - os: ubuntu-20.04 + python: 3.9 + - os: ubuntu-latest + python: 3.9 + - os: ubuntu-latest + python: 3.9 + tesseract5: true env: OS: ${{ matrix.os }} @@ -35,6 +47,11 @@ jobs: with: python-version: ${{ matrix.python }} + - name: Install Tesseract 5 + if: matrix.tesseract5 + run: | + sudo add-apt-repository ppa:alex-p/tesseract-ocr-devel + - name: Install common packages run: | sudo apt-get update From 0a110fac559d3630afdf73582ff402d43e6c18a0 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Sat, 21 Aug 2021 17:30:14 -0700 Subject: [PATCH 048/106] watcher: fix bool not working as expecting Closes #821 --- misc/watcher.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/misc/watcher.py b/misc/watcher.py index 991ee9ac..193b485a 100644 --- a/misc/watcher.py +++ b/misc/watcher.py @@ -37,14 +37,19 @@ import ocrmypdf # pylint: disable=logging-format-interpolation + +def getenv_bool(name: str, default: str = 'False'): + return os.getenv(name, default).lower() in ('true', 'yes', 'y', '1') + + INPUT_DIRECTORY = os.getenv('OCR_INPUT_DIRECTORY', '/input') OUTPUT_DIRECTORY = os.getenv('OCR_OUTPUT_DIRECTORY', '/output') -OUTPUT_DIRECTORY_YEAR_MONTH = bool(os.getenv('OCR_OUTPUT_DIRECTORY_YEAR_MONTH', '')) -ON_SUCCESS_DELETE = bool(os.getenv('OCR_ON_SUCCESS_DELETE', '')) -DESKEW = bool(os.getenv('OCR_DESKEW', '')) +OUTPUT_DIRECTORY_YEAR_MONTH = getenv_bool('OCR_OUTPUT_DIRECTORY_YEAR_MONTH') +ON_SUCCESS_DELETE = getenv_bool('OCR_ON_SUCCESS_DELETE') +DESKEW = getenv_bool('OCR_DESKEW') OCR_JSON_SETTINGS = json.loads(os.getenv('OCR_JSON_SETTINGS', '{}')) POLL_NEW_FILE_SECONDS = int(os.getenv('OCR_POLL_NEW_FILE_SECONDS', '1')) -USE_POLLING = bool(os.getenv('OCR_USE_POLLING', '')) +USE_POLLING = getenv_bool('OCR_USE_POLLING') LOGLEVEL = os.getenv('OCR_LOGLEVEL', 'INFO') PATTERNS = ['*.pdf', '*.PDF'] From 86c04305f467071616951085c7af85cb93181c52 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Sat, 21 Aug 2021 17:37:10 -0700 Subject: [PATCH 049/106] readme: confirm Tesseract 5 support --- README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 713ec619..b03627a0 100644 --- a/README.md +++ b/README.md @@ -91,6 +91,11 @@ brew install tesseract-lang You can then pass the `-l LANG` argument to OCRmyPDF to give a hint as to what languages it should search for. Multiple languages can be requested. +OCRmyPDF supports Tesseract 4.0 and the beta versions of Tesseract 5.0. It will +automatically use whichever version it finds first on the `PATH` environment +variable. On Windows, if `PATH` does not provide a Tesseract binary, we use +the highest version number that is installed according to the Windows Registry. + ## Documentation and support Once OCRmyPDF is installed, the built-in help which explains the command syntax and options can be accessed via: @@ -115,7 +120,7 @@ In addition to the required Python version (3.6+), OCRmyPDF requires external pr - [heise Open Source, 09/2014: Texterkennung mit OCRmyPDF](https://heise.de/-2356670) - [heise Durchsuchbare PDF-Dokumente mit OCRmyPDF erstellen](https://www.heise.de/ratgeber/Durchsuchbare-PDF-Dokumente-mit-OCRmyPDF-erstellen-4607592.html) - [Excellent Utilities: OCRmyPDF](https://www.linuxlinks.com/excellent-utilities-ocrmypdf-add-ocr-text-layer-scanned-pdfs/) -- [LinuxUser Texterkennung mit OCRmyPDF und Scanbd automatisieren](https://www.linux-community.de/ausgaben/linuxuser/2021/06/texterkennung-mit-ocrmypdf-und-scanbd-automatisieren/) +- [LinuxUser Texterkennung mit OCRmyPDF und Scanbd automatisieren](https://www.linux-community.de/ausgaben/linuxuser/2021/06/texterkennung-mit-ocrmypdf-und-scanbd-automatisieren/) ## Business enquiries From d8d9c41abb36f4cfa6b9c2f3b4ba55a984730ee4 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Sat, 21 Aug 2021 18:06:14 -0700 Subject: [PATCH 050/106] v12.3.3 release notes --- docs/release_notes.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/release_notes.rst b/docs/release_notes.rst index ebe4fa59..64d3bbc7 100644 --- a/docs/release_notes.rst +++ b/docs/release_notes.rst @@ -12,6 +12,13 @@ may be unreliable. Use the API to depend on precise behavior. The public API may be useful in scripts that launch OCRmyPDF processes or that wish to use some of its features for working with PDFs. +v12.3.3 +======= + +- watcher.py: fixed interpretation of boolean env vars (:issue:`821`). +- Adjust CI scripts to test Tesseract 5 betas. +- Document our support for the Tesseract 5 betas. + v12.3.2 ======= From 1b46481f7e694d2ad097f9a826e4d25e6157d022 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Thu, 26 Aug 2021 17:59:40 -0700 Subject: [PATCH 051/106] pre-commit: add setup.cfg fmt --- .pre-commit-config.yaml | 4 ++ pyproject.toml | 2 +- setup.cfg | 116 +++++++++++++++++++++------------------- tests/test_pipeline.py | 6 +-- 4 files changed, 67 insertions(+), 61 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3cd240e2..38e9419f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -21,3 +21,7 @@ repos: - id: black language_version: python exclude: ^src/ocrmypdf/lib/_leptonica.py + - repo: https://github.com/asottile/setup-cfg-fmt + rev: v1.17.0 + hooks: + - id: setup-cfg-fmt diff --git a/pyproject.toml b/pyproject.toml index 6a96810d..0ad451d3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -63,7 +63,7 @@ exclude_lines = [ [tool.isort] profile = "black" known_first_party = "ocrmypdf" -known_third_party = ["PIL","_cffi_backend","cffi","flask","img2pdf","pdfminer","pikepdf","pkg_resources","pluggy","pytest","reportlab","setuptools","sphinx_rtd_theme","tqdm","watchdog","werkzeug"] +known_third_party = ["PIL", "_cffi_backend", "cffi", "flask", "img2pdf", "ocrmypdf", "pdfminer", "pikepdf", "pkg_resources", "pluggy", "pytest", "reportlab", "setuptools", "sphinx_rtd_theme", "tqdm", "watchdog", "werkzeug"] [tool.pytest.ini_options] minversion = "6.0" diff --git a/setup.cfg b/setup.cfg index d0ecf331..8c760454 100644 --- a/setup.cfg +++ b/setup.cfg @@ -2,23 +2,15 @@ name = ocrmypdf description = OCRmyPDF adds an OCR text layer to scanned PDF files, allowing them to be searched long_description = file: README.md -long_description_content_type = text/markdown; charset=UTF-8 +long_description_content_type = text/markdown url = https://github.com/jbarlow83/OCRmyPDF author = James R. Barlow author_email = james@purplerock.ca +license = MPL-2.0 +license_file = LICENSE license_files = LICENSE -keywords = - PDF - OCR - optical character recognition - PDF/A - scanning classifiers = - Programming Language :: Python :: 3.6 - Programming Language :: Python :: 3.7 - Programming Language :: Python :: 3.8 - Programming Language :: Python :: 3.9 Development Status :: 5 - Production/Stable Environment :: Console Intended Audience :: End Users/Desktop @@ -30,69 +22,81 @@ classifiers = Operating System :: POSIX Operating System :: POSIX :: BSD Operating System :: POSIX :: Linux + Programming Language :: Python :: 3 + Programming Language :: Python :: 3 :: Only + Programming Language :: Python :: 3.6 + Programming Language :: Python :: 3.7 + Programming Language :: Python :: 3.8 + Programming Language :: Python :: 3.9 Topic :: Scientific/Engineering :: Image Recognition Topic :: Text Processing :: Indexing Topic :: Text Processing :: Linguistic +keywords = + PDF + OCR + optical character recognition + PDF/A + scanning project_urls = Documentation = https://ocrmypdf.readthedocs.io/ Source = https://github.com/jbarlow83/ocrmypdf Tracker = https://github.com/jbarlow83/ocrmypdf/issues [options] -zip_safe = False packages = find: +install_requires = + Pillow>=8.2.0 + cffi>=1.9.1 # must be a setup and install requirement + coloredlogs>=14.0 # strictly optional + img2pdf>=0.3.0,<0.5 # pure Python + pdfminer.six!=20200720,>=20191110,<=20201018 + pikepdf>=2.10.0 + pluggy>=0.13.0,<1.0 + reportlab>=3.5.66 + setuptools + tqdm>=4 +python_requires = >=3.6 +include_package_data = True package_dir = =src platforms = any -include_package_data=True -install_requires = - cffi >= 1.9.1 # must be a setup and install requirement - coloredlogs >= 14.0 # strictly optional - img2pdf >= 0.3.0, < 0.5 # pure Python, so track HEAD closely - pdfminer.six >= 20191110, != 20200720, <= 20201018 - pikepdf >= 2.10.0 - Pillow >= 8.2.0 - pluggy >= 0.13.0, < 1.0 - reportlab >= 3.5.66 - setuptools - tqdm >= 4 -python_requires = >= 3.6 -setup_requires = # can be removed whenever we can drop pip 9 support - cffi >= 1.9.1 # to build the leptonica module - setuptools_scm # so that version will work - setuptools_scm_git_archive # enable version from github tarballs +setup_requires = + # + cffi>=1.9.1 # to build the leptonica module + setuptools_scm + setuptools_scm_git_archive +zip_safe = False + +[options.packages.find] +where = src + +[options.entry_points] +console_scripts = + ocrmypdf = ocrmypdf.__main__:run + +[options.extras_require] +docs = + sphinx + sphinx-issues + sphinx-rtd-theme +extended_test = + PyMuPDF==1.13.4 +test = + coverage[toml]>=5 + pytest>=6.0.0 + pytest-cov>=2.11.1 + pytest-xdist>=2.2.0 + python-xmp-toolkit==2.0.1 # also requires apt-get install libexempi3 +watcher = + watchdog>=1.0.2,<3 +webservice = + Flask>=1,<3 [options.package_data] ocrmypdf = data/sRGB.icc py.typed -[options.packages.find] -where = src - -[options.extras_require] -test = - coverage[toml] >= 5 - pytest >= 6.0.0 - pytest-xdist >= 2.2.0 - pytest-cov >= 2.11.1 - python-xmp-toolkit == 2.0.1 # also requires apt-get install libexempi3 - # or brew install exempi -docs = - sphinx - sphinx-rtd-theme - sphinx-issues -extended_test = - PyMuPDF == 1.13.4 -watcher = - watchdog >= 1.0.2, < 3 -webservice = - Flask >= 1, < 3 - -[options.entry_points] -console_scripts = - ocrmypdf = ocrmypdf.__main__:run - [bdist_wheel] python-tag = py36 @@ -101,4 +105,4 @@ test = pytest [check-manifest] ignore = - .github + .github diff --git a/tests/test_pipeline.py b/tests/test_pipeline.py index becef2c4..41cb0f04 100644 --- a/tests/test_pipeline.py +++ b/tests/test_pipeline.py @@ -79,9 +79,7 @@ def test_dpi_needed(image, text, vector, result, rgb_image, outdir): # Input: ('', '', '', '', ''), # Output: - ( - ((1, 5), None), - ), + (((1, 5), None),), ), ( 'no_empty_values', @@ -147,4 +145,4 @@ def test_dpi_needed(image, text, vector, result, rgb_image, outdir): ), ) def test_enumerate_compress_ranges(name, input, output): - assert output == tuple(_pipeline.enumerate_compress_ranges(input)) \ No newline at end of file + assert output == tuple(_pipeline.enumerate_compress_ranges(input)) From 067e61e03a9324d265209620fd4573e5c36e042b Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Thu, 26 Aug 2021 18:00:37 -0700 Subject: [PATCH 052/106] pre-commit: auto update --- .pre-commit-config.yaml | 6 +++--- setup.cfg | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 38e9419f..c3fb7fcb 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v3.4.0 + rev: v4.0.1 hooks: - id: check-case-conflict - id: check-merge-conflict @@ -12,11 +12,11 @@ repos: hooks: - id: seed-isort-config - repo: https://github.com/pre-commit/mirrors-isort - rev: v5.7.0 # pick the isort version you'd like to use from https://github.com/pre-commit/mirrors-isort/releases + rev: v5.9.3 # pick the isort version you'd like to use from https://github.com/pre-commit/mirrors-isort/releases hooks: - id: isort - repo: https://github.com/psf/black - rev: 20.8b1 + rev: 21.7b0 hooks: - id: black language_version: python diff --git a/setup.cfg b/setup.cfg index 8c760454..7c96d66e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -61,7 +61,6 @@ package_dir = =src platforms = any setup_requires = - # cffi>=1.9.1 # to build the leptonica module setuptools_scm setuptools_scm_git_archive From 4eca0a165b273fa5d953a133cb6f0b2b1cb23179 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Thu, 26 Aug 2021 18:04:38 -0700 Subject: [PATCH 053/106] pre-commit: pyupgrade modernizing --- .pre-commit-config.yaml | 5 +++++ misc/webservice.py | 2 +- src/ocrmypdf/_exec/tesseract.py | 2 +- src/ocrmypdf/_exec/unpaper.py | 2 +- src/ocrmypdf/_pipeline.py | 6 +++--- src/ocrmypdf/_sync.py | 2 +- src/ocrmypdf/_validation.py | 6 +++--- src/ocrmypdf/cli.py | 2 +- src/ocrmypdf/helpers.py | 4 ++-- src/ocrmypdf/leptonica.py | 7 +++---- src/ocrmypdf/pdfinfo/layout.py | 2 +- tests/test_main.py | 4 ++-- 12 files changed, 24 insertions(+), 20 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c3fb7fcb..ff10f765 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -25,3 +25,8 @@ repos: rev: v1.17.0 hooks: - id: setup-cfg-fmt + - repo: https://github.com/asottile/pyupgrade + rev: v2.24.0 + hooks: + - id: pyupgrade + args: ["--py36-plus"] diff --git a/misc/webservice.py b/misc/webservice.py index a8a87ad3..e005b7c2 100644 --- a/misc/webservice.py +++ b/misc/webservice.py @@ -37,7 +37,7 @@ app.secret_key = "secret" app.config['MAX_CONTENT_LENGTH'] = 50_000_000 app.config.from_envvar("OCRMYPDF_WEBSERVICE_SETTINGS", silent=True) -ALLOWED_EXTENSIONS = set(["pdf"]) +ALLOWED_EXTENSIONS = {"pdf"} def allowed_file(filename): diff --git a/src/ocrmypdf/_exec/tesseract.py b/src/ocrmypdf/_exec/tesseract.py index ac7b4fab..25f09bbd 100644 --- a/src/ocrmypdf/_exec/tesseract.py +++ b/src/ocrmypdf/_exec/tesseract.py @@ -117,7 +117,7 @@ def get_languages(): if line.startswith('Error'): raise MissingDependencyError(lang_error(output)) _header, *rest = output.splitlines() - return set(lang.strip() for lang in rest) + return {lang.strip() for lang in rest} def tess_base_args(langs: List[str], engine_mode: Optional[int]) -> List[str]: diff --git a/src/ocrmypdf/_exec/unpaper.py b/src/ocrmypdf/_exec/unpaper.py index 5226326d..3c3ae72c 100644 --- a/src/ocrmypdf/_exec/unpaper.py +++ b/src/ocrmypdf/_exec/unpaper.py @@ -45,7 +45,7 @@ def _setup_unpaper_io(tmpdir: Path, input_file: Path) -> Tuple[Path, Path]: im = im.convert(mode='1') else: im = im.convert(mode='RGB') - except IOError as e: + except OSError as e: raise MissingDependencyError( "Could not convert image with type " + im.mode ) from e diff --git a/src/ocrmypdf/_pipeline.py b/src/ocrmypdf/_pipeline.py index ce3b8d88..ebf69295 100644 --- a/src/ocrmypdf/_pipeline.py +++ b/src/ocrmypdf/_pipeline.py @@ -48,7 +48,7 @@ def triage_image_file(input_file, output_file, options): log.info("Input file is not a PDF, checking if it is an image...") try: im = Image.open(input_file) - except EnvironmentError as e: + except OSError as e: # Recover the original filename log.error(str(e).replace(str(input_file), str(options.input_file))) raise UnsupportedImageFormatError() from e @@ -135,7 +135,7 @@ def triage(original_filename, input_file, output_file, options): # Origin file is a pdf create a symlink with pdf extension safe_symlink(input_file, output_file) return output_file - except EnvironmentError as e: + except OSError as e: log.debug(f"Temporary file was at: {input_file}") msg = str(e).replace(str(input_file), original_filename) raise InputFileError(msg) from e @@ -856,7 +856,7 @@ def merge_sidecars(txt_files: Iterable[Optional[Path]], context: PdfContext): if frm != 1: stream.write('\f') # Form feed between pages if txt_file: - with open(txt_file, 'r', encoding="utf-8") as in_: + with open(txt_file, encoding="utf-8") as in_: txt = in_.read() # Some OCR engines (e.g. Tesseract v4 alpha) add form feeds # between pages, and some do not. For consistency, we ignore diff --git a/src/ocrmypdf/_sync.py b/src/ocrmypdf/_sync.py index 53e5c7cf..303b0cf6 100644 --- a/src/ocrmypdf/_sync.py +++ b/src/ocrmypdf/_sync.py @@ -421,7 +421,7 @@ def run_pipeline(options, *, plugin_manager, api=False): try: debug_log_handler.close() log.removeHandler(debug_log_handler) - except EnvironmentError as e: + except OSError as e: print(e, file=sys.stderr) cleanup_working_files(work_folder, options) diff --git a/src/ocrmypdf/_validation.py b/src/ocrmypdf/_validation.py index 4c348f5b..cb55c454 100644 --- a/src/ocrmypdf/_validation.py +++ b/src/ocrmypdf/_validation.py @@ -182,10 +182,10 @@ def _pages_from_ranges(ranges: str) -> Set[int]: def check_options_ocr_behavior(options): exclusive_options = sum( - [ + (1 if opt else 0) for opt in (options.force_ocr, options.skip_text, options.redo_ocr) - ] + ) if exclusive_options >= 2: raise BadArgsError("Choose only one of --force-ocr, --skip-text, --redo-ocr.") @@ -302,7 +302,7 @@ def check_closed_streams(options): # pragma: no cover if options.input_file == '-': log.error("Trying to read from stdin but stdin seems closed") return False - sys.stdin = open(os.devnull, 'r') + sys.stdin = open(os.devnull) if sys.stdout is None: if options.output_file == '-': diff --git a/src/ocrmypdf/cli.py b/src/ocrmypdf/cli.py index 204b7b31..43ea1de7 100644 --- a/src/ocrmypdf/cli.py +++ b/src/ocrmypdf/cli.py @@ -22,7 +22,7 @@ def numeric(basetype: Type[T], min_: Optional[T] = None, max_: Optional[T] = Non def _numeric(string): value = basetype(string) if (min_ is not None and value < min_) or (max_ is not None and value > max_): - msg = "%r not in valid range %r" % (string, (min_, max_)) + msg = f"{string!r} not in valid range {(min_, max_)!r}" raise argparse.ArgumentTypeError(msg) return value diff --git a/src/ocrmypdf/helpers.py b/src/ocrmypdf/helpers.py index 916fb42c..3f012ef1 100644 --- a/src/ocrmypdf/helpers.py +++ b/src/ocrmypdf/helpers.py @@ -189,7 +189,7 @@ def is_file_writable(test_file: os.PathLike) -> bool: with suppress(OSError): p.unlink() return True - except (EnvironmentError, RuntimeError) as e: + except (OSError, RuntimeError) as e: log.debug(e) log.error(str(e)) return False @@ -273,7 +273,7 @@ def deprecated(func): def new_func(*args, **kwargs): warnings.simplefilter('always', DeprecationWarning) # turn off filter warnings.warn( - "Call to deprecated function {}.".format(func.__name__), + f"Call to deprecated function {func.__name__}.", category=DeprecationWarning, stacklevel=2, ) diff --git a/src/ocrmypdf/leptonica.py b/src/ocrmypdf/leptonica.py index 807560ff..9f2554de 100644 --- a/src/ocrmypdf/leptonica.py +++ b/src/ocrmypdf/leptonica.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- # # © 2013-16: jbarlow83 from Github (https://github.com/jbarlow83) # @@ -272,7 +271,7 @@ class LeptonicaObject: # Leptonica API uses double-pointers for its destroy APIs to prevent # dangling pointers. This means we need to put our single pointer, # cdata, in a temporary CDATA**. - pp = ffi.new('{} **'.format(cls.LEPTONICA_TYPENAME), cdata) + pp = ffi.new(f'{cls.LEPTONICA_TYPENAME} **', cdata) cls.cdata_destroy(pp) @@ -844,7 +843,7 @@ class Box(LeptonicaObject): def __repr__(self): if self._cdata: - return ''.format( + return ''.format( self.x, self.y, self.w, self.h ) return '' @@ -916,7 +915,7 @@ class Sel(LeptonicaObject): lines = [line.strip() for line in selstr.split('\n') if line.strip()] h = len(lines) w = len(lines[0]) - lengths = set(len(line) for line in lines) + lengths = {len(line) for line in lines} if len(lengths) != 1: raise ValueError("All lines in selstr must be same length") diff --git a/src/ocrmypdf/pdfinfo/layout.py b/src/ocrmypdf/pdfinfo/layout.py index 4159a1cb..bf8d7c30 100644 --- a/src/ocrmypdf/pdfinfo/layout.py +++ b/src/ocrmypdf/pdfinfo/layout.py @@ -135,7 +135,7 @@ class LTStateAwareChar(LTChar): return self._text def __repr__(self): - return '<%s %s matrix=%s rendermode=%r font=%r adv=%s text=%r>' % ( + return '<{} {} matrix={} rendermode={!r} font={!r} adv={} text={!r}>'.format( self.__class__.__name__, bbox2str(self.bbox), matrix2str(self.matrix), diff --git a/tests/test_main.py b/tests/test_main.py index 0d827fa3..0bd69ae9 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -701,7 +701,7 @@ def test_sidecar_pagecount(resources, outpdf): pdfinfo = PdfInfo(resources / '3small.pdf') num_pages = len(pdfinfo) - with open(sidecar, 'r', encoding='utf-8') as f: + with open(sidecar, encoding='utf-8') as f: ocr_text = f.read() # There should a formfeed between each pair of pages, so the count of @@ -722,7 +722,7 @@ def test_sidecar_nonempty(resources, outpdf): 'tests/plugins/tesseract_cache.py', ) - with open(sidecar, 'r', encoding='utf-8') as f: + with open(sidecar, encoding='utf-8') as f: ocr_text = f.read() assert 'the' in ocr_text From 6f9b9480640f7b73a75db4aa8d8fe358c2b562ce Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Thu, 26 Aug 2021 23:46:44 -0700 Subject: [PATCH 054/106] typing: fix some trivial issues --- docs/conf.py | 2 +- src/ocrmypdf/_validation.py | 8 +++----- src/ocrmypdf/leptonica.py | 2 +- src/ocrmypdf/optimize.py | 1 + src/ocrmypdf/pluginspec.py | 2 +- 5 files changed, 7 insertions(+), 8 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 2df277f4..bc77b700 100755 --- a/docs/conf.py +++ b/docs/conf.py @@ -275,7 +275,7 @@ htmlhelp_basename = 'ocrmypdfdoc' # -- Options for LaTeX output --------------------------------------------- -latex_elements = { +latex_elements = { # type: ignore # The paper size ('letterpaper' or 'a4paper'). # # 'papersize': 'letterpaper', diff --git a/src/ocrmypdf/_validation.py b/src/ocrmypdf/_validation.py index cb55c454..5c595f5e 100644 --- a/src/ocrmypdf/_validation.py +++ b/src/ocrmypdf/_validation.py @@ -13,7 +13,7 @@ import sys import unicodedata from pathlib import Path from shutil import copyfileobj -from typing import List, Set, Tuple, Union +from typing import List, Set, Tuple import pikepdf import PIL @@ -182,10 +182,8 @@ def _pages_from_ranges(ranges: str) -> Set[int]: def check_options_ocr_behavior(options): exclusive_options = sum( - - (1 if opt else 0) - for opt in (options.force_ocr, options.skip_text, options.redo_ocr) - + (1 if opt else 0) + for opt in (options.force_ocr, options.skip_text, options.redo_ocr) ) if exclusive_options >= 2: raise BadArgsError("Choose only one of --force-ocr, --skip-text, --redo-ocr.") diff --git a/src/ocrmypdf/leptonica.py b/src/ocrmypdf/leptonica.py index 9f2554de..a053b43d 100644 --- a/src/ocrmypdf/leptonica.py +++ b/src/ocrmypdf/leptonica.py @@ -66,7 +66,7 @@ if os.name == 'nt': # Loading zlib from other places could cause a version mismatch _zlib_path = os.path.join(os.path.dirname(_libpath), 'zlib1.dll') if not os.path.exists(_zlib_path): - _zlib_path = find_library('zlib') + _zlib_path = find_library('zlib') or '' try: zlib = ffi.dlopen(_zlib_path) except ffi.error as e: diff --git a/src/ocrmypdf/optimize.py b/src/ocrmypdf/optimize.py index fce66464..0df62327 100644 --- a/src/ocrmypdf/optimize.py +++ b/src/ocrmypdf/optimize.py @@ -365,6 +365,7 @@ def convert_to_jbig2( When the JBIG2 symbolic coder is not used, each JBIG2 stands on its own and needs no dictionary. Currently this must be lossless JBIG2. """ + jbig2_globals_dict: Optional[Dictionary] _produce_jbig2_images(jbig2_groups, root, options, executor) diff --git a/src/ocrmypdf/pluginspec.py b/src/ocrmypdf/pluginspec.py index 47de64b6..c7db3e93 100644 --- a/src/ocrmypdf/pluginspec.py +++ b/src/ocrmypdf/pluginspec.py @@ -197,7 +197,7 @@ def rasterize_pdf_page( @hookspec(firstresult=True) -def filter_ocr_image(page: 'PageContext', image: 'Image') -> 'Image': +def filter_ocr_image(page: 'PageContext', image: Image.Image) -> Image.Image: """Called to filter the image before it is sent to OCR. This is the image that OCR sees, not what the user sees when they view the From 72279e77599517dcceeb4aee2efaff82418150b9 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Thu, 26 Aug 2021 23:47:11 -0700 Subject: [PATCH 055/106] typing: confirmed that _pages_from_ranges(not-str) is never used --- src/ocrmypdf/_validation.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/ocrmypdf/_validation.py b/src/ocrmypdf/_validation.py index 5c595f5e..f5ca30e6 100644 --- a/src/ocrmypdf/_validation.py +++ b/src/ocrmypdf/_validation.py @@ -142,8 +142,6 @@ def check_options_preprocessing(options): def _pages_from_ranges(ranges: str) -> Set[int]: - if is_iterable_notstr(ranges): - return set(ranges) pages: List[int] = [] page_groups = ranges.replace(' ', '').split(',') for g in page_groups: From 0956fc81aacb2b698234f2978c7ac07881409875 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Thu, 26 Aug 2021 23:47:53 -0700 Subject: [PATCH 056/106] typing: improvements for concurrency files --- src/ocrmypdf/builtin_plugins/concurrency.py | 18 ++++++++++-------- src/ocrmypdf/extra_plugins/semfree.py | 12 ++++++++---- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/src/ocrmypdf/builtin_plugins/concurrency.py b/src/ocrmypdf/builtin_plugins/concurrency.py index 797087ae..1fe34d8c 100644 --- a/src/ocrmypdf/builtin_plugins/concurrency.py +++ b/src/ocrmypdf/builtin_plugins/concurrency.py @@ -20,9 +20,8 @@ import signal import sys import threading from contextlib import suppress -from multiprocessing import Pool as ProcessPool -from multiprocessing.pool import ThreadPool -from typing import Callable, Iterable, Union +from multiprocessing.pool import Pool, ThreadPool +from typing import Callable, Iterable, Optional, Tuple, Type, Union from tqdm import tqdm @@ -31,7 +30,10 @@ from ocrmypdf._logging import TqdmConsole from ocrmypdf.exceptions import InputFileError from ocrmypdf.helpers import remove_all_log_handlers +ProcessPool = Pool Queue = Union[multiprocessing.Queue, queue.Queue] +UserInit = Callable[[], None] +WorkerInit = Callable[[Queue, UserInit, int], None] def log_listener(q: Queue): @@ -62,7 +64,7 @@ def process_sigbus(*args): raise InputFileError("A worker process lost access to an input file") -def process_init(q: Queue, user_init: Callable[[], None], loglevel): +def process_init(q: Queue, user_init: UserInit, loglevel) -> None: """Initialize a process pool worker""" # Ignore SIGINT (our parent process will kill us gracefully) @@ -85,7 +87,7 @@ def process_init(q: Queue, user_init: Callable[[], None], loglevel): return -def thread_init(_queue: Queue, user_init: Callable[[], None], _loglevel): +def thread_init(q: Queue, user_init: UserInit, loglevel) -> None: # As a thread, block SIGBUS so the main thread deals with it... with suppress(AttributeError): signal.pthread_sigmask(signal.SIG_BLOCK, {signal.SIGBUS}) @@ -107,9 +109,9 @@ class StandardExecutor(Executor): task_finished: Callable, ): if use_threads: - log_queue = queue.Queue(-1) - pool_class = ThreadPool - initializer = thread_init + log_queue: Queue = queue.Queue(-1) + pool_class: Type[Pool] = ThreadPool + initializer: WorkerInit = thread_init else: log_queue = multiprocessing.Queue(-1) pool_class = ProcessPool diff --git a/src/ocrmypdf/extra_plugins/semfree.py b/src/ocrmypdf/extra_plugins/semfree.py index c84b1b2d..186206d9 100644 --- a/src/ocrmypdf/extra_plugins/semfree.py +++ b/src/ocrmypdf/extra_plugins/semfree.py @@ -28,7 +28,7 @@ from enum import Enum, auto from itertools import islice, repeat, takewhile, zip_longest from multiprocessing import Pipe, Process from multiprocessing.connection import Connection, wait -from typing import Callable, Iterable, Iterator +from typing import Callable, Iterable, Iterator, List from ocrmypdf import Executor, hookimpl from ocrmypdf._concurrent import NullProgressBar @@ -60,7 +60,9 @@ def process_sigbus(*args): class ConnectionLogHandler(logging.handlers.QueueHandler): def __init__(self, conn: Connection) -> None: - super().__init__(None) + # sets the parent's queue to None - parent only touches queue + # in enqueue() which we override + super().__init__(None) # type: ignore self.conn = conn def enqueue(self, record): @@ -126,8 +128,8 @@ class LambdaExecutor(Executor): if not grouped_args: return - processes = [] - connections = [] + processes: List[Process] = [] + connections: List[Connection] = [] for chunk in grouped_args: parent_conn, child_conn = Pipe() @@ -152,6 +154,8 @@ class LambdaExecutor(Executor): with self.pbar_class(**tqdm_kwargs) as pbar: while connections: for r in wait(connections): + if not isinstance(r, Connection): + raise NotImplementedError("We only support Connection()") try: msg_type, msg = r.recv() except EOFError: From 9b81e76ed4114c0a1c664ef956a64f977da8b9dc Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Thu, 26 Aug 2021 23:48:27 -0700 Subject: [PATCH 057/106] typing: fix issues; avoid some magic literals --- src/ocrmypdf/pdfinfo/info.py | 42 ++++++++++++++++++++++-------------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/src/ocrmypdf/pdfinfo/info.py b/src/ocrmypdf/pdfinfo/info.py index 64b59b9c..4182a515 100644 --- a/src/ocrmypdf/pdfinfo/info.py +++ b/src/ocrmypdf/pdfinfo/info.py @@ -17,7 +17,7 @@ from functools import partial from math import hypot, inf, isclose from os import PathLike from pathlib import Path -from typing import Container, Iterator, Optional, Tuple, Union +from typing import Container, Dict, Iterator, List, Mapping, Optional, Tuple, Union from warnings import warn import pikepdf @@ -36,7 +36,7 @@ Encoding = Enum( 'Encoding', 'ccitt jpeg jpeg2000 jbig2 asciihex ascii85 lzw flate runlength' ) -FRIENDLY_COLORSPACE = { +FRIENDLY_COLORSPACE: Dict[str, Colorspace] = { '/DeviceGray': Colorspace.gray, '/CalGray': Colorspace.gray, '/DeviceRGB': Colorspace.rgb, @@ -54,7 +54,7 @@ FRIENDLY_COLORSPACE = { '/I': Colorspace.index, } -FRIENDLY_ENCODING = { +FRIENDLY_ENCODING: Dict[str, Encoding] = { '/CCITTFaxDecode': Encoding.ccitt, '/DCTDecode': Encoding.jpeg, '/JPXDecode': Encoding.jpeg2000, @@ -68,7 +68,7 @@ FRIENDLY_ENCODING = { '/RL': Encoding.runlength, } -FRIENDLY_COMP = { +FRIENDLY_COMP: Dict[Colorspace, int] = { Colorspace.gray: 1, Colorspace.rgb: 3, Colorspace.cmyk: 4, @@ -271,6 +271,9 @@ def _get_dpi(ctm_shorthand, image_size) -> Resolution: class ImageInfo: DPI_PREC = Decimal('1.000') + _comp: Optional[int] + _name: str + def __init__( self, *, @@ -303,14 +306,14 @@ class ImageInfo: self._bpc = int(pim.bits_per_component) try: - self._enc = FRIENDLY_ENCODING.get(pim.filters[0], 'image') + self._enc = FRIENDLY_ENCODING.get(pim.filters[0]) except IndexError: - self._enc = '?' + self._enc = None try: - self._color = FRIENDLY_COLORSPACE.get(pim.colorspace, '?') + self._color = FRIENDLY_COLORSPACE.get(pim.colorspace) except NotImplementedError: - self._color = '?' + self._color = None if self._enc == Encoding.jpeg2000: self._color = Colorspace.jpeg2000 @@ -324,11 +327,14 @@ class ImageInfo: else: self._comp = 3 else: - self._comp = FRIENDLY_COMP.get(self._color, '?') + if isinstance(self._color, Colorspace): + self._comp = FRIENDLY_COMP.get(self._color) + else: + self._comp = None # Bit of a hack... infer grayscale if component count is uncertain # but encoding only supports monochrome. - if self._comp == '?' and self._enc in (Encoding.ccitt, Encoding.jbig2): + if self._comp is None and self._enc in (Encoding.ccitt, Encoding.jbig2): self._comp = FRIENDLY_COMP[Colorspace.gray] @property @@ -353,15 +359,15 @@ class ImageInfo: @property def color(self): - return self._color + return self._color if self._color is not None else '?' @property def comp(self): - return self._comp + return self._comp if self._comp is not None else '?' @property def enc(self): - return self._enc + return self._enc if self._enc is not None else 'image' @property def renderable(self): @@ -661,6 +667,10 @@ def _pdf_pageinfo_concurrent( class PageInfo: + _has_text: Optional[bool] + _has_vector: Optional[bool] + _images: List[ImageInfo] + def __init__( self, pdf: Pdf, @@ -732,7 +742,7 @@ class PageInfo: else: self._has_vector = None # i.e. "no information" self._has_text = None - self._images = None + self._images = [] self._dpi = None if self._images: @@ -749,7 +759,7 @@ class PageInfo: @property def has_text(self) -> bool: - return self._has_text + return bool(self._has_text) @property def has_corrupt_text(self) -> bool: @@ -759,7 +769,7 @@ class PageInfo: @property def has_vector(self) -> bool: - return self._has_vector + return bool(self._has_vector) @property def width_inches(self) -> Decimal: From f2545d4496dd221d87f76fc221812c845f376f43 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Thu, 26 Aug 2021 23:51:11 -0700 Subject: [PATCH 058/106] typing: remove deprecated abstractstaticmethod --- src/ocrmypdf/pluginspec.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/ocrmypdf/pluginspec.py b/src/ocrmypdf/pluginspec.py index c7db3e93..1078eeaf 100644 --- a/src/ocrmypdf/pluginspec.py +++ b/src/ocrmypdf/pluginspec.py @@ -5,7 +5,7 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. -from abc import ABC, abstractmethod, abstractstaticmethod +from abc import ABC, abstractmethod from argparse import ArgumentParser, Namespace from collections import namedtuple from logging import Handler @@ -325,11 +325,13 @@ class OcrEngine(ABC): Tesseract OCR. """ - @abstractstaticmethod + @staticmethod + @abstractmethod def version() -> str: """Returns the version of the OCR engine.""" - @abstractstaticmethod + @staticmethod + @abstractmethod def creator_tag(options: Namespace) -> str: """Returns the creator tag to identify this software's role in creating the PDF. @@ -349,24 +351,28 @@ class OcrEngine(ABC): to the user, usually in an error message. """ - @abstractstaticmethod + @staticmethod + @abstractmethod def languages(options: Namespace) -> AbstractSet[str]: """Returns the set of all languages that are supported by the engine. Languages are typically given in 3-letter ISO 3166-1 codes, but actually can be any value understood by the OCR engine.""" - @abstractstaticmethod + @staticmethod + @abstractmethod def get_orientation(input_file: Path, options: Namespace) -> OrientationConfidence: """Returns the orientation of the image.""" - @abstractstaticmethod + @staticmethod + @abstractmethod def generate_hocr( input_file: Path, output_hocr: Path, output_text: Path, options: Namespace ) -> None: """Called to produce a hOCR file and sidecar text file.""" - @abstractstaticmethod + @staticmethod + @abstractmethod def generate_pdf( input_file: Path, output_pdf: Path, output_text: Path, options: Namespace ) -> None: From 53cd04799abedb6318e40d40c8b76cd94a2d6c7d Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Fri, 27 Aug 2021 00:06:38 -0700 Subject: [PATCH 059/106] typing: fix Pillow usage, leptonica --- src/ocrmypdf/_pipeline.py | 5 ++--- src/ocrmypdf/leptonica.py | 9 +++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/ocrmypdf/_pipeline.py b/src/ocrmypdf/_pipeline.py index ebf69295..be1b935d 100644 --- a/src/ocrmypdf/_pipeline.py +++ b/src/ocrmypdf/_pipeline.py @@ -521,13 +521,12 @@ def create_ocr_image(image: Path, page_context: PageContext): # be None) bbox = [float(v) for v in textarea] xyscale = tuple(float(coord) / 72.0 for coord in im.info['dpi']) - pixcoords = [ + pixcoords = ( bbox[0] * xyscale[0], im.height - bbox[3] * xyscale[1], bbox[2] * xyscale[0], im.height - bbox[1] * xyscale[1], - ] - pixcoords = [int(round(c)) for c in pixcoords] + ) log.debug('blanking %r', pixcoords) draw.rectangle(pixcoords, fill=white) # draw.rectangle(pixcoords, outline=pink) diff --git a/src/ocrmypdf/leptonica.py b/src/ocrmypdf/leptonica.py index a053b43d..211be3aa 100644 --- a/src/ocrmypdf/leptonica.py +++ b/src/ocrmypdf/leptonica.py @@ -22,6 +22,7 @@ from functools import lru_cache from io import BytesIO, UnsupportedOperation from os import fspath from tempfile import TemporaryFile +from typing import ContextManager, Type from warnings import warn from ocrmypdf.exceptions import MissingDependencyError @@ -85,7 +86,7 @@ except ffi.error as e: ) from e -class _LeptonicaErrorTrap_Redirect: +class _LeptonicaErrorTrap_Redirect(ContextManager): """ Context manager to trap errors reported by Leptonica < 1.79 or on Apple Silicon. @@ -131,7 +132,7 @@ class _LeptonicaErrorTrap_Redirect: except Exception: self.leptonica_lock.release() raise - return self + return def __exit__(self, exc_type, exc_value, traceback): # Restore old stderr @@ -171,7 +172,7 @@ tls = threading.local() tls.trap = None -class _LeptonicaErrorTrap_Queue: +class _LeptonicaErrorTrap_Queue(ContextManager): def __init__(self): self.queue = deque() @@ -225,7 +226,7 @@ except (ffi.error, MemoryError): # Pre-1.79 Leptonica does not have leptSetStderrHandler # And some platforms, notably Apple ARM 64, do not allow the write+execute # memory needed to set up the callback function. - _LeptonicaErrorTrap = _LeptonicaErrorTrap_Redirect + _LeptonicaErrorTrap: Type[ContextManager] = _LeptonicaErrorTrap_Redirect else: # 1.79 have this new symbol _LeptonicaErrorTrap = _LeptonicaErrorTrap_Queue From e402d5cb4b368980f07e63986d11b17e2e1751c9 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Fri, 27 Aug 2021 00:23:38 -0700 Subject: [PATCH 060/106] typing: fix path and numeric issues --- .pre-commit-config.yaml | 9 +++++++++ src/ocrmypdf/_exec/tesseract.py | 7 +++++-- src/ocrmypdf/api.py | 2 +- src/ocrmypdf/cli.py | 6 ++++-- 4 files changed, 19 insertions(+), 5 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ff10f765..eb7716ca 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -30,3 +30,12 @@ repos: hooks: - id: pyupgrade args: ["--py36-plus"] + - repo: https://github.com/pre-commit/mirrors-mypy + rev: v0.910 + hooks: + - id: mypy + additional_dependencies: + - types-toml + - types-setuptools + - types-requests + - types-Pillow diff --git a/src/ocrmypdf/_exec/tesseract.py b/src/ocrmypdf/_exec/tesseract.py index 25f09bbd..33ead41e 100644 --- a/src/ocrmypdf/_exec/tesseract.py +++ b/src/ocrmypdf/_exec/tesseract.py @@ -20,6 +20,7 @@ from typing import List, Optional from PIL import Image +from ocrmypdf.api import StrPath from ocrmypdf.exceptions import ( MissingDependencyError, SubprocessOutputError, @@ -250,7 +251,8 @@ def generate_hocr( # Reminder: test suite tesseract test plugins will break after any changes # to the number of order parameters here - args_tesseract.extend([input_file, prefix, 'hocr', 'txt'] + tessconfig) + args_tesseract.extend([os.fspath(input_file), os.fspath(prefix), 'hocr', 'txt']) + args_tesseract.extend(tessconfig) try: p = run(args_tesseract, stdout=PIPE, stderr=STDOUT, timeout=timeout, check=True) stdout = p.stdout @@ -324,7 +326,8 @@ def generate_pdf( # Reminder: test suite tesseract test plugins might break after any changes # to the number of order parameters here - args_tesseract.extend([input_file, prefix, 'pdf', 'txt'] + tessconfig) + args_tesseract.extend([os.fspath(input_file), os.fspath(prefix), 'pdf', 'txt']) + args_tesseract.extend(tessconfig) try: p = run(args_tesseract, stdout=PIPE, stderr=STDOUT, timeout=timeout, check=True) stdout = p.stdout diff --git a/src/ocrmypdf/api.py b/src/ocrmypdf/api.py index 65ab910b..de3561e4 100644 --- a/src/ocrmypdf/api.py +++ b/src/ocrmypdf/api.py @@ -31,7 +31,7 @@ except ModuleNotFoundError: coloredlogs = None -StrPath = Union[os.PathLike, AnyStr] +StrPath = Union[Path, AnyStr] PathOrIO = Union[BinaryIO, StrPath] _api_lock = threading.Lock() diff --git a/src/ocrmypdf/cli.py b/src/ocrmypdf/cli.py index 43ea1de7..199e0c41 100644 --- a/src/ocrmypdf/cli.py +++ b/src/ocrmypdf/cli.py @@ -6,7 +6,7 @@ import argparse -from typing import Optional, Type, TypeVar +from typing import Any, Callable, Optional, TypeVar from ocrmypdf._version import PROGRAM_NAME as _PROGRAM_NAME from ocrmypdf._version import __version__ as _VERSION @@ -14,7 +14,9 @@ from ocrmypdf._version import __version__ as _VERSION T = TypeVar('T') -def numeric(basetype: Type[T], min_: Optional[T] = None, max_: Optional[T] = None): +def numeric( + basetype: Callable[[Any], T], min_: Optional[T] = None, max_: Optional[T] = None +): """Validator for numeric params""" min_ = basetype(min_) if min_ is not None else None max_ = basetype(max_) if max_ is not None else None From 3764ee872a742329307fed0d0537bce9e2181187 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Fri, 27 Aug 2021 00:51:11 -0700 Subject: [PATCH 061/106] typing: refactor namedtuples in info --- src/ocrmypdf/pdfinfo/info.py | 46 +++++++++++++++++++++++++++--------- 1 file changed, 35 insertions(+), 11 deletions(-) diff --git a/src/ocrmypdf/pdfinfo/info.py b/src/ocrmypdf/pdfinfo/info.py index 4182a515..7264e80b 100644 --- a/src/ocrmypdf/pdfinfo/info.py +++ b/src/ocrmypdf/pdfinfo/info.py @@ -17,11 +17,21 @@ from functools import partial from math import hypot, inf, isclose from os import PathLike from pathlib import Path -from typing import Container, Dict, Iterator, List, Mapping, Optional, Tuple, Union +from typing import ( + Container, + Dict, + Iterator, + List, + Mapping, + NamedTuple, + Optional, + Tuple, + Union, +) from warnings import warn import pikepdf -from pikepdf import Object, Pdf, PdfMatrix +from pikepdf import Name, Object, Pdf, PdfInlineImage, PdfMatrix from ocrmypdf._concurrent import Executor, SerialExecutor from ocrmypdf.exceptions import EncryptedPdfError, InputFileError @@ -86,16 +96,30 @@ def _is_unit_square(shorthand): return all(isclose(a, b, rel_tol=1e-3) for a, b in pairwise) -XobjectSettings = namedtuple('XobjectSettings', ['name', 'shorthand', 'stack_depth']) +class XobjectSettings(NamedTuple): + name: str + shorthand: Tuple[float, float, float, float, float, float] + stack_depth: int -InlineSettings = namedtuple('InlineSettings', ['iimage', 'shorthand', 'stack_depth']) -ContentsInfo = namedtuple( - 'ContentsInfo', - ['xobject_settings', 'inline_images', 'found_vector', 'found_text', 'name_index'], -) +class InlineSettings(NamedTuple): + iimage: PdfInlineImage + shorthand: Tuple[float, float, float, float, float, float] + stack_depth: int -TextboxInfo = namedtuple('TextboxInfo', ['bbox', 'is_visible', 'is_corrupt']) + +class ContentsInfo(NamedTuple): + xobject_settings: List[XobjectSettings] + inline_images: List[InlineSettings] + found_vector: bool + found_text: bool + name_index: Mapping[Object, List[XobjectSettings]] + + +class TextboxInfo(NamedTuple): + bbox: Tuple[float, float, float, float] + is_visible: bool + is_corrupt: bool class VectorMarker: @@ -146,8 +170,8 @@ def _interpret_contents(contentstream: Object, initial_shorthand=UNIT_SQUARE): stack = [] ctm = PdfMatrix(initial_shorthand) - xobject_settings = [] - inline_images = [] + xobject_settings: List[XobjectSettings] = [] + inline_images: List[InlineSettings] = [] name_index = defaultdict(lambda: []) found_vector = False found_text = False From cb6c1939e90866e51fd38d6e7f83a1d554781c6c Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Fri, 27 Aug 2021 02:18:54 -0700 Subject: [PATCH 062/106] typing: fix runtime issues --- src/ocrmypdf/pluginspec.py | 2 +- tests/test_page_numbers.py | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/ocrmypdf/pluginspec.py b/src/ocrmypdf/pluginspec.py index 1078eeaf..8d485bf1 100644 --- a/src/ocrmypdf/pluginspec.py +++ b/src/ocrmypdf/pluginspec.py @@ -197,7 +197,7 @@ def rasterize_pdf_page( @hookspec(firstresult=True) -def filter_ocr_image(page: 'PageContext', image: Image.Image) -> Image.Image: +def filter_ocr_image(page: 'PageContext', image: 'Image.Image') -> 'Image.Image': """Called to filter the image before it is sent to OCR. This is the image that OCR sees, not what the user sees when they view the diff --git a/tests/test_page_numbers.py b/tests/test_page_numbers.py index 71f06a0d..d89a3bea 100644 --- a/tests/test_page_numbers.py +++ b/tests/test_page_numbers.py @@ -50,10 +50,6 @@ def test_nonmonotonic_warning(caplog): assert 'out of order' in caplog.text -def test_list_range(): - assert _pages_from_ranges([0, 1, 2]) == {0, 1, 2} - - def test_limited_pages(resources, outpdf): multi = resources / 'multipage.pdf' ocrmypdf.ocr( From 95d9e8d91a672c0097f4aeb6d6c789f6c0c79542 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Sat, 28 Aug 2021 00:18:14 -0700 Subject: [PATCH 063/106] info: inconsistent types used in ContentsInfo.name_index This broke PyPy but CPython is fine with it. --- src/ocrmypdf/pdfinfo/info.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ocrmypdf/pdfinfo/info.py b/src/ocrmypdf/pdfinfo/info.py index 7264e80b..4aeecb3e 100644 --- a/src/ocrmypdf/pdfinfo/info.py +++ b/src/ocrmypdf/pdfinfo/info.py @@ -113,7 +113,7 @@ class ContentsInfo(NamedTuple): inline_images: List[InlineSettings] found_vector: bool found_text: bool - name_index: Mapping[Object, List[XobjectSettings]] + name_index: Mapping[str, List[XobjectSettings]] class TextboxInfo(NamedTuple): @@ -209,7 +209,7 @@ def _interpret_contents(contentstream: Object, initial_shorthand=UNIT_SQUARE): name=image_name, shorthand=ctm.shorthand, stack_depth=len(stack) ) xobject_settings.append(settings) - name_index[image_name].append(settings) + name_index[str(image_name)].append(settings) elif operator == 'INLINE IMAGE': # BI/ID/EI are grouped into this iimage = operands[0] inline = InlineSettings( From b91096c615fcb4d82f7d3cc21d8e19fde52e6ef5 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Sat, 28 Aug 2021 02:11:33 -0700 Subject: [PATCH 064/106] hoctransform: fix deprecation warning --- src/ocrmypdf/hocrtransform.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ocrmypdf/hocrtransform.py b/src/ocrmypdf/hocrtransform.py index ea91b673..86145358 100755 --- a/src/ocrmypdf/hocrtransform.py +++ b/src/ocrmypdf/hocrtransform.py @@ -349,7 +349,7 @@ class HocrTransform: interword_spaces: bool, show_bounding_boxes: bool, ): - if not line: + if line is not None: return pxl_line_coords = self.element_coordinates(line) line_box = self.pt_from_pixel(pxl_line_coords) From 0a31acf888c06046024fe78c75d62ac0e842a6a3 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Tue, 31 Aug 2021 05:15:43 -0400 Subject: [PATCH 065/106] Allow pluggy v1. (#822) There are breaking changes, but I could not find any reference to them in the code. --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index d0ecf331..4a4b92b6 100644 --- a/setup.cfg +++ b/setup.cfg @@ -52,7 +52,7 @@ install_requires = pdfminer.six >= 20191110, != 20200720, <= 20201018 pikepdf >= 2.10.0 Pillow >= 8.2.0 - pluggy >= 0.13.0, < 1.0 + pluggy >= 0.13.0, < 2 reportlab >= 3.5.66 setuptools tqdm >= 4 From 4e4f0bfa1ffff829a2aa1a0c4c29cab97d5b8d86 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Tue, 31 Aug 2021 02:16:25 -0700 Subject: [PATCH 066/106] graft: use faster unparse_content_stream if available --- src/ocrmypdf/_graft.py | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/src/ocrmypdf/_graft.py b/src/ocrmypdf/_graft.py index 7b9a9a55..f1b69903 100644 --- a/src/ocrmypdf/_graft.py +++ b/src/ocrmypdf/_graft.py @@ -73,17 +73,24 @@ def strip_invisible_text(pdf, page): except AttributeError: return str(op).encode('ascii') - lines = [] + if hasattr(pikepdf, 'unparse_content_stream'): + content_stream = pikepdf.unparse_content_stream(stream) + else: + lines = [] - for operands, operator in stream: - if operator == pikepdf.Operator('INLINE IMAGE'): - iim = operands[0] - line = iim.unparse() - else: - line = b' '.join(convert(op) for op in operands) + b' ' + operator.unparse() - lines.append(line) + for operands, operator in stream: + if operator == pikepdf.Operator('INLINE IMAGE'): + iim = operands[0] + line = iim.unparse() + else: + line = ( + b' '.join(convert(op) for op in operands) + + b' ' + + operator.unparse() + ) + lines.append(line) - content_stream = b'\n'.join(lines) + content_stream = b'\n'.join(lines) page.Contents = pikepdf.Stream(pdf, content_stream) From c28858a09983595bc4080be3ca33a2bba8ac177d Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Tue, 31 Aug 2021 02:31:58 -0700 Subject: [PATCH 067/106] leptonica: fix a PyPy-specific error Error is: TypeError: from_buffer() got a 'memoryview' object, which supports the buffer interface but cannot be rendered as a plain raw address on PyPy PyPy is happy to access a bytes() copy of the memoryview. --- src/ocrmypdf/leptonica.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/ocrmypdf/leptonica.py b/src/ocrmypdf/leptonica.py index 211be3aa..e4814f1a 100644 --- a/src/ocrmypdf/leptonica.py +++ b/src/ocrmypdf/leptonica.py @@ -12,6 +12,7 @@ import argparse import logging import os +import platform import sys import threading from collections import deque @@ -439,6 +440,9 @@ class Pix(LeptonicaObject): bio = BytesIO() pillow_image.save(bio, format='png', compress_level=1) py_buffer = bio.getbuffer() + if platform.python_implementation() == 'PyPy': + # PyPy complains that it cannot do from_buffer(memoryview) + py_buffer = bytes(py_buffer) c_buffer = ffi.from_buffer(py_buffer) with _LeptonicaErrorTrap(): pix = Pix(lept.pixReadMem(c_buffer, len(c_buffer))) From 390b9924f5df1de8f3f81f50c7052ae8999364ca Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Tue, 31 Aug 2021 02:34:12 -0700 Subject: [PATCH 068/106] ci: Add PyPy to matrix --- .github/workflows/build.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2130dfe7..a4aa3178 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -29,6 +29,10 @@ jobs: python: 3.9 - os: ubuntu-latest python: 3.9 + - os: ubuntu-20.04 + python: "pypy-3.6" + - os: ubuntu-latest + python: "pypy-3.7" - os: ubuntu-latest python: 3.9 tesseract5: true From 1eb45de5c9a9d4133f2fdb9950d1db13c0a67060 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Tue, 31 Aug 2021 02:35:39 -0700 Subject: [PATCH 069/106] v12.4.0 release notes --- docs/release_notes.rst | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/release_notes.rst b/docs/release_notes.rst index 64d3bbc7..f56b9aa3 100644 --- a/docs/release_notes.rst +++ b/docs/release_notes.rst @@ -12,6 +12,18 @@ may be unreliable. Use the API to depend on precise behavior. The public API may be useful in scripts that launch OCRmyPDF processes or that wish to use some of its features for working with PDFs. +v12.4.0 +======= + +- When grafting text layers, use pikepdf's ``unparse_content_stream`` if available. +- Confirmed support for pluggy 1.0. (Thanks @QuLogic.) +- Fixed some typing issues, improved pre-commit settings, and fixed issues + flagged by linters. +- PyPy 7.3.3 (=Python 3.6) is now supported. Note that PyPy does not necessarily + run faster, because the vast majority of OCRmyPDF's execution time is spent + running OCR or generally executing native code. However, PyPy may bring speed + improvements in some areas. + v12.3.3 ======= From 9b4516af7a34900df3e63f85b2f2c202648db76f Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Tue, 31 Aug 2021 02:44:07 -0700 Subject: [PATCH 070/106] ci : add packages for PyPy build --- .github/workflows/build.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a4aa3178..a34c046b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -86,6 +86,14 @@ jobs: sudo apt-get install -y --no-install-recommends \ libexempi8 + - name: Install Ubuntu packages for PyPy + if: startsWith(matrix.python, 'pypy') + run: | + sudo apt-get install -y --no-install-recommends \ + libxml2-dev \ + libxslt1-dev \ + pypy3-dev + - name: Install Python packages run: | python -m pip install .[test] From 0b19b084e2890aff9597a371fd6ae66d9a996396 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 8 Sep 2021 00:03:35 -0700 Subject: [PATCH 071/106] build(deps): bump pillow from 8.2.0 to 8.3.2 in /requirements/main.txt (#825) Bumps [pillow](https://github.com/python-pillow/Pillow) from 8.2.0 to 8.3.2. - [Release notes](https://github.com/python-pillow/Pillow/releases) - [Changelog](https://github.com/python-pillow/Pillow/blob/master/CHANGES.rst) - [Commits](https://github.com/python-pillow/Pillow/compare/8.2.0...8.3.2) --- updated-dependencies: - dependency-name: pillow dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements/main.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/main.txt b/requirements/main.txt index 8f11afe0..f4a7865e 100644 --- a/requirements/main.txt +++ b/requirements/main.txt @@ -5,6 +5,6 @@ img2pdf == 0.4.0 pdfminer.six == 20201018 pikepdf == 2.10.0 pluggy == 0.13.1 -Pillow == 8.2.0 +Pillow == 8.3.2 reportlab == 3.5.66 tqdm == 4.59.0 From 9c5c7d9be0299655a46bc0ca9885e8067b525d33 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Wed, 8 Sep 2021 00:04:21 -0700 Subject: [PATCH 072/106] release notes: mention Py3.6 EOL --- docs/release_notes.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/release_notes.rst b/docs/release_notes.rst index f56b9aa3..37001812 100644 --- a/docs/release_notes.rst +++ b/docs/release_notes.rst @@ -12,6 +12,12 @@ may be unreliable. Use the API to depend on precise behavior. The public API may be useful in scripts that launch OCRmyPDF processes or that wish to use some of its features for working with PDFs. +.. note:: + + Python 3.6 reaches end of life on December 23, 2021. We will end support + for Python 3.6 around that time. The change will be marked with a major + release. + v12.4.0 ======= From f07d0c39bb2f12937907d1bb33d70185b60d4e46 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Thu, 9 Sep 2021 15:25:38 -0700 Subject: [PATCH 073/106] Require pikepdf<3 on PyPy 3.6 Because cibuildwheel does not build wheels for PyPy 3.6 anymore, so pikepdf does not offer one. --- setup.cfg | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.cfg b/setup.cfg index 7ca9b19b..34569e80 100644 --- a/setup.cfg +++ b/setup.cfg @@ -51,6 +51,7 @@ install_requires = img2pdf>=0.3.0,<0.5 # pure Python pdfminer.six!=20200720,>=20191110,<=20201018 pikepdf>=2.10.0 + pikepdf<3;implementation_name=="pypy" and python_version=='3.6' pluggy>=0.13.0,<2 reportlab>=3.5.66 setuptools From eb8992e58b6163189d523f478f8fe03daec8d746 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Thu, 9 Sep 2021 15:53:08 -0700 Subject: [PATCH 074/106] Update release notes --- docs/release_notes.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/release_notes.rst b/docs/release_notes.rst index 37001812..88d9006e 100644 --- a/docs/release_notes.rst +++ b/docs/release_notes.rst @@ -18,6 +18,13 @@ wish to use some of its features for working with PDFs. for Python 3.6 around that time. The change will be marked with a major release. +next +==== + +- Fixed build failure for the combination of PyPy 3.6 and pikepdf 3.0. This + combination can work in a source build but does not work with wheels. +- Accepted bot that wanted to upgrade our deprecated requirements.txt. + v12.4.0 ======= From f3de980447593dc2e2e7bc39e7e215e80f1e3ad0 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Mon, 13 Sep 2021 00:56:04 -0700 Subject: [PATCH 075/106] Introduce importlib-resources,metadata backports for Python < 3.9 --- setup.cfg | 2 ++ 1 file changed, 2 insertions(+) diff --git a/setup.cfg b/setup.cfg index 34569e80..277246c1 100644 --- a/setup.cfg +++ b/setup.cfg @@ -49,6 +49,8 @@ install_requires = cffi>=1.9.1 # must be a setup and install requirement coloredlogs>=14.0 # strictly optional img2pdf>=0.3.0,<0.5 # pure Python + importlib-metadata>=4;python_version<'3.8' + importlib-resources>=5;python_version<'3.9' pdfminer.six!=20200720,>=20191110,<=20201018 pikepdf>=2.10.0 pikepdf<3;implementation_name=="pypy" and python_version=='3.6' From 208657f840a58d8c740f8b0fcece8f9dc03971f7 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Mon, 13 Sep 2021 00:56:19 -0700 Subject: [PATCH 076/106] pdfa: replace pkg_resources with importlib.resources --- src/ocrmypdf/data/__init__.py | 8 ++++++++ src/ocrmypdf/pdfa.py | 16 ++++++++++------ 2 files changed, 18 insertions(+), 6 deletions(-) create mode 100644 src/ocrmypdf/data/__init__.py diff --git a/src/ocrmypdf/data/__init__.py b/src/ocrmypdf/data/__init__.py new file mode 100644 index 00000000..ad56a522 --- /dev/null +++ b/src/ocrmypdf/data/__init__.py @@ -0,0 +1,8 @@ +# © 2021 James R. Barlow: github.com/jbarlow83 +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +"""Data files used to generate certain PDFs.""" diff --git a/src/ocrmypdf/pdfa.py b/src/ocrmypdf/pdfa.py index 4eaee8f1..36592e79 100644 --- a/src/ocrmypdf/pdfa.py +++ b/src/ocrmypdf/pdfa.py @@ -10,16 +10,20 @@ Utilities for PDF/A production and confirmation with Ghostspcript. """ import base64 +import importlib.resources from pathlib import Path from typing import Dict, Iterator, Union import pikepdf -import pkg_resources +import pkg_resources # deprecated +# Deprecated ICC_PROFILE_RELPATH = 'data/sRGB.icc' - +# Deprecated SRGB_ICC_PROFILE = pkg_resources.resource_filename('ocrmypdf', ICC_PROFILE_RELPATH) +SRGB_ICC_PROFILE_NAME = 'sRGB.icc' + def _postscript_objdef( alias: str, @@ -97,12 +101,12 @@ def generate_pdfa_ps(target_filename: Path, icc: str = 'sRGB'): References: Adobe PDFMARK Reference: https://www.adobe.com/content/dam/acom/en/devnet/acrobat/pdfs/pdfmark_reference.pdf """ - if icc == 'sRGB': - icc_profile = SRGB_ICC_PROFILE - else: + if icc != 'sRGB': raise NotImplementedError("Only supporting sRGB") - bytes_icc_profile = Path(icc_profile).read_bytes() + bytes_icc_profile = importlib.resources.read_binary( + 'ocrmypdf.data', SRGB_ICC_PROFILE_NAME + ) ps = '\n'.join(_make_postscript(icc, bytes_icc_profile, 3)) # We should have encoded everything to pure ASCII by this point, and From cc6e9cecc0dcf770da0ff2442d6c543787f1efe3 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Mon, 13 Sep 2021 01:02:39 -0700 Subject: [PATCH 077/106] Replace pkg_resources version lookup with importlib.metadata --- docs/conf.py | 5 ++--- src/ocrmypdf/_version.py | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index bc77b700..adfcb4d6 100755 --- a/docs/conf.py +++ b/docs/conf.py @@ -88,11 +88,10 @@ if on_rtd: ] sys.modules.update((mod_name, Mock()) for mod_name in MOCK_MODULES) - -from pkg_resources import get_distribution, DistributionNotFound +from importlib.metadata import version as package_version # The full version, including alpha/beta/rc tags. -release = get_distribution('ocrmypdf').version +release = package_version('ocrmypdf').version version = '.'.join(release.split('.')[:2]) diff --git a/src/ocrmypdf/_version.py b/src/ocrmypdf/_version.py index 6751fede..c788d761 100644 --- a/src/ocrmypdf/_version.py +++ b/src/ocrmypdf/_version.py @@ -5,9 +5,9 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. -import pkg_resources +from importlib.metadata import version as _package_version PROGRAM_NAME = 'ocrmypdf' # Official PEP 396 -__version__ = pkg_resources.get_distribution('ocrmypdf').version +__version__ = _package_version('ocrmypdf') From 8bfd46c80d8a1d7f3107ff8b08f00e86376fa422 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Mon, 13 Sep 2021 01:06:42 -0700 Subject: [PATCH 078/106] docs: update copyright --- docs/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index adfcb4d6..c23376e2 100755 --- a/docs/conf.py +++ b/docs/conf.py @@ -56,7 +56,7 @@ master_doc = 'index' # General information about the project. project = 'ocrmypdf' copyright = ( - '2020, James R. Barlow. Licensed under Creative Commons Attribution-ShareAlike 4.0.' + '2021, James R. Barlow. Licensed under Creative Commons Attribution-ShareAlike 4.0.' ) author = 'James R. Barlow' From 3534742ef933a7bab0d075b37fcb1124a9b2ef5e Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Mon, 13 Sep 2021 01:35:14 -0700 Subject: [PATCH 079/106] ci: Enable build of feature branches --- .github/workflows/build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a34c046b..23820f1b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -6,6 +6,7 @@ on: - master - ci - release/* + - feature/* tags: - v* paths-ignore: From 4d67812d51521267e403e595d9666320917f2283 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Tue, 14 Sep 2021 00:15:07 -0700 Subject: [PATCH 080/106] importlib helpers don't provide importlib.thing, but importlib_thing Fix everywhere. --- docs/conf.py | 2 +- setup.cfg | 4 ++-- src/ocrmypdf/_version.py | 2 +- src/ocrmypdf/pdfa.py | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index c23376e2..a17933cd 100755 --- a/docs/conf.py +++ b/docs/conf.py @@ -88,7 +88,7 @@ if on_rtd: ] sys.modules.update((mod_name, Mock()) for mod_name in MOCK_MODULES) -from importlib.metadata import version as package_version +from importlib_metadata import version as package_version # The full version, including alpha/beta/rc tags. release = package_version('ocrmypdf').version diff --git a/setup.cfg b/setup.cfg index 277246c1..2dbb9a1b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -49,8 +49,8 @@ install_requires = cffi>=1.9.1 # must be a setup and install requirement coloredlogs>=14.0 # strictly optional img2pdf>=0.3.0,<0.5 # pure Python - importlib-metadata>=4;python_version<'3.8' - importlib-resources>=5;python_version<'3.9' + importlib-metadata>=4 # until Python 3.8 + importlib-resources>=5 # until Python 3.9 pdfminer.six!=20200720,>=20191110,<=20201018 pikepdf>=2.10.0 pikepdf<3;implementation_name=="pypy" and python_version=='3.6' diff --git a/src/ocrmypdf/_version.py b/src/ocrmypdf/_version.py index c788d761..091eacf9 100644 --- a/src/ocrmypdf/_version.py +++ b/src/ocrmypdf/_version.py @@ -5,7 +5,7 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. -from importlib.metadata import version as _package_version +from importlib_metadata import version as _package_version PROGRAM_NAME = 'ocrmypdf' diff --git a/src/ocrmypdf/pdfa.py b/src/ocrmypdf/pdfa.py index 36592e79..cc34f906 100644 --- a/src/ocrmypdf/pdfa.py +++ b/src/ocrmypdf/pdfa.py @@ -10,10 +10,10 @@ Utilities for PDF/A production and confirmation with Ghostspcript. """ import base64 -import importlib.resources from pathlib import Path from typing import Dict, Iterator, Union +import importlib_resources import pikepdf import pkg_resources # deprecated @@ -104,7 +104,7 @@ def generate_pdfa_ps(target_filename: Path, icc: str = 'sRGB'): if icc != 'sRGB': raise NotImplementedError("Only supporting sRGB") - bytes_icc_profile = importlib.resources.read_binary( + bytes_icc_profile = importlib_resources.read_binary( 'ocrmypdf.data', SRGB_ICC_PROFILE_NAME ) ps = '\n'.join(_make_postscript(icc, bytes_icc_profile, 3)) From a4da05b66b1036a99d02f29ef8bae69a4f772a76 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Tue, 14 Sep 2021 00:24:18 -0700 Subject: [PATCH 081/106] docs: various fixes As suggested by @Chealer Closes #829, #830, #831, #832 --- docs/introduction.rst | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/docs/introduction.rst b/docs/introduction.rst index 6753735d..929a4242 100644 --- a/docs/introduction.rst +++ b/docs/introduction.rst @@ -2,7 +2,12 @@ Introduction ============ -OCRmyPDF is a Python 3 application and library that adds OCR layers to PDFs. +OCRmyPDF is an application and library that adds text "layers" to images +in PDFs, making scanned image PDFs searchable. It uses OCR to guess what text +is contained in images. It is written in Python. OCRmyPDF supports plugins +that allow customization of its processing steps, and is very tolerant of +PDFs that contain scanned images and "born digital" content that needs no +text recognition. About OCR ========= @@ -26,7 +31,7 @@ exactly. They contain `vector graphics `__ that can contain raster objects such as scanned images. Because PDFs can contain multiple pages (unlike many image formats) and can contain fonts -and text, it is a good formats for exchanging scanned documents. +and text, it is a good format for exchanging scanned documents. |image| @@ -35,9 +40,9 @@ have one image. Some scanners or scanning software will segment pages into monochromatic text and color regions for example, to improve the compression ratio and appearance of the page. -Rasterizing a PDF is the process of generating an image suitable for -display or analyzing with an OCR engine. OCR engines like Tesseract work -with images, not vector objects. +Rasterizing a PDF is the process of generating corresponding raster images. +OCR engines like Tesseract work with images, not scalable vector graphics +or mixed raster-vector-text graphics such as PDF. About PDF/A =========== @@ -76,7 +81,7 @@ OCRmyPDF analyzes each page of a PDF to determine the colorspace and resolution (DPI) needed to capture all of the information on that page without losing content. It uses `Ghostscript `__ to rasterize the page, and -then performs on OCR on the rasterized image to create an OCR "layer". +then performs on OCR the rasterized image to create an OCR "layer". The layer is then grafted back onto the original PDF. While one can use a program like Ghostscript or ImageMagick to get an @@ -84,9 +89,9 @@ image and put the image through Tesseract, that actually creates a new PDF and many details may be lost. OCRmyPDF can produce a minimally changed PDF as output. -OCRmyPDF also some image processing options like deskew which improve -the appearance of files and quality of OCR. When these are used, the OCR -layer is grafted onto the processed image instead. +OCRmyPDF also provides some image processing options, like deskew, which +improves the appearance of files and quality of OCR. When these are used, +the OCR layer is grafted onto the processed image instead. By default, OCRmyPDF produces archival PDFs – PDF/A, which are a stricter subset of PDF features designed for long term archives. If From ee1a7baae77e3393bc36e0b86d229b7e5e21448f Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Tue, 14 Sep 2021 00:28:42 -0700 Subject: [PATCH 082/106] requirements: drop setuptools With importlib_* we no longer setuptools's pkg_resources. --- setup.cfg | 1 - 1 file changed, 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 2dbb9a1b..42e362d5 100644 --- a/setup.cfg +++ b/setup.cfg @@ -56,7 +56,6 @@ install_requires = pikepdf<3;implementation_name=="pypy" and python_version=='3.6' pluggy>=0.13.0,<2 reportlab>=3.5.66 - setuptools tqdm>=4 python_requires = >=3.6 include_package_data = True From f6396fbaac7cde608664ec41c160fc209bea157a Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Tue, 14 Sep 2021 17:20:43 -0700 Subject: [PATCH 083/106] pre-commit: drop isort mirror --- .pre-commit-config.yaml | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index eb7716ca..b6a76e3a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -7,14 +7,11 @@ repos: - id: check-toml - id: check-yaml - id: debug-statements - - repo: https://github.com/asottile/seed-isort-config - rev: v2.2.0 - hooks: - - id: seed-isort-config - - repo: https://github.com/pre-commit/mirrors-isort - rev: v5.9.3 # pick the isort version you'd like to use from https://github.com/pre-commit/mirrors-isort/releases + - repo: https://github.com/pycqa/isort + rev: 5.9.3 hooks: - id: isort + args: ["--profile", "black"] - repo: https://github.com/psf/black rev: 21.7b0 hooks: From 585595a98e8a018eb37544291e227783bea206d9 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Tue, 14 Sep 2021 17:22:29 -0700 Subject: [PATCH 084/106] pre-commit: autoupdate --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b6a76e3a..20da1ef6 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: - id: isort args: ["--profile", "black"] - repo: https://github.com/psf/black - rev: 21.7b0 + rev: 21.9b0 hooks: - id: black language_version: python @@ -23,7 +23,7 @@ repos: hooks: - id: setup-cfg-fmt - repo: https://github.com/asottile/pyupgrade - rev: v2.24.0 + rev: v2.26.0 hooks: - id: pyupgrade args: ["--py36-plus"] From dfa4ce1612dbc6fe04a9d5f410dfa68444225b30 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Tue, 14 Sep 2021 17:22:41 -0700 Subject: [PATCH 085/106] pyproject: configure mypy --- pyproject.toml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 0ad451d3..49d9b760 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -71,4 +71,12 @@ norecursedirs = ["lib", ".pc", ".git", "venv", "output", "cache", "resources"] testpaths = ["tests"] addopts = "-n auto" markers = ["slow"] -filterwarnings = ["ignore:.*XMLParser.*:DeprecationWarning"] \ No newline at end of file +filterwarnings = ["ignore:.*XMLParser.*:DeprecationWarning"] + +[tool.mypy] + +[[tool.mypy.overrides]] +module = [ + 'pluggy', 'tqdm', 'coloredlogs', 'img2pdf', 'cffi', '_cffi_backend', 'pdfminer.*', 'reportlab.*' +] +ignore_missing_imports = true From f5053158d46ddfd5145356e33e411186f57f3afc Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Tue, 14 Sep 2021 17:23:09 -0700 Subject: [PATCH 086/106] hocrtransform: fix regression causing hocr text to be not rendered Fixes #828 --- src/ocrmypdf/hocrtransform.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ocrmypdf/hocrtransform.py b/src/ocrmypdf/hocrtransform.py index 86145358..90895b4b 100755 --- a/src/ocrmypdf/hocrtransform.py +++ b/src/ocrmypdf/hocrtransform.py @@ -349,7 +349,7 @@ class HocrTransform: interword_spaces: bool, show_bounding_boxes: bool, ): - if line is not None: + if line is None: return pxl_line_coords = self.element_coordinates(line) line_box = self.pt_from_pixel(pxl_line_coords) From 4634b3db55267c0759621c25bd100d5e7c7222ca Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Tue, 14 Sep 2021 17:26:07 -0700 Subject: [PATCH 087/106] v12.5.0 release notes --- docs/release_notes.rst | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/docs/release_notes.rst b/docs/release_notes.rst index 88d9006e..094bd9ca 100644 --- a/docs/release_notes.rst +++ b/docs/release_notes.rst @@ -18,12 +18,17 @@ wish to use some of its features for working with PDFs. for Python 3.6 around that time. The change will be marked with a major release. -next -==== +v12.5.0 +======= - Fixed build failure for the combination of PyPy 3.6 and pikepdf 3.0. This combination can work in a source build but does not work with wheels. - Accepted bot that wanted to upgrade our deprecated requirements.txt. +- Documentation updates. +- Replace pkg_resources and install dependency on setuptools with + importlib-metadata and importlib-resources. +- Fixed regression in hocrtransform causing text to be omitted when this + renderer was used. v12.4.0 ======= From b4b32a35b5d6957a534d05999ed454c9a588093f Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Wed, 15 Sep 2021 00:09:08 -0700 Subject: [PATCH 088/106] optimize: fix typing consistency --- src/ocrmypdf/optimize.py | 36 ++++++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/src/ocrmypdf/optimize.py b/src/ocrmypdf/optimize.py index 0df62327..9712983a 100644 --- a/src/ocrmypdf/optimize.py +++ b/src/ocrmypdf/optimize.py @@ -25,8 +25,16 @@ from typing import ( ) import img2pdf -import pikepdf -from pikepdf import Dictionary, Name, Object, Pdf, PdfImage +from pikepdf import ( + Dictionary, + Name, + Object, + ObjectStreamMode, + Pdf, + PdfImage, + Stream, + UnsupportedImageTypeError, +) from PIL import Image from ocrmypdf import leptonica @@ -63,7 +71,7 @@ def jpg_name(root: Path, xref: Xref) -> Path: def extract_image_filter( - pike: Pdf, root: Path, image: Object, xref: Xref + pike: Pdf, root: Path, image: Stream, xref: Xref ) -> Optional[Tuple[PdfImage, Tuple[Name, Object]]]: del pike # unused args del root @@ -104,7 +112,7 @@ def extract_image_filter( def extract_image_jbig2( - *, pike: pikepdf.Pdf, root: Path, image: Object, xref: Xref, options + *, pike: Pdf, root: Path, image: Stream, xref: Xref, options ) -> Optional[XrefExt]: del options # unused arg @@ -123,16 +131,16 @@ def extract_image_jbig2( # Showing the palette or ICC to jbig2enc will cause it to perform # colorspace transform to 1bpp, which will conflict the palette or # ICC if it exists. - colorspace = pim.obj.get(pikepdf.Name.ColorSpace, None) + colorspace = pim.obj.get(Name.ColorSpace, None) if colorspace is not None or pim.image_mask: try: # Set to DeviceGray temporarily; we already in 1 bpc. - pim.obj.ColorSpace = pikepdf.Name.DeviceGray + pim.obj.ColorSpace = Name.DeviceGray imgname = root / f'{xref:08d}' with imgname.open('wb') as f: ext = pim.extract_to(stream=f) imgname.rename(imgname.with_suffix(ext)) - except pikepdf.UnsupportedImageTypeError: + except UnsupportedImageTypeError: return None finally: # Restore image colorspace after temporarily setting it to DeviceGray @@ -145,7 +153,7 @@ def extract_image_jbig2( def extract_image_generic( - *, pike: Pdf, root: Path, image: PdfImage, xref: Xref, options + *, pike: Pdf, root: Path, image: Stream, xref: Xref, options ) -> Optional[XrefExt]: result = extract_image_filter(pike, root, image, xref) if result is None: @@ -178,7 +186,7 @@ def extract_image_generic( with imgname.open('wb') as f: ext = pim.extract_to(stream=f) imgname.rename(imgname.with_suffix(ext)) - except pikepdf.UnsupportedImageTypeError: + except UnsupportedImageTypeError: return None return XrefExt(xref, ext) elif ( @@ -374,7 +382,7 @@ def convert_to_jbig2( jbig2_symfile = root / (prefix + '.sym') if jbig2_symfile.exists(): jbig2_globals_data = jbig2_symfile.read_bytes() - jbig2_globals = pikepdf.Stream(pike, jbig2_globals_data) + jbig2_globals = Stream(pike, jbig2_globals_data) jbig2_globals_dict = Dictionary(JBIG2Globals=jbig2_globals) elif options.jbig2_page_group_size == 1: jbig2_globals_dict = None @@ -445,7 +453,7 @@ def _transcode_png(pike: Pdf, filename: Path, xref: Xref) -> bool: with output.open('wb') as f: img2pdf.convert(fspath(filename), outputstream=f) - with pikepdf.open(output) as pdf_image: + with Pdf.open(output) as pdf_image: foreign_image = next(pdf_image.pages[0].images.values()) local_image = pike.copy_foreign(foreign_image) @@ -544,7 +552,7 @@ def optimize( if options.jbig2_page_group_size == 0: options.jbig2_page_group_size = 10 if options.jbig2_lossy else 1 - with pikepdf.Pdf.open(input_file) as pike: + with Pdf.open(input_file) as pike: root = output_file.parent / 'images' root.mkdir(exist_ok=True) @@ -576,7 +584,7 @@ def optimize( if savings < 0: log.info("Image optimization did not improve the file - discarded") # We still need to save the file - with pikepdf.open(input_file) as pike: + with Pdf.open(input_file) as pike: pike.remove_unreferenced_resources() pike.save(output_file, **save_settings) else: @@ -623,7 +631,7 @@ def main(infile, outfile, level, jobs=1): dict( compress_streams=True, preserve_pdfa=True, - object_stream_mode=pikepdf.ObjectStreamMode.generate, + object_stream_mode=ObjectStreamMode.generate, ), ) copy(fspath(tmpout), fspath(outfile)) From 79fe7a0a856cd4c1133b639967bdfeb01de020a1 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Wed, 15 Sep 2021 00:25:06 -0700 Subject: [PATCH 089/106] graft: remove separate implementation of unparse_content_stream --- src/ocrmypdf/_graft.py | 88 ++++++++++++++++++------------------------ 1 file changed, 38 insertions(+), 50 deletions(-) diff --git a/src/ocrmypdf/_graft.py b/src/ocrmypdf/_graft.py index f1b69903..a59513ab 100644 --- a/src/ocrmypdf/_graft.py +++ b/src/ocrmypdf/_graft.py @@ -11,8 +11,19 @@ from contextlib import suppress from pathlib import Path from typing import Optional -import pikepdf -from pikepdf.objects import Dictionary, Name +from pikepdf import ( + Dictionary, + Name, + Object, + Operator, + Page, + Pdf, + PdfError, + PdfMatrix, + Stream, + parse_content_stream, + unparse_content_stream, +) log = logging.getLogger(__name__) MAX_REPLACE_PAGES = 100 @@ -47,51 +58,28 @@ def strip_invisible_text(pdf, page): render_mode = 0 text_objects = [] - rich_page = pikepdf.Page(page) + rich_page = Page(page) rich_page.contents_coalesce() - for operands, operator in pikepdf.parse_content_stream(page, ''): + for operands, operator in parse_content_stream(page, ''): if not in_text_obj: - if operator == pikepdf.Operator('BT'): + if operator == Operator('BT'): in_text_obj = True render_mode = 0 text_objects.append((operands, operator)) else: stream.append((operands, operator)) else: - if operator == pikepdf.Operator('Tr'): + if operator == Operator('Tr'): render_mode = operands[0] text_objects.append((operands, operator)) - if operator == pikepdf.Operator('ET'): + if operator == Operator('ET'): in_text_obj = False if render_mode != 3: stream.extend(text_objects) text_objects.clear() - def convert(op): - try: - return op.unparse() - except AttributeError: - return str(op).encode('ascii') - - if hasattr(pikepdf, 'unparse_content_stream'): - content_stream = pikepdf.unparse_content_stream(stream) - else: - lines = [] - - for operands, operator in stream: - if operator == pikepdf.Operator('INLINE IMAGE'): - iim = operands[0] - line = iim.unparse() - else: - line = ( - b' '.join(convert(op) for op in operands) - + b' ' - + operator.unparse() - ) - lines.append(line) - - content_stream = b'\n'.join(lines) - page.Contents = pikepdf.Stream(pdf, content_stream) + content_stream = unparse_content_stream(stream) + page.Contents = Stream(pdf, content_stream) class OcrGrafter: @@ -99,14 +87,14 @@ class OcrGrafter: self.context = context self.path_base = context.origin - self.pdf_base = pikepdf.open(self.path_base) + self.pdf_base = Pdf.open(self.path_base) self.font, self.font_key = None, None self.pdfinfo = context.pdfinfo self.output_file = context.get_path('graft_layers.pdf') self.procset = self.pdf_base.make_indirect( - pikepdf.Object.parse(b'[ /PDF /Text /ImageB /ImageC /ImageI ]') + Object.parse(b'[ /PDF /Text /ImageB /ImageC /ImageI ]') ) self.emplacements = 1 @@ -130,7 +118,7 @@ class OcrGrafter: # We are updating the old page with a rasterized PDF of the new # page (without changing objgen, to preserve references) log.debug("Emplacement update") - with pikepdf.open(image) as pdf_image: + with Pdf.open(path_image) as pdf_image: self.emplacements += 1 foreign_image_page = pdf_image.pages[0] self.pdf_base.pages.append(foreign_image_page) @@ -203,7 +191,7 @@ class OcrGrafter: self.pdf_base.save(next_file) self.pdf_base.close() - self.pdf_base = pikepdf.open(next_file) + self.pdf_base = Pdf.open(next_file) self.procset = self.pdf_base.pages[0].Resources.ProcSet self.font, self.font_key = None, None # Ensure we reacquire this information self.interim_count += 1 @@ -219,7 +207,7 @@ class OcrGrafter: font, font_key = None, None possible_font_names = ('/f-0-0', '/F1') try: - with pikepdf.open(text) as pdf_text: + with Pdf.open(text) as pdf_text: try: pdf_text_fonts = pdf_text.pages[0].Resources.get('/Font', {}) except (AttributeError, IndexError, KeyError): @@ -233,7 +221,7 @@ class OcrGrafter: if pdf_text_font: font = self.pdf_base.copy_foreign(pdf_text_font) return font, font_key - except (FileNotFoundError, pikepdf.PdfError): + except (FileNotFoundError, PdfError): # PdfError occurs if a 0-length file is written e.g. due to OCR timeout return None, None @@ -242,9 +230,9 @@ class OcrGrafter: *, page_num: int, textpdf: Path, - font: pikepdf.Object, - font_key: pikepdf.Object, - procset: pikepdf.Object, + font: Object, + font_key: Object, + procset: Object, text_rotation: int, strip_old_text: bool, ): @@ -255,7 +243,7 @@ class OcrGrafter: return # This is a pointer indicating a specific page in the base file - with pikepdf.open(textpdf) as pdf_text: + with Pdf.open(textpdf) as pdf_text: pdf_text_contents = pdf_text.pages[0].Contents.read_bytes() base_page = self.pdf_base.pages.p(page_num) @@ -270,13 +258,13 @@ class OcrGrafter: mediabox = [float(base_page.MediaBox[v]) for v in range(4)] wp, hp = mediabox[2] - mediabox[0], mediabox[3] - mediabox[1] - translate = pikepdf.PdfMatrix().translated(-wt / 2, -ht / 2) - untranslate = pikepdf.PdfMatrix().translated(wp / 2, hp / 2) - corner = pikepdf.PdfMatrix().translated(mediabox[0], mediabox[1]) + translate = PdfMatrix().translated(-wt / 2, -ht / 2) + untranslate = PdfMatrix().translated(wp / 2, hp / 2) + corner = PdfMatrix().translated(mediabox[0], mediabox[1]) # -rotation because the input is a clockwise angle and this formula # uses CCW text_rotation = -text_rotation % 360 - rotate = pikepdf.PdfMatrix().rotated(text_rotation) + rotate = PdfMatrix().rotated(text_rotation) # Because of rounding of DPI, we might get a text layer that is not # identically sized to the target page. Scale to adjust. Normally this @@ -287,7 +275,7 @@ class OcrGrafter: scale_y = hp / ht # log.debug('%r', scale_x, scale_y) - scale = pikepdf.PdfMatrix().scaled(scale_x, scale_y) + scale = PdfMatrix().scaled(scale_x, scale_y) # Translate the text so it is centered at (0, 0), rotate it there, adjust # for a size different between initial and text PDF, then untranslate, and @@ -310,14 +298,14 @@ class OcrGrafter: pdf_draw_xobj = ( (b'q %s cm\n' % ctm.encode()) + (b'%s Do\n' % text_xobj_name) + b'\nQ\n' ) - new_text_layer = pikepdf.Stream(self.pdf_base, pdf_draw_xobj) + new_text_layer = Stream(self.pdf_base, pdf_draw_xobj) if strip_old_text: strip_invisible_text(self.pdf_base, base_page) - if hasattr(pikepdf.Page, 'contents_add'): + if hasattr(Page, 'contents_add'): # pikepdf >= 2.14 adds this method and deprecates the one below - pikepdf.Page(base_page).contents_add(new_text_layer, prepend=True) + Page(base_page).contents_add(new_text_layer, prepend=True) else: # pikepdf < 2.14 base_page.page_contents_add( From 79fd8d01a510ca1a8e90090c5f4d57308b2c2408 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Wed, 15 Sep 2021 00:26:15 -0700 Subject: [PATCH 090/106] info: fix incorrect handling of inline images and other typing fixes --- src/ocrmypdf/pdfinfo/info.py | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/src/ocrmypdf/pdfinfo/info.py b/src/ocrmypdf/pdfinfo/info.py index 4aeecb3e..c977ce73 100644 --- a/src/ocrmypdf/pdfinfo/info.py +++ b/src/ocrmypdf/pdfinfo/info.py @@ -30,8 +30,14 @@ from typing import ( ) from warnings import warn -import pikepdf -from pikepdf import Name, Object, Pdf, PdfInlineImage, PdfMatrix +from pikepdf import ( + Object, + Pdf, + PdfImage, + PdfInlineImage, + PdfMatrix, + parse_content_stream, +) from ocrmypdf._concurrent import Executor, SerialExecutor from ocrmypdf.exceptions import EncryptedPdfError, InputFileError @@ -181,9 +187,7 @@ def _interpret_contents(contentstream: Object, initial_shorthand=UNIT_SQUARE): operator_whitelist = ' '.join(vector_ops | text_showing_ops | image_ops) for n, graphobj in enumerate( - _normalize_stack( - pikepdf.parse_content_stream(contentstream, operator_whitelist) - ) + _normalize_stack(parse_content_stream(contentstream, operator_whitelist)) ): operands, operator = graphobj if operator == 'q': @@ -303,18 +307,20 @@ class ImageInfo: *, name='', pdfimage: Optional[Object] = None, - inline: Optional[Object] = None, + inline: Optional[PdfInlineImage] = None, shorthand=None, ): self._name = str(name) self._shorthand = shorthand + pim: Union[PdfInlineImage, PdfImage] + if inline is not None: self._origin = 'inline' - pim = inline.iimage + pim = inline elif pdfimage is not None: self._origin = 'xobject' - pim = pikepdf.PdfImage(pdfimage) + pim = PdfImage(pdfimage) else: raise ValueError("Either pdfimage or inline must be set") self._width = pim.width @@ -335,7 +341,7 @@ class ImageInfo: self._enc = None try: - self._color = FRIENDLY_COLORSPACE.get(pim.colorspace) + self._color = FRIENDLY_COLORSPACE.get(pim.colorspace or '') except NotImplementedError: self._color = None if self._enc == Encoding.jpeg2000: @@ -418,7 +424,7 @@ def _find_inline_images(contentsinfo: ContentsInfo) -> Iterator[ImageInfo]: for n, inline in enumerate(contentsinfo.inline_images): yield ImageInfo( - name='inline-%02d' % n, shorthand=inline.shorthand, inline=inline + name='inline-%02d' % n, shorthand=inline.shorthand, inline=inline.iimage ) @@ -613,7 +619,7 @@ def _pdf_pageinfo_sync_init(pdf: Pdf, infile: Path, pdfminer_loglevel): # If the pdf is not opened, open a copy for our worker process to use if pdf is None: - worker_pdf = pikepdf.open(infile) + worker_pdf = Pdf.open(infile) def on_process_close(): worker_pdf.close() @@ -627,7 +633,7 @@ def _pdf_pageinfo_sync(args): pdf = thread_pdf if thread_pdf is not None else worker_pdf with ExitStack() as stack: if not pdf: # When called with SerialExecutor - pdf = stack.enter_context(pikepdf.open(infile)) + pdf = stack.enter_context(Pdf.open(infile)) page = PageInfo(pdf, pageno, infile, check_pages, detailed_analysis) return page @@ -888,7 +894,7 @@ class PdfInfo: if check_pages is None: check_pages = range(0, 1_000_000_000) - with pikepdf.open(infile) as pdf: + with Pdf.open(infile) as pdf: if pdf.is_encrypted: raise EncryptedPdfError() # Triggered by encryption with empty passwd self._pages = _pdf_pageinfo_concurrent( From 5629e960b90fd91d5825d13ea977061e65fc3e94 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Wed, 15 Sep 2021 00:27:32 -0700 Subject: [PATCH 091/106] v12.5.0 release notes update --- docs/release_notes.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/release_notes.rst b/docs/release_notes.rst index 094bd9ca..cea2ba1f 100644 --- a/docs/release_notes.rst +++ b/docs/release_notes.rst @@ -29,6 +29,7 @@ v12.5.0 importlib-metadata and importlib-resources. - Fixed regression in hocrtransform causing text to be omitted when this renderer was used. +- Fixed some typing errors. v12.4.0 ======= From 45736b7c2ba5f2d81163cae9884e31a7f46ab229 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Thu, 16 Sep 2021 16:03:39 -0700 Subject: [PATCH 092/106] cli: clarify text to more accurately describe behavior of --jbig2-lossy --- src/ocrmypdf/cli.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/ocrmypdf/cli.py b/src/ocrmypdf/cli.py index 199e0c41..eab0862f 100644 --- a/src/ocrmypdf/cli.py +++ b/src/ocrmypdf/cli.py @@ -340,8 +340,9 @@ Online documentation is located at: "Control how PDF is optimized after processing:" "0 - do not optimize; " "1 - do safe, lossless optimizations (default); " - "2 - do some lossy optimizations; " - "3 - do aggressive lossy optimizations (including lossy JBIG2)" + "2 - do lossy JPEG and JPEG2000 optimizations; " + "3 - do more aggressive lossy JPEG and JPEG2000 optimizations. " + "To enable lossy JBIG2, see --jbig2-lossy." ), ) optimizing.add_argument( @@ -379,7 +380,8 @@ Online documentation is located at: action='store_true', help=( "Enable JBIG2 lossy mode (better compression, not suitable for some " - "use cases - see documentation)." + "use cases - see documentation). Only takes effect if --optimize 1 or " + "higher is also enabled." ), ) optimizing.add_argument( From 9559f76fae59b4b28a52058aa1549e0e59aa3c95 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Sun, 19 Sep 2021 16:31:00 -0700 Subject: [PATCH 093/106] optimize: fix typo in debug msg --- src/ocrmypdf/optimize.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ocrmypdf/optimize.py b/src/ocrmypdf/optimize.py index 9712983a..bec39f6e 100644 --- a/src/ocrmypdf/optimize.py +++ b/src/ocrmypdf/optimize.py @@ -97,7 +97,7 @@ def extract_image_filter( return None # Don't mess with wide gamut images if filtdp[0] == Name.JPXDecode: - log.debug(f"Skipping JPEG2000 iamge, xref {xref}") + log.debug(f"Skipping JPEG2000 image, xref {xref}") return None # Don't do JPEG2000 if filtdp[0] == Name.CCITTFaxDecode and filtdp[1].get('/K', 0) >= 0: From c725bf79daf302b19ae2a4b037ef65e7e754ec19 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Tue, 21 Sep 2021 16:37:03 -0700 Subject: [PATCH 094/106] flake8 delinting --- misc/batch.py | 2 +- misc/synology.py | 2 +- setup.cfg | 6 ++++++ src/ocrmypdf/_exec/tesseract.py | 1 - src/ocrmypdf/_exec/unpaper.py | 4 ++-- src/ocrmypdf/_sync.py | 4 ++-- src/ocrmypdf/_validation.py | 9 ++------- src/ocrmypdf/api.py | 19 +++++++++++++++---- src/ocrmypdf/builtin_plugins/concurrency.py | 2 +- src/ocrmypdf/builtin_plugins/tesseract_ocr.py | 1 - src/ocrmypdf/helpers.py | 2 +- src/ocrmypdf/optimize.py | 5 ++++- src/ocrmypdf/pdfinfo/info.py | 9 ++++++--- src/ocrmypdf/subprocess/__init__.py | 1 - src/ocrmypdf/subprocess/_windows.py | 12 +++++------- tests/test_metadata.py | 2 +- 16 files changed, 47 insertions(+), 34 deletions(-) diff --git a/misc/batch.py b/misc/batch.py index fcd0e5cf..0b3793b2 100644 --- a/misc/batch.py +++ b/misc/batch.py @@ -52,7 +52,7 @@ logging.basicConfig( ocrmypdf.configure_logging(ocrmypdf.Verbosity.default) -for dir_name, subdirs, file_list in os.walk(start_dir): +for dir_name, _subdirs, file_list in os.walk(start_dir): logging.info(dir_name + '\n') os.chdir(dir_name) for filename in file_list: diff --git a/misc/synology.py b/misc/synology.py index 6e294ce1..7e243229 100644 --- a/misc/synology.py +++ b/misc/synology.py @@ -46,7 +46,7 @@ if len(sys.argv) > 1: else: start_dir = '.' -for dir_name, subdirs, file_list in os.walk(start_dir): +for dir_name, _subdirs, file_list in os.walk(start_dir): logging.info(dir_name) os.chdir(dir_name) for filename in file_list: diff --git a/setup.cfg b/setup.cfg index 42e362d5..a23304f7 100644 --- a/setup.cfg +++ b/setup.cfg @@ -107,3 +107,9 @@ test = pytest [check-manifest] ignore = .github + +[flake8] +ignore = D203,F401,W503,E501,E203,F841 +exclude = .git,__pycache__,docs/conf.py,build,dist,.venv,.venvpp,.eggs,tmp,src/ocrmypdf/lib/ +max-complexity = 10 +max-line-length = 100 \ No newline at end of file diff --git a/src/ocrmypdf/_exec/tesseract.py b/src/ocrmypdf/_exec/tesseract.py index 33ead41e..79374670 100644 --- a/src/ocrmypdf/_exec/tesseract.py +++ b/src/ocrmypdf/_exec/tesseract.py @@ -20,7 +20,6 @@ from typing import List, Optional from PIL import Image -from ocrmypdf.api import StrPath from ocrmypdf.exceptions import ( MissingDependencyError, SubprocessOutputError, diff --git a/src/ocrmypdf/_exec/unpaper.py b/src/ocrmypdf/_exec/unpaper.py index 3c3ae72c..aec365c2 100644 --- a/src/ocrmypdf/_exec/unpaper.py +++ b/src/ocrmypdf/_exec/unpaper.py @@ -96,12 +96,12 @@ def run( try: with Image.open(output_pnm) as imout: imout.save(output_file, dpi=(dpi, dpi)) - except (FileNotFoundError, OSError): + except OSError as e: raise SubprocessOutputError( "unpaper: failed to produce the expected output file. " + " Called with: " + str(args_unpaper) - ) from None + ) from e def validate_custom_args(args: str) -> List[str]: diff --git a/src/ocrmypdf/_sync.py b/src/ocrmypdf/_sync.py index 303b0cf6..b6611595 100644 --- a/src/ocrmypdf/_sync.py +++ b/src/ocrmypdf/_sync.py @@ -399,7 +399,7 @@ def run_pipeline(options, *, plugin_manager, api=False): return ExitCode.invalid_output_pdf report_output_file_size(options, start_input_file, options.output_file) - except (KeyboardInterrupt if not api else NeverRaise) as e: + except (KeyboardInterrupt if not api else NeverRaise): if options.verbose >= 1: log.exception("KeyboardInterrupt") else: @@ -413,7 +413,7 @@ def run_pipeline(options, *, plugin_manager, api=False): else: log.error(type(e).__name__) return e.exit_code - except (Exception if not api else NeverRaise) as e: # pylint: disable=broad-except + except (Exception if not api else NeverRaise): # pylint: disable=broad-except log.exception("An exception occurred while executing the pipeline") return ExitCode.other_error finally: diff --git a/src/ocrmypdf/_validation.py b/src/ocrmypdf/_validation.py index f5ca30e6..47d97680 100644 --- a/src/ocrmypdf/_validation.py +++ b/src/ocrmypdf/_validation.py @@ -26,12 +26,7 @@ from ocrmypdf.exceptions import ( MissingDependencyError, OutputFileAccessError, ) -from ocrmypdf.helpers import ( - is_file_writable, - is_iterable_notstr, - monotonic, - safe_symlink, -) +from ocrmypdf.helpers import is_file_writable, monotonic, safe_symlink from ocrmypdf.hocrtransform import HOCR_OK_LANGS from ocrmypdf.subprocess import check_external_program @@ -68,7 +63,7 @@ def check_options_languages(options, ocr_engine_languages): missing_languages = options.languages - ocr_engine_languages if missing_languages: msg = ( - f"OCR engine does not have language data for the following " + "OCR engine does not have language data for the following " "requested languages: \n" ) msg += '\n'.join(lang for lang in missing_languages) diff --git a/src/ocrmypdf/api.py b/src/ocrmypdf/api.py index de3561e4..8f731fa3 100644 --- a/src/ocrmypdf/api.py +++ b/src/ocrmypdf/api.py @@ -15,10 +15,7 @@ from pathlib import Path from typing import AnyStr, BinaryIO, Iterable, Optional, Union from warnings import warn -from ocrmypdf._logging import ( # pylint: disable=unused-import - PageNumberFilter, - TqdmConsole, -) +from ocrmypdf._logging import PageNumberFilter, TqdmConsole from ocrmypdf._plugin_manager import get_plugin_manager from ocrmypdf._sync import run_pipeline from ocrmypdf._validation import check_options @@ -338,3 +335,17 @@ def ocr( # pylint: disable=unused-argument options = create_options(**create_options_kwargs) check_options(options, plugin_manager) return run_pipeline(options=options, plugin_manager=plugin_manager, api=True) + + +__all__ = [ + 'PageNumberFilter', + 'TqdmConsole', + 'Verbosity', + 'check_options', + 'configure_logging', + 'create_options', + 'get_parser', + 'get_plugin_manager', + 'ocr', + 'run_pipeline', +] diff --git a/src/ocrmypdf/builtin_plugins/concurrency.py b/src/ocrmypdf/builtin_plugins/concurrency.py index 1fe34d8c..74e6b504 100644 --- a/src/ocrmypdf/builtin_plugins/concurrency.py +++ b/src/ocrmypdf/builtin_plugins/concurrency.py @@ -21,7 +21,7 @@ import sys import threading from contextlib import suppress from multiprocessing.pool import Pool, ThreadPool -from typing import Callable, Iterable, Optional, Tuple, Type, Union +from typing import Callable, Iterable, Type, Union from tqdm import tqdm diff --git a/src/ocrmypdf/builtin_plugins/tesseract_ocr.py b/src/ocrmypdf/builtin_plugins/tesseract_ocr.py index a6973b01..f4f45639 100644 --- a/src/ocrmypdf/builtin_plugins/tesseract_ocr.py +++ b/src/ocrmypdf/builtin_plugins/tesseract_ocr.py @@ -11,7 +11,6 @@ import os from ocrmypdf import hookimpl from ocrmypdf._exec import tesseract from ocrmypdf.cli import numeric -from ocrmypdf.exceptions import MissingDependencyError from ocrmypdf.helpers import clamp from ocrmypdf.pluginspec import OcrEngine from ocrmypdf.subprocess import check_external_program diff --git a/src/ocrmypdf/helpers.py b/src/ocrmypdf/helpers.py index 3f012ef1..7ff2ea71 100644 --- a/src/ocrmypdf/helpers.py +++ b/src/ocrmypdf/helpers.py @@ -226,7 +226,7 @@ def check_pdf(input_file: Path) -> bool: except ( # Workaround for a problematic pikepdf version # pragma: no cover - getattr(pikepdf, 'ForeignObjectError') + pikepdf.ForeignObjectError if pikepdf.__version__ == '2.1.0' else NeverRaise ): diff --git a/src/ocrmypdf/optimize.py b/src/ocrmypdf/optimize.py index bec39f6e..55a8841a 100644 --- a/src/ocrmypdf/optimize.py +++ b/src/ocrmypdf/optimize.py @@ -533,12 +533,15 @@ def transcode_pngs( _transcode_png(pike, filename, xref) +DEFAULT_EXECUTOR = SerialExecutor() + + def optimize( input_file: Path, output_file: Path, context, save_settings, - executor: Executor = SerialExecutor(), + executor: Executor = DEFAULT_EXECUTOR, ) -> None: options = context.options if options.optimize == 0: diff --git a/src/ocrmypdf/pdfinfo/info.py b/src/ocrmypdf/pdfinfo/info.py index c977ce73..b656e107 100644 --- a/src/ocrmypdf/pdfinfo/info.py +++ b/src/ocrmypdf/pdfinfo/info.py @@ -9,7 +9,7 @@ import atexit import logging import re -from collections import defaultdict, namedtuple +from collections import defaultdict from contextlib import ExitStack from decimal import Decimal from enum import Enum @@ -449,7 +449,7 @@ def _image_xobjects(container) -> Iterator[Tuple[Object, str]]: xobjs = resources['/XObject'].as_dict() for xobj in xobjs: candidate: Object = xobjs[xobj] - if not '/Subtype' in candidate: + if '/Subtype' not in candidate: continue if candidate['/Subtype'] == '/Image': pdfimage = candidate @@ -877,6 +877,9 @@ class PageInfo: ) +DEFAULT_EXECUTOR = SerialExecutor() + + class PdfInfo: """Get summary information about a PDF""" @@ -888,7 +891,7 @@ class PdfInfo: progbar: bool = False, max_workers: int = None, check_pages=None, - executor: Executor = SerialExecutor(), + executor: Executor = DEFAULT_EXECUTOR, ): self._infile = infile if check_pages is None: diff --git a/src/ocrmypdf/subprocess/__init__.py b/src/ocrmypdf/subprocess/__init__.py index 99f052c6..28604648 100644 --- a/src/ocrmypdf/subprocess/__init__.py +++ b/src/ocrmypdf/subprocess/__init__.py @@ -15,7 +15,6 @@ from collections.abc import Mapping from contextlib import suppress from distutils.version import LooseVersion, Version from functools import lru_cache -from pathlib import Path from subprocess import PIPE, STDOUT, CalledProcessError, CompletedProcess, Popen from subprocess import run as subprocess_run from typing import Callable, Optional, Type, Union diff --git a/src/ocrmypdf/subprocess/_windows.py b/src/ocrmypdf/subprocess/_windows.py index aec082b7..d1cc31e0 100644 --- a/src/ocrmypdf/subprocess/_windows.py +++ b/src/ocrmypdf/subprocess/_windows.py @@ -9,9 +9,9 @@ import os import shutil import sys from distutils.version import LooseVersion -from itertools import chain, filterfalse +from itertools import chain from pathlib import Path -from typing import Any, Callable, Iterator, Optional, Tuple, TypeVar, cast +from typing import Any, Callable, Iterable, Iterator, Set, Tuple, TypeVar try: import winreg @@ -137,14 +137,12 @@ def fix_windows_args(program, args, env): return args -def unique_everseen(iterable, key=None): - "List unique elements, preserving order. Remember all elements ever seen." +def unique_everseen(iterable: Iterable[T], key: Callable[[T], T]) -> Iterator[T]: + "List unique elements, preserving order." # unique_everseen('AAAABBBCCDAABBB') --> A B C D # unique_everseen('ABBCcAD', str.lower) --> A B C D - seen = set() + seen: Set[T] = set() seen_add = seen.add - if key is None: - key = lambda x: x for element in iterable: k = key(element) if k not in seen: diff --git a/tests/test_metadata.py b/tests/test_metadata.py index ebcbf031..cea73dd7 100644 --- a/tests/test_metadata.py +++ b/tests/test_metadata.py @@ -287,7 +287,7 @@ def test_srgb_in_unicode_path(tmp_path): def test_kodak_toc(resources, outpdf): - _output = check_ocrmypdf( + check_ocrmypdf( resources / 'kcs.pdf', outpdf, '--output-type', From ec311af796771f76ff858140f8392e048f68fe74 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Wed, 22 Sep 2021 17:18:59 -0700 Subject: [PATCH 095/106] typing: subprocess --- src/ocrmypdf/subprocess/__init__.py | 12 +++++++++--- src/ocrmypdf/subprocess/_windows.py | 2 +- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/ocrmypdf/subprocess/__init__.py b/src/ocrmypdf/subprocess/__init__.py index 28604648..7cc153b5 100644 --- a/src/ocrmypdf/subprocess/__init__.py +++ b/src/ocrmypdf/subprocess/__init__.py @@ -26,7 +26,9 @@ from ocrmypdf.exceptions import MissingDependencyError log = logging.getLogger(__name__) -def run(args, *, env=None, logs_errors_to_stdout=False, **kwargs): +def run( + args, *, env=None, logs_errors_to_stdout: bool = False, **kwargs +) -> CompletedProcess: """Wrapper around :py:func:`subprocess.run` The main purpose of this wrapper is to log subprocess output in an orderly @@ -64,7 +66,9 @@ def run(args, *, env=None, logs_errors_to_stdout=False, **kwargs): return proc -def run_polling_stderr(args, *, callback, check=False, env=None, **kwargs): +def run_polling_stderr( + args, *, callback: Callable[[str], None], check: bool = False, env=None, **kwargs +) -> CompletedProcess: """Run a process like ``ocrmypdf.subprocess.run``, and poll stderr. Every line of produced by stderr will be forwarded to the callback function. @@ -82,6 +86,8 @@ def run_polling_stderr(args, *, callback, check=False, env=None, **kwargs): with Popen(args, env=env, **kwargs) as proc: lines = [] while proc.poll() is None: + if proc.stderr is None: + continue for msg in iter(proc.stderr.readline, ''): if process_log.isEnabledFor(logging.DEBUG): process_log.debug(msg.strip()) @@ -101,7 +107,7 @@ def _fix_process_args(args, env, kwargs): env = os.environ # Search in spoof path if necessary - program = args[0] + program = str(args[0]) if os.name == 'nt': from ocrmypdf.subprocess._windows import fix_windows_args diff --git a/src/ocrmypdf/subprocess/_windows.py b/src/ocrmypdf/subprocess/_windows.py index d1cc31e0..c3130a7d 100644 --- a/src/ocrmypdf/subprocess/_windows.py +++ b/src/ocrmypdf/subprocess/_windows.py @@ -113,7 +113,7 @@ SHIMS = [ ] -def fix_windows_args(program, args, env): +def fix_windows_args(program: str, args, env): """Adjust our desired program and command line arguments for use on Windows""" if sys.version_info < (3, 8): From 790d3022f68acfc6b9b4247b0593e6f956621fdd Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Sun, 26 Sep 2021 01:07:34 -0700 Subject: [PATCH 096/106] Implement --output-type=none to skip producing the PDF and use only the sidecar Closes #787 --- misc/completion/ocrmypdf.fish | 1 + src/ocrmypdf/_sync.py | 15 ++++++++------- src/ocrmypdf/_validation.py | 18 ++++++++++++++---- src/ocrmypdf/cli.py | 5 +++-- tests/conftest.py | 5 +++++ tests/test_main.py | 26 ++++++++++++++++++++++++++ tests/test_validation.py | 6 ++++++ 7 files changed, 63 insertions(+), 13 deletions(-) diff --git a/misc/completion/ocrmypdf.fish b/misc/completion/ocrmypdf.fish index d085acdd..d4bb76d3 100644 --- a/misc/completion/ocrmypdf.fish +++ b/misc/completion/ocrmypdf.fish @@ -54,6 +54,7 @@ function __fish_ocrmypdf_output_type echo -e "pdfa-1\t"(_ "output a PDF/A-1b") echo -e "pdfa-2\t"(_ "output a PDF/A-2b") echo -e "pdfa-3\t"(_ "output a PDF/A-3b") + echo -e "none\t"(_ "do not produce an output PDF (for example, if you only care about --sidecar)") end complete -c ocrmypdf -x -l output-type -a '(__fish_ocrmypdf_output_type)' -d "select PDF output options" diff --git a/src/ocrmypdf/_sync.py b/src/ocrmypdf/_sync.py index b6611595..31693f9e 100644 --- a/src/ocrmypdf/_sync.py +++ b/src/ocrmypdf/_sync.py @@ -290,15 +290,16 @@ def exec_concurrent(context: PdfContext, executor: Executor): # Copy text file to destination copy_final(text, options.sidecar, context) - # Merge layers to one single pdf - pdf = ocrgraft.finalize() + if options.output_type != 'none': + # Merge layers to one single pdf + pdf = ocrgraft.finalize() - # PDF/A and metadata - log.info("Postprocessing...") - pdf = post_process(pdf, context, executor) + # PDF/A and metadata + log.info("Postprocessing...") + pdf = post_process(pdf, context, executor) - # Copy PDF file to destination - copy_final(pdf, options.output_file, context) + # Copy PDF file to destination + copy_final(pdf, options.output_file, context) def configure_debug_logging(log_filename: Path, prefix: str = ''): diff --git a/src/ocrmypdf/_validation.py b/src/ocrmypdf/_validation.py index 47d97680..15c45226 100644 --- a/src/ocrmypdf/_validation.py +++ b/src/ocrmypdf/_validation.py @@ -26,7 +26,7 @@ from ocrmypdf.exceptions import ( MissingDependencyError, OutputFileAccessError, ) -from ocrmypdf.helpers import is_file_writable, monotonic, safe_symlink +from ocrmypdf.helpers import is_file_writable, monotonic, safe_symlink, samefile from ocrmypdf.hocrtransform import HOCR_OK_LANGS from ocrmypdf.subprocess import check_external_program @@ -75,12 +75,18 @@ def check_options_output(options): is_latin = options.languages.issubset(HOCR_OK_LANGS) if options.pdf_renderer.startswith('hocr') and not is_latin: - msg = ( + log.warning( "The 'hocr' PDF renderer is known to cause problems with one " "or more of the languages in your document. Use " - "--pdf-renderer auto (the default) to avoid this issue." + "`--pdf-renderer auto` (the default) to avoid this issue." + ) + + if options.output_type == 'none' and options.output_file != os.devnull: + raise BadArgsError( + "Since you specified `--pdf-renderer none`, the output file " + f"{options.output_file} cannot be produced. Set the output file to " + f"{os.devnull} to suppress this message." ) - log.warning(msg) lossless_reconstruction = False if not any( @@ -107,6 +113,10 @@ def check_options_sidecar(options): raise BadArgsError( "--sidecar filename must be specified when output file is stdout." ) + elif options.output_file == os.devnull: + raise BadArgsError( + "--sidecar filename must be specified when output file is /dev/null or NUL." + ) options.sidecar = options.output_file + '.txt' if options.sidecar == options.input_file or options.sidecar == options.output_file: raise BadArgsError( diff --git a/src/ocrmypdf/cli.py b/src/ocrmypdf/cli.py index eab0862f..87012321 100644 --- a/src/ocrmypdf/cli.py +++ b/src/ocrmypdf/cli.py @@ -147,7 +147,7 @@ Online documentation is located at: ) parser.add_argument( '--output-type', - choices=['pdfa', 'pdf', 'pdfa-1', 'pdfa-2', 'pdfa-3'], + choices=['pdfa', 'pdf', 'pdfa-1', 'pdfa-2', 'pdfa-3', 'none'], default='pdfa', help="Choose output type. 'pdfa' creates a PDF/A-2b compliant file for " "long term archiving (default, recommended) but may not suitable " @@ -155,7 +155,8 @@ Online documentation is located at: "also has problems with full Unicode text. 'pdf' attempts to " "preserve file contents as much as possible. 'pdf-a1' creates a " "PDF/A1-b file. 'pdf-a2' is equivalent to 'pdfa'. 'pdf-a3' creates a " - "PDF/A3-b file.", + "PDF/A3-b file. 'none' will produce no output, which may be helpful if " + "only the --sidecar is desired.", ) # Use null string '\0' as sentinel to indicate the user supplied no argument, diff --git a/tests/conftest.py b/tests/conftest.py index 9a059c83..f7874255 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -69,6 +69,11 @@ def outpdf(tmp_path): return tmp_path / 'out.pdf' +@pytest.fixture(scope="function") +def outtxt(tmp_path): + return tmp_path / 'out.txt' + + @pytest.fixture(scope="function") def no_outpdf(tmp_path): """This just documents the fact that a test is not expected to produce diff --git a/tests/test_main.py b/tests/test_main.py index 0bd69ae9..92e1c97f 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -881,3 +881,29 @@ def test_image_dpi_threshold(resources, outpdf): 'tests/plugins/tesseract_noop.py', ) assert outpdf.exists() + + +def test_outputtype_none_bad_setup(resources, outpdf): + p, _out, err = run_ocrmypdf( + resources / 'trivial.pdf', + outpdf, + '--output-type=none', + '--plugin', + 'tests/plugins/tesseract_noop.py', + ) + assert p.returncode == ExitCode.bad_args + assert 'Set the output file to' in err + + +def test_outputtype_none(resources, outtxt): + p, _out, err = run_ocrmypdf( + resources / 'trivial.pdf', + os.devnull, + '--output-type=none', + '--sidecar', + outtxt, + '--plugin', + 'tests/plugins/tesseract_noop.py', + ) + assert p.returncode == ExitCode.ok + assert outtxt.exists() diff --git a/tests/test_validation.py b/tests/test_validation.py index a027e17a..fdd5080d 100644 --- a/tests/test_validation.py +++ b/tests/test_validation.py @@ -6,6 +6,7 @@ import logging +import os from unittest.mock import patch import pikepdf @@ -298,3 +299,8 @@ def test_sidecar_equals_output(resources, no_outpdf): op = no_outpdf with pytest.raises(BadArgsError, match=r'--sidecar'): run_ocrmypdf_api(resources / 'trivial.pdf', op, '--sidecar', op) + + +def test_devnull_sidecar(resources): + with pytest.raises(BadArgsError, match=r'--sidecar.*NUL'): + run_ocrmypdf_api(resources / 'trivial.pdf', os.devnull, '--sidecar') From 9a08e71e7f1d7764890a426a8befb666ff670136 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Sun, 26 Sep 2021 23:40:54 -0700 Subject: [PATCH 097/106] docs: show logo --- docs/index.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/index.rst b/docs/index.rst index 91eac433..721b73a9 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,6 +1,8 @@ OCRmyPDF documentation ====================== +.. figure:: images/logo.svg + OCRmyPDF adds an optical character recognition (OCR) text layer to scanned PDF files, allowing them to be searched. From 9d04795f7fd8bea1a47942ff8eaf8239b59d3550 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Sun, 26 Sep 2021 23:41:17 -0700 Subject: [PATCH 098/106] docs: fix package version error --- docs/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index a17933cd..65504c8a 100755 --- a/docs/conf.py +++ b/docs/conf.py @@ -91,7 +91,7 @@ if on_rtd: from importlib_metadata import version as package_version # The full version, including alpha/beta/rc tags. -release = package_version('ocrmypdf').version +release = package_version('ocrmypdf') version = '.'.join(release.split('.')[:2]) From 313c9e7dc171351126ad6dac3d5b46cea27cc6f7 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Sun, 26 Sep 2021 23:41:25 -0700 Subject: [PATCH 099/106] docs: add missing sphinx extensions --- docs/conf.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index 65504c8a..2a8712f3 100755 --- a/docs/conf.py +++ b/docs/conf.py @@ -31,7 +31,13 @@ # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. -extensions = ['sphinx.ext.napoleon', 'sphinx_issues'] +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.intersphinx', + 'sphinx.ext.autosummary', + 'sphinx.ext.napoleon', + 'sphinx_issues', +] # Extension settings napoleon_use_rtype = False From b621df69470858c3c293d435dd3c1103f8a04eb5 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Sat, 2 Oct 2021 01:02:17 -0700 Subject: [PATCH 100/106] v12.6.0 release notes --- docs/release_notes.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/release_notes.rst b/docs/release_notes.rst index cea2ba1f..5dddba3d 100644 --- a/docs/release_notes.rst +++ b/docs/release_notes.rst @@ -18,6 +18,14 @@ wish to use some of its features for working with PDFs. for Python 3.6 around that time. The change will be marked with a major release. +v12.6.0 +======= + +- Implemented ``--output-type=none`` to skip producing PDFs for applications that + only want sidecar files (:issue:`787`). +- Fixed ambiguities in descriptions of behavior of ``--jbig2-lossy``. +- Various improvements to documentation. + v12.5.0 ======= From af18bc0684138d371a842b9924b7a17e719a608b Mon Sep 17 00:00:00 2001 From: fedeliallalinea Date: Mon, 4 Oct 2021 06:30:11 +0000 Subject: [PATCH 101/106] fixs importlib.{metadata,resource} for new python version (#840) Signed-off-by: Marco Genasci --- setup.cfg | 6 +++--- src/ocrmypdf/_version.py | 5 ++++- src/ocrmypdf/pdfa.py | 7 +++++-- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/setup.cfg b/setup.cfg index a23304f7..b5aa691e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -49,8 +49,8 @@ install_requires = cffi>=1.9.1 # must be a setup and install requirement coloredlogs>=14.0 # strictly optional img2pdf>=0.3.0,<0.5 # pure Python - importlib-metadata>=4 # until Python 3.8 - importlib-resources>=5 # until Python 3.9 + importlib-metadata>=4;python_version<'3.8' # until Python 3.8 + importlib-resources>=5;python_version<'3.9' # until Python 3.9 pdfminer.six!=20200720,>=20191110,<=20201018 pikepdf>=2.10.0 pikepdf<3;implementation_name=="pypy" and python_version=='3.6' @@ -112,4 +112,4 @@ ignore = ignore = D203,F401,W503,E501,E203,F841 exclude = .git,__pycache__,docs/conf.py,build,dist,.venv,.venvpp,.eggs,tmp,src/ocrmypdf/lib/ max-complexity = 10 -max-line-length = 100 \ No newline at end of file +max-line-length = 100 diff --git a/src/ocrmypdf/_version.py b/src/ocrmypdf/_version.py index 091eacf9..3cf5ff8b 100644 --- a/src/ocrmypdf/_version.py +++ b/src/ocrmypdf/_version.py @@ -5,7 +5,10 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. -from importlib_metadata import version as _package_version +try: + from importlib_metadata import version as _package_version +except ImportError: + from importlib.metadata import version as _package_version PROGRAM_NAME = 'ocrmypdf' diff --git a/src/ocrmypdf/pdfa.py b/src/ocrmypdf/pdfa.py index cc34f906..b17eb213 100644 --- a/src/ocrmypdf/pdfa.py +++ b/src/ocrmypdf/pdfa.py @@ -13,7 +13,10 @@ import base64 from pathlib import Path from typing import Dict, Iterator, Union -import importlib_resources +try: + from importlib_resources import read_binary +except ImportError: + from importlib.resources import read_binary import pikepdf import pkg_resources # deprecated @@ -104,7 +107,7 @@ def generate_pdfa_ps(target_filename: Path, icc: str = 'sRGB'): if icc != 'sRGB': raise NotImplementedError("Only supporting sRGB") - bytes_icc_profile = importlib_resources.read_binary( + bytes_icc_profile = read_binary( 'ocrmypdf.data', SRGB_ICC_PROFILE_NAME ) ps = '\n'.join(_make_postscript(icc, bytes_icc_profile, 3)) From a8f513eeeb58527ab7856c6a0d28878e68d52166 Mon Sep 17 00:00:00 2001 From: mara004 <65915611+mara004@users.noreply.github.com> Date: Mon, 4 Oct 2021 09:34:39 +0200 Subject: [PATCH 102/106] [ci skip] Update api.rst (#839) --- docs/api.rst | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index 5da9cf4d..f3e03534 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -12,7 +12,7 @@ subprocess call anyway, as this provides isolation of its activities. Example ======= -OCRmyPDF one high-level function to run its main engine from an +OCRmyPDF provides one high-level function to run its main engine from an application. The parameters are symmetric to the command line arguments and largely have the same functions. @@ -23,7 +23,7 @@ and largely have the same functions. if __name__ == '__main__': # To ensure correct behavior on Windows and macOS ocrmypdf.ocr('input.pdf', 'output.pdf', deskew=True) -With a few exceptions, all of the command line arguments are available +With some exceptions, all of the command line arguments are available and may be passed as equivalent keywords. A few differences are that ``verbose`` and ``quiet`` are not available. @@ -41,29 +41,29 @@ execution. To do this, it will: - manage the signal flags of its worker processes - execute other subprocesses (forking and executing other programs) -The Python process that calls ``ocrmypdf.ocr()`` must be sufficiently +The Python process that calls :func:`ocrmypdf.ocr()` must be sufficiently privileged to perform these actions. There currently is no option to manage how jobs are scheduled other than the argument ``jobs=`` which will limit the number of worker processes. -Creating a child process to call ``ocrmypdf.ocr()`` is suggested. That +Creating a child process to call :func:`ocrmypdf.ocr()` is suggested. That way your application will survive and remain interactive even if OCRmyPDF fails for any reason. -Programs that call ``ocrmypdf.ocr()`` should also install a SIGBUS signal +Programs that call :func:`ocrmypdf.ocr()` should also install a SIGBUS signal handler (except on Windows), to raise an exception if access to a memory mapped file fails. OCRmyPDF may use memory mapping. -``ocrmypdf.ocr()`` will take a threading lock to prevent multiple runs of itself +:func:`ocrmypdf.ocr()` will take a threading lock to prevent multiple runs of itself in the same Python interpreter process. This is not thread-safe, because of how OCRmyPDF's plugins and Python's library import system work. If you need to parallelize OCRmyPDF, use processes. .. warning:: - On Windows and macOS, the script that calls ``ocrmypdf.ocr()`` must be + On Windows and macOS, the script that calls :func:`ocrmypdf.ocr()` must be protected by an "ifmain" guard (``if __name__ == '__main__'``). If you do not take at least one of these steps, process semantics will prevent OCRmyPDF from working correctly. @@ -96,7 +96,7 @@ Exceptions OCRmyPDF may throw standard Python exceptions, ``ocrmypdf.exceptions.*`` exceptions, some exceptions related to multiprocessing, and -``KeyboardInterrupt``. The parent process should provide an exception +:exc:`KeyboardInterrupt`. The parent process should provide an exception handler. OCRmyPDF will clean up its temporary files and worker processes automatically when an exception occurs. From 7bdd1828a986a74b1784e4a617b88517d4998b3a Mon Sep 17 00:00:00 2001 From: mara004 <65915611+mara004@users.noreply.github.com> Date: Mon, 4 Oct 2021 09:34:59 +0200 Subject: [PATCH 103/106] [ci skip] docs/conf.py: add intersphinx mapping to make external links work (#838) --- docs/conf.py | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/conf.py b/docs/conf.py index 2a8712f3..2aa1ad24 100755 --- a/docs/conf.py +++ b/docs/conf.py @@ -40,6 +40,7 @@ extensions = [ ] # Extension settings +intersphinx_mapping = {'https://docs.python.org/': None} napoleon_use_rtype = False issues_github_path = "jbarlow83/OCRmyPDF" From 78f391536b03c2c6b221b73cbdfd4d1ec9202224 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Wed, 6 Oct 2021 00:19:11 -0700 Subject: [PATCH 104/106] Offer hint to user to use --max-image-mpixels after decompression bob error Closes #801 --- src/ocrmypdf/_sync.py | 7 +++++++ tests/test_main.py | 11 +++++------ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/ocrmypdf/_sync.py b/src/ocrmypdf/_sync.py index 31693f9e..ea76dabb 100644 --- a/src/ocrmypdf/_sync.py +++ b/src/ocrmypdf/_sync.py @@ -414,6 +414,13 @@ def run_pipeline(options, *, plugin_manager, api=False): else: log.error(type(e).__name__) return e.exit_code + except (PIL.Image.DecompressionBombError if not api else NeverRaise) as e: + log.exception( + "A decompression bomb error was encountered while executing the " + "pipeline. Use the argument --max-image-mpixels to raise the maximum " + "image pixel limit." + ) + return ExitCode.other_error except (Exception if not api else NeverRaise): # pylint: disable=broad-except log.exception("An exception occurred while executing the pipeline") return ExitCode.other_error diff --git a/tests/test_main.py b/tests/test_main.py index 92e1c97f..5c326b79 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -745,14 +745,13 @@ def test_pdfa_n(pdfa_level, resources, outpdf): assert pdfa_info['conformance'] == f'PDF/A-{pdfa_level}B' -@pytest.mark.skipif( - PIL.__version__ < '5.0.0', reason="Pillow < 5.0.0 doesn't raise the exception" -) -@pytest.mark.slow -def test_decompression_bomb(resources, outpdf): +def test_decompression_bomb_error(resources, outpdf): p, _out, err = run_ocrmypdf(resources / 'hugemono.pdf', outpdf) - assert 'decompression bomb' in err + assert 'decompression bomb' in err and '--max-image-mpixels' in err + +@pytest.mark.slow +def test_decompression_bomb_succeeds(resources, outpdf): p, _out, err = run_ocrmypdf( resources / 'hugemono.pdf', outpdf, '--max-image-mpixels', '2000' ) From 690f88119d3ec24b17ddd14bb44832954a452e48 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Tue, 12 Oct 2021 13:38:52 -0700 Subject: [PATCH 105/106] Fix test failures on pikepdf 3.2.0 + pybind11 2.8.0 When compiled without pybind11 2.8.0, pikepdf supplies a shim to implement pikepdf._ObjectMapping.values() which has subtly different semantics from a true dict-like objects; in particular it supports next(objectmap.values()) where a standard dict requires next(iter(objectmap.values()). pybind11 2.8.0 now implements .values() properly, meaning some misuses of protocol in ocrmypdf fail. If pybind11 < 2.8.0, pikepdf will continue to offer its shim. If pybind11 >= 2.8.0, pikepdf does not add its shim. Consequently no changes were needed in pikepdf. Closes #843 --- src/ocrmypdf/optimize.py | 2 +- tests/test_image_input.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ocrmypdf/optimize.py b/src/ocrmypdf/optimize.py index 55a8841a..0c6d3b77 100644 --- a/src/ocrmypdf/optimize.py +++ b/src/ocrmypdf/optimize.py @@ -454,7 +454,7 @@ def _transcode_png(pike: Pdf, filename: Path, xref: Xref) -> bool: img2pdf.convert(fspath(filename), outputstream=f) with Pdf.open(output) as pdf_image: - foreign_image = next(pdf_image.pages[0].images.values()) + foreign_image = next(iter(pdf_image.pages[0].images.values())) local_image = pike.copy_foreign(foreign_image) im_obj = pike.get_object(xref, 0) diff --git a/tests/test_image_input.py b/tests/test_image_input.py index 78ad5fd1..bbce6011 100644 --- a/tests/test_image_input.py +++ b/tests/test_image_input.py @@ -87,4 +87,4 @@ def test_jpeg_in_jpeg_out(resources, outpdf): 'tests/plugins/tesseract_noop.py', ) with pikepdf.open(outpdf) as pdf: - assert next(pdf.pages[0].images.values()).Filter == pikepdf.Name.DCTDecode + assert next(iter(pdf.pages[0].images.values())).Filter == pikepdf.Name.DCTDecode From 42713b77d7e9ac80c0c9cf9b77bb1900aa9ab0b5 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Tue, 12 Oct 2021 13:39:49 -0700 Subject: [PATCH 106/106] v12.7.0 release notes --- docs/release_notes.rst | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/release_notes.rst b/docs/release_notes.rst index 5dddba3d..b03f84be 100644 --- a/docs/release_notes.rst +++ b/docs/release_notes.rst @@ -18,6 +18,18 @@ wish to use some of its features for working with PDFs. for Python 3.6 around that time. The change will be marked with a major release. +v12.7.0 +======= + +- Fixed test suite failure when using pikepdf 3.2.0 that was compiled with pybind11 + 2.8.0. :issue:`843` +- Improve advice to user about using ``--max-image-mpixels`` if OCR fails for this + reason. +- Minor documentation fixes. (Thanks to @mara004.) +- Don't require importlib-metadata and importlib-resources backports on versions of + Python where the standard library implementation is sufficient. + (Thanks to Marco Genasci.) + v12.6.0 =======