Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f072e91120 | ||
|
|
efa2bca8a3 | ||
|
|
b039010d3e | ||
|
|
3f7cefcf5d | ||
|
|
45f97d1565 | ||
|
|
1281f8eb68 | ||
|
|
9ef61864fb | ||
|
|
90b2119ad3 | ||
|
|
f0cc7f2230 | ||
|
|
d60a384aab | ||
|
|
14a6093636 | ||
|
|
54b42d73ab | ||
|
|
9abed14f1f | ||
|
|
d09f61d4fe | ||
|
|
4a0130649e | ||
|
|
a0224d94ca | ||
|
|
9e7b9de830 | ||
|
|
08fc5fc01d | ||
|
|
110c75cba2 | ||
|
|
5afca3f342 | ||
|
|
d9eb0ba7ef | ||
|
|
46d0978a09 | ||
|
|
4e35100978 | ||
|
|
7bd0e43243 | ||
|
|
9cd97da5f2 | ||
|
|
d002703c41 | ||
|
|
b1fecf3b05 | ||
|
|
c2ccc7f29d | ||
|
|
36dfd12e2c | ||
|
|
7da4e6ca7f | ||
|
|
16fc52079d | ||
|
|
f37decf3b3 | ||
|
|
4ad4a13ef0 | ||
|
|
6f61f69a8d | ||
|
|
88831e8ab1 | ||
|
|
2ebc36fcec | ||
|
|
1709e23701 | ||
|
|
2e55cb5980 | ||
|
|
6dbaebdc0c | ||
|
|
5156fe7662 | ||
|
|
2c99c89e45 | ||
|
|
74286e7e1e | ||
|
|
2e937dee9f | ||
|
|
23f3830533 | ||
|
|
305e6594be | ||
|
|
f4155dca77 | ||
|
|
545cd031b0 | ||
|
|
a1c7826336 | ||
|
|
c5359bd990 | ||
|
|
7f77308846 | ||
|
|
8e7c5f3001 | ||
|
|
79db985181 | ||
|
|
7d23a661fc | ||
|
|
99e94807c5 | ||
|
|
8412de9344 | ||
|
|
b458b1422b | ||
|
|
76bd8cab13 | ||
|
|
ef70c9499e | ||
|
|
47dcb6fcd0 | ||
|
|
88d2949e6b | ||
|
|
c9389c7713 | ||
|
|
4d2f499f97 | ||
|
|
4104904a1e | ||
|
|
1a0a797ca6 | ||
|
|
670ce2b969 | ||
|
|
7e97981114 | ||
|
|
53db866ef9 | ||
|
|
d591a3e059 | ||
|
|
37c050aa4f | ||
|
|
4b9ea40a0c | ||
|
|
165432486b | ||
|
|
d619fac0bd | ||
|
|
acc70036cc | ||
|
|
80b7cf6330 | ||
|
|
8a8c06c79c | ||
|
|
67773da309 | ||
|
|
d5a9861d5c | ||
|
|
9ffe829a10 | ||
|
|
8a3b82e364 | ||
|
|
580822a6a2 | ||
|
|
9f3a52fd12 | ||
|
|
52e829d845 | ||
|
|
2b2e5c271a | ||
|
|
5fe3102e4e | ||
|
|
5b57520c98 |
+9
-3
@@ -1,5 +1,5 @@
|
|||||||
# OCRmyPDF
|
# SPDX-FileCopyrightText: 2022 James R. Barlow
|
||||||
#
|
# SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
FROM ubuntu:22.04 as base
|
FROM ubuntu:22.04 as base
|
||||||
|
|
||||||
@@ -50,8 +50,14 @@ RUN pip3 install --no-cache-dir .[test,webservice,watcher]
|
|||||||
|
|
||||||
FROM base
|
FROM base
|
||||||
|
|
||||||
|
# For Tesseract 5
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
software-properties-common gpg-agent
|
||||||
|
RUN add-apt-repository -y ppa:alex-p/tesseract-ocr-devel
|
||||||
|
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
ghostscript \
|
ghostscript \
|
||||||
|
jbig2dec \
|
||||||
img2pdf \
|
img2pdf \
|
||||||
libsm6 libxext6 libxrender-dev \
|
libsm6 libxext6 libxrender-dev \
|
||||||
pngquant \
|
pngquant \
|
||||||
@@ -74,7 +80,7 @@ COPY --from=builder /app/misc/webservice.py /app/
|
|||||||
COPY --from=builder /app/misc/watcher.py /app/
|
COPY --from=builder /app/misc/watcher.py /app/
|
||||||
|
|
||||||
# Copy minimal project files to get the test suite.
|
# Copy minimal project files to get the test suite.
|
||||||
COPY --from=builder /app/setup.cfg /app/setup.py /app/README.md /app/
|
COPY --from=builder /app/pyproject.toml /app/README.md /app/
|
||||||
COPY --from=builder /app/tests /app/tests
|
COPY --from=builder /app/tests /app/tests
|
||||||
|
|
||||||
ENTRYPOINT ["/usr/local/bin/ocrmypdf"]
|
ENTRYPOINT ["/usr/local/bin/ocrmypdf"]
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
# SPDX-FileCopyrightText: 2022 James R. Barlow
|
||||||
|
# SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
# dotfiles
|
# dotfiles
|
||||||
.*
|
.*
|
||||||
!.coveragerc
|
!.coveragerc
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
# SPDX-FileCopyrightText: 2022 James R. Barlow
|
||||||
|
# SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
# Always use Unix convention for new lines
|
# Always use Unix convention for new lines
|
||||||
* text eol=lf
|
* text eol=lf
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
# SPDX-FileCopyrightText: 2022 James R. Barlow
|
||||||
|
# SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
# These are supported funding model platforms
|
# These are supported funding model platforms
|
||||||
|
|
||||||
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||||
|
|||||||
+1
-5
@@ -17,11 +17,7 @@ A clear and concise description of what you want to happen.
|
|||||||
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.
|
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**
|
**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.
|
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. You could also look in ``tests/resources`` and see if any of those files demonstrates your issue.
|
||||||
|
|
||||||
Please provide an input file with no personal or confidential information. At your option you may [GPG-encrypt the file](https://github.com/ocrmypdf/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**
|
**Additional context**
|
||||||
Add any other context or screenshots about the feature request here.
|
Add any other context or screenshots about the feature request here.
|
||||||
+2
-1
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
name: General issues
|
name: General issues
|
||||||
about: Installation, packages, dependencies, "nothing works", test suite failures...
|
about: Installation, packages, dependencies, "nothing works", test suite failures...
|
||||||
title: ''
|
title: "[BUG]"
|
||||||
labels: ''
|
labels: ''
|
||||||
assignees: ''
|
assignees: ''
|
||||||
|
|
||||||
@@ -23,6 +23,7 @@ If applicable, add screenshots to help explain your problem.
|
|||||||
- OS:
|
- OS:
|
||||||
- Python version:
|
- Python version:
|
||||||
- OCRmyPDF version:
|
- OCRmyPDF version:
|
||||||
|
- Platform: x64 or ARM
|
||||||
|
|
||||||
**Installation**
|
**Installation**
|
||||||
How did you install OCRmyPDF? Did you install it from your operating system's
|
How did you install OCRmyPDF? Did you install it from your operating system's
|
||||||
+2
-2
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
name: Problem with a specific input file
|
name: Problem with specific file
|
||||||
about: Something went wrong while trying to OCR a specific file
|
about: Something went wrong while trying to OCR a specific file
|
||||||
title: ''
|
title: "[BUG]"
|
||||||
labels: ''
|
labels: ''
|
||||||
assignees: ''
|
assignees: ''
|
||||||
|
|
||||||
@@ -1,3 +1,6 @@
|
|||||||
|
# SPDX-FileCopyrightText: 2022 James R. Barlow
|
||||||
|
# SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
# To get started with Dependabot version updates, you'll need to specify which
|
# To get started with Dependabot version updates, you'll need to specify which
|
||||||
# package ecosystems to update and where the package manifests are located.
|
# package ecosystems to update and where the package manifests are located.
|
||||||
# Please see the documentation for all configuration options:
|
# Please see the documentation for all configuration options:
|
||||||
|
|||||||
+22
-22
@@ -1,3 +1,5 @@
|
|||||||
|
# SPDX-FileCopyrightText: 2022 James R. Barlow
|
||||||
|
# SPDX-License-Identifier: MPL-2.0
|
||||||
name: Test and deploy
|
name: Test and deploy
|
||||||
|
|
||||||
on:
|
on:
|
||||||
@@ -20,8 +22,6 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- os: ubuntu-18.04
|
|
||||||
python: "3.7"
|
|
||||||
- os: ubuntu-20.04
|
- os: ubuntu-20.04
|
||||||
python: "3.8"
|
python: "3.8"
|
||||||
- os: ubuntu-20.04
|
- os: ubuntu-20.04
|
||||||
@@ -31,7 +31,13 @@ jobs:
|
|||||||
- os: ubuntu-latest
|
- os: ubuntu-latest
|
||||||
python: "3.9"
|
python: "3.9"
|
||||||
- os: ubuntu-latest
|
- os: ubuntu-latest
|
||||||
python: "pypy-3.8"
|
python: "3.10"
|
||||||
|
- os: ubuntu-latest
|
||||||
|
python: "3.11"
|
||||||
|
# - os: ubuntu-latest
|
||||||
|
# python: "pypy3.8"
|
||||||
|
#- os: ubuntu-latest
|
||||||
|
# python: "pypy3.9"
|
||||||
- os: ubuntu-latest
|
- os: ubuntu-latest
|
||||||
python: "3.9"
|
python: "3.9"
|
||||||
tesseract5: true
|
tesseract5: true
|
||||||
@@ -73,12 +79,6 @@ jobs:
|
|||||||
unpaper \
|
unpaper \
|
||||||
zlib1g
|
zlib1g
|
||||||
|
|
||||||
- name: Install Ubuntu 18.04 packages
|
|
||||||
if: matrix.os == 'ubuntu-18.04'
|
|
||||||
run: |
|
|
||||||
sudo apt-get install -y --no-install-recommends \
|
|
||||||
libexempi3
|
|
||||||
|
|
||||||
- name: Install Ubuntu 20.04 packages
|
- name: Install Ubuntu 20.04 packages
|
||||||
if: matrix.os == 'ubuntu-20.04' || matrix.os == 'ubuntu-latest'
|
if: matrix.os == 'ubuntu-20.04' || matrix.os == 'ubuntu-latest'
|
||||||
run: |
|
run: |
|
||||||
@@ -96,7 +96,7 @@ jobs:
|
|||||||
- name: Install Python packages
|
- name: Install Python packages
|
||||||
run: |
|
run: |
|
||||||
python -m pip install --upgrade pip wheel
|
python -m pip install --upgrade pip wheel
|
||||||
python -m pip install .[test]
|
python -m pip install --prefer-binary .[test]
|
||||||
|
|
||||||
- name: Report versions
|
- name: Report versions
|
||||||
run: |
|
run: |
|
||||||
@@ -122,7 +122,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [macos-latest]
|
os: [macos-latest]
|
||||||
python: ["3.9", "3.10"]
|
python: ["3.10", "3.11"]
|
||||||
|
|
||||||
env:
|
env:
|
||||||
OS: ${{ matrix.os }}
|
OS: ${{ matrix.os }}
|
||||||
@@ -133,12 +133,8 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
fetch-depth: "0" # 0=all, needed for setuptools-scm to resolve version tags
|
fetch-depth: "0" # 0=all, needed for setuptools-scm to resolve version tags
|
||||||
|
|
||||||
- uses: actions/setup-python@v4
|
|
||||||
name: Install Python
|
|
||||||
with:
|
|
||||||
python-version: ${{ matrix.python }}
|
|
||||||
|
|
||||||
- name: Install Homebrew deps
|
- name: Install Homebrew deps
|
||||||
|
continue-on-error: true
|
||||||
run: |
|
run: |
|
||||||
brew update
|
brew update
|
||||||
brew install \
|
brew install \
|
||||||
@@ -149,10 +145,15 @@ jobs:
|
|||||||
pngquant \
|
pngquant \
|
||||||
tesseract
|
tesseract
|
||||||
|
|
||||||
|
- uses: actions/setup-python@v4
|
||||||
|
name: Install Python
|
||||||
|
with:
|
||||||
|
python-version: ${{ matrix.python }}
|
||||||
|
|
||||||
- name: Install Python packages
|
- name: Install Python packages
|
||||||
run: |
|
run: |
|
||||||
python -m pip install --upgrade pip wheel
|
python -m pip install --upgrade pip wheel
|
||||||
python -m pip install .[test]
|
python -m pip install --prefer-binary .[test]
|
||||||
|
|
||||||
- name: Report versions
|
- name: Report versions
|
||||||
run: |
|
run: |
|
||||||
@@ -177,7 +178,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [windows-latest]
|
os: [windows-latest]
|
||||||
python: ["3.9", "3.10"]
|
python: ["3.10", "3.11"]
|
||||||
|
|
||||||
env:
|
env:
|
||||||
OS: ${{ matrix.os }}
|
OS: ${{ matrix.os }}
|
||||||
@@ -201,7 +202,7 @@ jobs:
|
|||||||
- name: Install Python packages
|
- name: Install Python packages
|
||||||
run: |
|
run: |
|
||||||
python -m pip install --upgrade pip wheel
|
python -m pip install --upgrade pip wheel
|
||||||
python -m pip install .[test]
|
python -m pip install --prefer-binary .[test]
|
||||||
|
|
||||||
- name: Test
|
- name: Test
|
||||||
run: |
|
run: |
|
||||||
@@ -228,9 +229,8 @@ jobs:
|
|||||||
|
|
||||||
- name: Make wheels and sdist
|
- name: Make wheels and sdist
|
||||||
run: |
|
run: |
|
||||||
python -m pip install --upgrade pip wheel
|
python -m pip install --upgrade pip wheel build
|
||||||
python setup.py sdist
|
python -m build --sdist --wheel
|
||||||
python setup.py bdist_wheel
|
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v3
|
- uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
|
|||||||
+10
-11
@@ -1,14 +1,14 @@
|
|||||||
|
# SPDX-FileCopyrightText: 2022 James R. Barlow
|
||||||
|
# SPDX-License-Identifier: CC-BY-SA-4.0
|
||||||
|
|
||||||
# dotfiles
|
# dotfiles
|
||||||
.*
|
.coverage
|
||||||
!.coveragerc
|
.venv*/
|
||||||
!.dockerignore
|
.tox/
|
||||||
!.git_archival.txt
|
.vscode/
|
||||||
!.gitattributes
|
.ipynb_checkpoints/
|
||||||
!.gitignore
|
.mypy_cache/
|
||||||
!.pre-commit-config.yaml
|
.pytest_cache/
|
||||||
!.readthedocs.yaml
|
|
||||||
!.github/
|
|
||||||
!.docker/
|
|
||||||
|
|
||||||
# Dev scratch
|
# Dev scratch
|
||||||
*.ipynb
|
*.ipynb
|
||||||
@@ -42,4 +42,3 @@ docs/_build/
|
|||||||
docs/_static/
|
docs/_static/
|
||||||
docs/_templates/
|
docs/_templates/
|
||||||
docs/Makefile
|
docs/Makefile
|
||||||
ocrmypdf/lib/_*.py
|
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
# SPDX-FileCopyrightText: 2022 James R. Barlow
|
||||||
|
# SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
rev: v4.3.0
|
rev: v4.3.0
|
||||||
@@ -25,7 +28,7 @@ repos:
|
|||||||
rev: v2.37.2
|
rev: v2.37.2
|
||||||
hooks:
|
hooks:
|
||||||
- id: pyupgrade
|
- id: pyupgrade
|
||||||
args: ["--py37-plus"]
|
args: ["--py38-plus"]
|
||||||
- repo: https://github.com/pre-commit/mirrors-mypy
|
- repo: https://github.com/pre-commit/mirrors-mypy
|
||||||
rev: v0.971
|
rev: v0.971
|
||||||
hooks:
|
hooks:
|
||||||
|
|||||||
+4
-1
@@ -1,3 +1,6 @@
|
|||||||
|
# SPDX-FileCopyrightText: 2022 James R. Barlow
|
||||||
|
# SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
# Read the Docs configuration file
|
# Read the Docs configuration file
|
||||||
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
|
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
|
||||||
|
|
||||||
@@ -14,7 +17,7 @@ formats:
|
|||||||
|
|
||||||
# Optionally set the version of Python and requirements required to build your docs
|
# Optionally set the version of Python and requirements required to build your docs
|
||||||
python:
|
python:
|
||||||
version: "3.7"
|
version: "3.8"
|
||||||
install:
|
install:
|
||||||
- method: pip
|
- method: pip
|
||||||
path: .
|
path: .
|
||||||
|
|||||||
+132
@@ -0,0 +1,132 @@
|
|||||||
|
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||||
|
Upstream-Name: OCRmyPDF
|
||||||
|
Upstream-Contact: James R. Barlow <james@purplerock.ca>
|
||||||
|
Source: https://github.com/ocrmypdf/OCRmyPDF
|
||||||
|
|
||||||
|
|
||||||
|
Files:
|
||||||
|
.git_archival.txt
|
||||||
|
docs/images/logo-social.png
|
||||||
|
docs/images/logo-square-256.svg
|
||||||
|
docs/images/logo-square.png
|
||||||
|
docs/images/logo-square.svg
|
||||||
|
docs/images/logo.svg
|
||||||
|
setup.cfg
|
||||||
|
Copyright: (C) 2022 James R. Barlow
|
||||||
|
License: MPL-2.0
|
||||||
|
|
||||||
|
Files:
|
||||||
|
.github/ISSUE_TEMPLATE/*.md
|
||||||
|
docs/images/macos-workflow.png
|
||||||
|
Copyright: (C) 2022 James R. Barlow
|
||||||
|
License: CC-BY-SA-4.0
|
||||||
|
|
||||||
|
Files:
|
||||||
|
tests/resources/acroform.pdf
|
||||||
|
tests/resources/aspect.pdf
|
||||||
|
tests/resources/blank.pdf
|
||||||
|
tests/resources/cmyk.pdf
|
||||||
|
tests/resources/crom.png
|
||||||
|
tests/resources/enormous.pdf
|
||||||
|
tests/resources/formxobject.pdf
|
||||||
|
tests/resources/francais.pdf
|
||||||
|
tests/resources/hugemono.pdf
|
||||||
|
tests/resources/invalid.pdf
|
||||||
|
tests/resources/kcs.pdf
|
||||||
|
tests/resources/livecycle.pdf
|
||||||
|
tests/resources/missing_docinfo.pdf
|
||||||
|
tests/resources/negzero.pdf
|
||||||
|
tests/resources/no_contents.pdf
|
||||||
|
tests/resources/toc.pdf
|
||||||
|
tests/resources/trivial.pdf
|
||||||
|
tests/resources/truetype_font_nomapping.pdf
|
||||||
|
tests/resources/type3_font_nomapping.pdf
|
||||||
|
Copyright: (C) 2022 James R. Barlow
|
||||||
|
License: CC-BY-SA-4.0
|
||||||
|
|
||||||
|
Files:
|
||||||
|
tests/resources/graph.pdf
|
||||||
|
tests/resources/graph_ocred.pdf
|
||||||
|
Copyright: (C) 2012 SmokeyJoe
|
||||||
|
License: GFDL-1.2-or-later or CC-BY-SA-3.0
|
||||||
|
|
||||||
|
Files: tests/resources/c02-22.pdf
|
||||||
|
tests/resources/congress.jpg
|
||||||
|
tests/resources/multipage.pdf
|
||||||
|
Copyright: Public domain
|
||||||
|
License: public-domain
|
||||||
|
Copyright on these files has expired.
|
||||||
|
|
||||||
|
Files: docs/images/bitmap_vs_svg.svg
|
||||||
|
Copyright: (C) 2006 Yug
|
||||||
|
License: CC-BY-SA-2.5
|
||||||
|
|
||||||
|
Files: tests/cache/*
|
||||||
|
Copyright: (C) 2022 James R. Barlow
|
||||||
|
License: CC-BY-SA-4.0
|
||||||
|
|
||||||
|
Files: tests/resources/linn.png
|
||||||
|
tests/resources/linn.pdf
|
||||||
|
tests/resources/linn.txt
|
||||||
|
tests/resources/ccitt.pdf
|
||||||
|
tests/resources/cardinal.pdf
|
||||||
|
tests/resources/jbig2.pdf
|
||||||
|
tests/resources/skew.pdf
|
||||||
|
tests/resources/rotated_skew.pdf
|
||||||
|
tests/resources/poster.pdf
|
||||||
|
Copyright: (C) 1985 Forat Electronics
|
||||||
|
License: GFDL-1.2-or-later or CC-BY-SA-3.0
|
||||||
|
|
||||||
|
Files: tests/resources/lichtenstein.pdf
|
||||||
|
Copyright: (C) 2001 Andreas Tille
|
||||||
|
(C) 2007 Alessio Damato
|
||||||
|
License: GFDL-1.2-or-later or CC-BY-SA-3.0
|
||||||
|
|
||||||
|
Files: tests/resources/masks.pdf
|
||||||
|
Copyright: held by the contributors to the German Wikipedia article "Linux"
|
||||||
|
see: https://de.wikipedia.org/w/index.php?title=Linux&action=history
|
||||||
|
(masks.pdf generated from Wikipedia article as of 2016-08-24)
|
||||||
|
License: CC-BY-SA-3.0
|
||||||
|
|
||||||
|
Files: tests/resources/epson.pdf
|
||||||
|
Copyright: held by the contributors to the Wikipedia article "Optical character recognition"
|
||||||
|
see: https://en.wikipedia.org/w/index.php?title=Optical_character_recognition&action=history
|
||||||
|
(epson.pdf generated from Wikipedia article as of 2016-09-14)
|
||||||
|
License: CC-BY-SA-3.0
|
||||||
|
|
||||||
|
Files: tests/resources/typewriter.png tests/resources/2400dpi.pdf
|
||||||
|
Copyright: (C) 2005 Ellywa
|
||||||
|
License: GFDL-1.2-or-later or CC-BY-SA-1.0 or CC-BY-SA-2.0 or CC-BY-SA-2.5 or CC-BY-SA-3.0
|
||||||
|
Comment:
|
||||||
|
Obtained from: https://commons.wikimedia.org/wiki/File:Triumph.typewriter_text_Linzensoep.gif
|
||||||
|
|
||||||
|
Files: tests/resources/overlay.pdf
|
||||||
|
Copyright: (C) 2017 Max Anderson
|
||||||
|
License: MIT
|
||||||
|
|
||||||
|
Files:
|
||||||
|
tests/resources/baiona*.png
|
||||||
|
tests/resources/baiona*.jpg
|
||||||
|
tests/resources/link.pdf
|
||||||
|
tests/resources/palette.pdf
|
||||||
|
Copyright: (C) 2014 Euskaldunaa
|
||||||
|
License: CC-BY-SA-4.0
|
||||||
|
|
||||||
|
Files: tests/resources/vector.pdf
|
||||||
|
Copyright: (C) 2018 Catscratch
|
||||||
|
License: MIT
|
||||||
|
|
||||||
|
Files: src/ocrmypdf/data/sRGB.icc
|
||||||
|
Copyright: Kai-Uwe Behrmann <www.behrmann.name>
|
||||||
|
Marti Maria <www.littlecms.com>
|
||||||
|
Photogamut <www.photogamut.org>
|
||||||
|
Graeme Gill <www.argyllcms.com>
|
||||||
|
ColorSolutions <www.basICColor.com>
|
||||||
|
License: Zlib
|
||||||
|
|
||||||
|
Files: tests/resources/3small.pdf
|
||||||
|
Copyright: (C) 2014 Euskaldunaa
|
||||||
|
(C) 2017 James R. Barlow
|
||||||
|
(C) 2005 Ellywa
|
||||||
|
License: CC-BY-SA-4.0 and (GFDL-1.2-or-later or CC-BY-SA-1.0 or CC-BY-SA-2.0 or CC-BY-SA-2.5 or CC-BY-SA-3.0)
|
||||||
|
Comment: concatenation of baiona_gray.png, crom.png and typewriter.png/2400dpi.pdf
|
||||||
@@ -1,3 +1,7 @@
|
|||||||
|
<!-- SPDX-FileCopyrightText: 2014 Julien Pfefferkorn -->
|
||||||
|
<!-- SPDX-FileCopyrightText: 2015 James R. Barlow -->
|
||||||
|
<!-- SPDX-License-Identifier: CC-BY-SA-4.0 -->
|
||||||
|
|
||||||
<img src="docs/images/logo.svg" width="240" alt="OCRmyPDF">
|
<img src="docs/images/logo.svg" width="240" alt="OCRmyPDF">
|
||||||
|
|
||||||
[](https://github.com/ocrmypdf/OCRmyPDF/actions/workflows/build.yml) [![PyPI version][pypi]](https://pypi.org/project/ocrmypdf/) ![Homebrew version][homebrew] ![ReadTheDocs][docs] ![Python versions][pyversions]
|
[](https://github.com/ocrmypdf/OCRmyPDF/actions/workflows/build.yml) [![PyPI version][pypi]](https://pypi.org/project/ocrmypdf/) ![Homebrew version][homebrew] ![ReadTheDocs][docs] ![Python versions][pyversions]
|
||||||
@@ -66,7 +70,7 @@ Linux, Windows, macOS and FreeBSD are supported. Docker images are also availabl
|
|||||||
| macOS (Homebrew) | ``brew install ocrmypdf`` |
|
| macOS (Homebrew) | ``brew install ocrmypdf`` |
|
||||||
| macOS (nix) | ``nix-env -i ocrmypdf`` |
|
| macOS (nix) | ``nix-env -i ocrmypdf`` |
|
||||||
| LinuxBrew | ``brew install ocrmypdf`` |
|
| LinuxBrew | ``brew install ocrmypdf`` |
|
||||||
| FreeBSD | ``pkg install py37-ocrmypdf`` |
|
| FreeBSD | ``pkg install py-ocrmypdf`` |
|
||||||
| Conda | ``conda install ocrmypdf`` |
|
| Conda | ``conda install ocrmypdf`` |
|
||||||
| Ubuntu Snap | ``snap install ocrmypdf`` |
|
| Ubuntu Snap | ``snap install ocrmypdf`` |
|
||||||
|
|
||||||
@@ -92,10 +96,7 @@ 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.
|
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
|
OCRmyPDF supports Tesseract 4.1.1+. 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.
|
||||||
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
|
## Documentation and support
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -160,7 +160,7 @@ Changing tesseract configuration variables
|
|||||||
------------------------------------------
|
------------------------------------------
|
||||||
|
|
||||||
You can override tesseract's default `control
|
You can override tesseract's default `control
|
||||||
parameters <https://github.com/tesseract-ocr/tesseract/wiki/ControlParams>`__
|
parameters <https://tesseract-ocr.github.io/tessdoc/tess3/ControlParams.html>`__
|
||||||
with a configuration file.
|
with a configuration file.
|
||||||
|
|
||||||
As an example, this configuration will disable Tesseract's dictionary
|
As an example, this configuration will disable Tesseract's dictionary
|
||||||
|
|||||||
@@ -72,14 +72,6 @@ OCRmyPDF, use processes.
|
|||||||
not take at least one of these steps, process semantics will prevent
|
not take at least one of these steps, process semantics will prevent
|
||||||
OCRmyPDF from working correctly.
|
OCRmyPDF from working correctly.
|
||||||
|
|
||||||
.. warning::
|
|
||||||
|
|
||||||
On macOS with Python 3.7, you must call
|
|
||||||
:func:`multiprocessing.set_start_method("spawn")`. Without this, multiprocessing
|
|
||||||
will be unstable. From the command line, OCRmyPDF does this automatically,
|
|
||||||
but as an API user you must do this. See Python bpo-33725 for details.
|
|
||||||
Python 3.8+ also resolve this automatically.
|
|
||||||
|
|
||||||
Logging
|
Logging
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -21,8 +21,8 @@ processors. To maximize parallelism without overloading your system with
|
|||||||
processes, consider using ``parallel -j 2`` to limit parallel to running
|
processes, consider using ``parallel -j 2`` to limit parallel to running
|
||||||
two jobs at once.
|
two jobs at once.
|
||||||
|
|
||||||
This command will run all ocrmypdf all files named ``*.pdf`` in the
|
This command will run ``ocrmypdf`` on all files named ``*.pdf`` in the
|
||||||
current directory and write them to the previous created ``output/``
|
current directory and write them to the previously created ``output/``
|
||||||
folder. It will not search subdirectories.
|
folder. It will not search subdirectories.
|
||||||
|
|
||||||
The ``--tag`` argument tells parallel to print the filename as a prefix
|
The ``--tag`` argument tells parallel to print the filename as a prefix
|
||||||
|
|||||||
+2
-4
@@ -76,6 +76,8 @@ author = 'James R. Barlow'
|
|||||||
# The short X.Y version.
|
# The short X.Y version.
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
from importlib.metadata import version as package_version
|
||||||
|
|
||||||
|
|
||||||
on_rtd = os.environ.get('READTHEDOCS') == 'True'
|
on_rtd = os.environ.get('READTHEDOCS') == 'True'
|
||||||
|
|
||||||
@@ -96,10 +98,6 @@ if on_rtd:
|
|||||||
]
|
]
|
||||||
sys.modules.update((mod_name, Mock()) for mod_name in MOCK_MODULES)
|
sys.modules.update((mod_name, Mock()) for mod_name in MOCK_MODULES)
|
||||||
|
|
||||||
try:
|
|
||||||
from importlib_metadata import version as package_version
|
|
||||||
except ModuleNotFoundError:
|
|
||||||
from importlib.metadata import version as package_version
|
|
||||||
|
|
||||||
# The full version, including alpha/beta/rc tags.
|
# The full version, including alpha/beta/rc tags.
|
||||||
release = package_version('ocrmypdf')
|
release = package_version('ocrmypdf')
|
||||||
|
|||||||
+1
-1
@@ -283,7 +283,7 @@ argument. (Normally, OCRmyPDF will exit with an error if asked to modify
|
|||||||
a file with OCR.)
|
a file with OCR.)
|
||||||
|
|
||||||
This may be helpful for users who want to take advantage of accuracy
|
This may be helpful for users who want to take advantage of accuracy
|
||||||
improvements in Tesseract 4.0 for files they previously OCRed with an
|
improvements in Tesseract for files they previously OCRed with an
|
||||||
earlier version of Tesseract and OCRmyPDF.
|
earlier version of Tesseract and OCRmyPDF.
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|||||||
+18
-59
@@ -44,7 +44,7 @@ install, or install a more recent version than your platform provides, read on.
|
|||||||
Installing on Linux
|
Installing on Linux
|
||||||
===================
|
===================
|
||||||
|
|
||||||
Debian and Ubuntu 18.04 or newer
|
Debian and Ubuntu 20.04 or newer
|
||||||
--------------------------------
|
--------------------------------
|
||||||
|
|
||||||
.. |deb-11| image:: https://repology.org/badge/version-for-repo/debian_11/ocrmypdf.svg
|
.. |deb-11| image:: https://repology.org/badge/version-for-repo/debian_11/ocrmypdf.svg
|
||||||
@@ -56,9 +56,6 @@ Debian and Ubuntu 18.04 or newer
|
|||||||
.. |deb-unstable| image:: https://repology.org/badge/version-for-repo/debian_unstable/ocrmypdf.svg
|
.. |deb-unstable| image:: https://repology.org/badge/version-for-repo/debian_unstable/ocrmypdf.svg
|
||||||
:alt: Debian unstable
|
:alt: Debian unstable
|
||||||
|
|
||||||
.. |ubu-1804| image:: https://repology.org/badge/version-for-repo/ubuntu_18_04/ocrmypdf.svg
|
|
||||||
:alt: Ubuntu 18.04 LTS
|
|
||||||
|
|
||||||
.. |ubu-2004| image:: https://repology.org/badge/version-for-repo/ubuntu_20_04/ocrmypdf.svg
|
.. |ubu-2004| image:: https://repology.org/badge/version-for-repo/ubuntu_20_04/ocrmypdf.svg
|
||||||
:alt: Ubuntu 20.04 LTS
|
:alt: Ubuntu 20.04 LTS
|
||||||
|
|
||||||
@@ -72,7 +69,7 @@ Debian and Ubuntu 18.04 or newer
|
|||||||
+-----------------------------------------------+
|
+-----------------------------------------------+
|
||||||
| |deb-11| |deb-12| |deb-unstable| |
|
| |deb-11| |deb-12| |deb-unstable| |
|
||||||
+-----------------------------------------------+
|
+-----------------------------------------------+
|
||||||
| |ubu-1804| |ubu-2004| |ubu-2204| |
|
| |ubu-2004| |ubu-2204| |
|
||||||
+-----------------------------------------------+
|
+-----------------------------------------------+
|
||||||
|
|
||||||
Users of Debian 9 ("stretch") or later, or Ubuntu 18.04 or later, including users
|
Users of Debian 9 ("stretch") or later, or Ubuntu 18.04 or later, including users
|
||||||
@@ -80,7 +77,7 @@ of Windows Subsystem for Linux, may simply
|
|||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
apt-get install ocrmypdf
|
apt install ocrmypdf
|
||||||
|
|
||||||
As indicated in the table above, Debian and Ubuntu releases may lag
|
As indicated in the table above, Debian and Ubuntu releases may lag
|
||||||
behind the latest version. If the version available for your platform is
|
behind the latest version. If the version available for your platform is
|
||||||
@@ -198,46 +195,6 @@ To install for the current user only:
|
|||||||
|
|
||||||
To add JBIG2 encoding, see :ref:`jbig2`.
|
To add JBIG2 encoding, see :ref:`jbig2`.
|
||||||
|
|
||||||
Ubuntu 18.04 LTS
|
|
||||||
----------------
|
|
||||||
|
|
||||||
Ubuntu 18.04 includes ocrmypdf 6.1.2 - you can install that with ``apt``, but
|
|
||||||
it is quite old now. To install a more recent version, uninstall the old version
|
|
||||||
of ocrmypdf, and install the following dependencies:
|
|
||||||
|
|
||||||
.. code-block:: bash
|
|
||||||
|
|
||||||
sudo apt-get -y remove ocrmypdf
|
|
||||||
sudo apt-get -y update
|
|
||||||
sudo apt-get -y install \
|
|
||||||
ghostscript \
|
|
||||||
icc-profiles-free \
|
|
||||||
libxml2 \
|
|
||||||
pngquant \
|
|
||||||
python3-distutils \
|
|
||||||
python3-pkg-resources \
|
|
||||||
python3-reportlab \
|
|
||||||
qpdf \
|
|
||||||
tesseract-ocr \
|
|
||||||
zlib1g \
|
|
||||||
unpaper
|
|
||||||
|
|
||||||
We will need a newer version of ``pip`` then was available for Ubuntu 18.04:
|
|
||||||
|
|
||||||
.. code-block:: bash
|
|
||||||
|
|
||||||
wget https://bootstrap.pypa.io/get-pip.py && python3 get-pip.py
|
|
||||||
|
|
||||||
Then install the most recent ocrmypdf for the local user and set the
|
|
||||||
user's ``PATH`` to check for the user's Python packages.
|
|
||||||
|
|
||||||
.. code-block:: bash
|
|
||||||
|
|
||||||
export PATH=$HOME/.local/bin:$PATH
|
|
||||||
python3 -m pip install --user ocrmypdf
|
|
||||||
|
|
||||||
To add JBIG2 encoding, see :ref:`jbig2`.
|
|
||||||
|
|
||||||
Arch Linux (AUR)
|
Arch Linux (AUR)
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
@@ -417,9 +374,9 @@ Native Windows
|
|||||||
|
|
||||||
You must install the following for Windows:
|
You must install the following for Windows:
|
||||||
|
|
||||||
* Python 3.7 (64-bit) or later
|
* Python 3.8 (64-bit) or later
|
||||||
* Tesseract 4.0 or later
|
* Tesseract 4.1.1 (64-bit) or later
|
||||||
* Ghostscript 9.50 or later
|
* Ghostscript 9.50 (64-bit) or later
|
||||||
|
|
||||||
Using the `Chocolatey <https://chocolatey.org/>`_ package manager, install the
|
Using the `Chocolatey <https://chocolatey.org/>`_ package manager, install the
|
||||||
following when running in an Administrator command prompt:
|
following when running in an Administrator command prompt:
|
||||||
@@ -438,10 +395,8 @@ Administrator.):
|
|||||||
|
|
||||||
* ``pip install ocrmypdf``
|
* ``pip install ocrmypdf``
|
||||||
|
|
||||||
Chocolatey automatically selects appropriate versions of these applications. If you
|
Chocolatey automatically selects appropriate versions of these applications. Please make sure
|
||||||
are installing them manually, please install 64-bit versions of all applications for
|
you are installing the 64-bit versions.
|
||||||
64-bit Windows, or 32-bit versions of all applications for 32-bit Windows. Mixing
|
|
||||||
the "bitness" of these programs will lead to errors.
|
|
||||||
|
|
||||||
OCRmyPDF will check the Windows Registry and standard locations in your Program Files
|
OCRmyPDF will check the Windows Registry and standard locations in your Program Files
|
||||||
for third party software it needs (specifically, Tesseract and Ghostscript). To
|
for third party software it needs (specifically, Tesseract and Ghostscript). To
|
||||||
@@ -456,6 +411,10 @@ to change the PATH.
|
|||||||
Please download Python from Python.org or Chocolatey instead, and do not use the
|
Please download Python from Python.org or Chocolatey instead, and do not use the
|
||||||
Microsoft Store version.
|
Microsoft Store version.
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
|
||||||
|
32-bit Windows might work, but is not supported.
|
||||||
|
|
||||||
Windows Subsystem for Linux
|
Windows Subsystem for Linux
|
||||||
---------------------------
|
---------------------------
|
||||||
|
|
||||||
@@ -481,7 +440,7 @@ Cygwin64
|
|||||||
|
|
||||||
First install the the following prerequisite Cygwin packages using ``setup-x86_64.exe``::
|
First install the the following prerequisite Cygwin packages using ``setup-x86_64.exe``::
|
||||||
|
|
||||||
python37 (or later)
|
python38 (or later)
|
||||||
python3?-devel
|
python3?-devel
|
||||||
python3?-pip
|
python3?-pip
|
||||||
python3?-lxml
|
python3?-lxml
|
||||||
@@ -616,9 +575,9 @@ manager. ``pip`` cannot provide them.
|
|||||||
|
|
||||||
The following versions are required:
|
The following versions are required:
|
||||||
|
|
||||||
- Python 3.7 or newer
|
- Python 3.8 or newer
|
||||||
- Ghostscript 9.23 or newer
|
- Ghostscript 9.50 or newer
|
||||||
- Tesseract 4.0.0 or newer
|
- Tesseract 4.1.1 or newer
|
||||||
- jbig2enc 0.29 or newer
|
- jbig2enc 0.29 or newer
|
||||||
- pngquant 2.5 or newer
|
- pngquant 2.5 or newer
|
||||||
- unpaper 6.1
|
- unpaper 6.1
|
||||||
@@ -649,7 +608,7 @@ unfortunately, the ``pip install`` command cannot satisfy all of them.
|
|||||||
Installing HEAD revision from sources
|
Installing HEAD revision from sources
|
||||||
=====================================
|
=====================================
|
||||||
|
|
||||||
If you have ``git`` and Python 3.7 or newer installed, you can install
|
If you have ``git`` and Python 3.8 or newer installed, you can install
|
||||||
from source. When the ``pip`` installer runs, it will alert you if
|
from source. When the ``pip`` installer runs, it will alert you if
|
||||||
dependencies are missing.
|
dependencies are missing.
|
||||||
|
|
||||||
@@ -679,7 +638,7 @@ system-wide:
|
|||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
git clone -b master https://github.com/ocrmypdf/OCRmyPDF.git
|
git clone -b master https://github.com/ocrmypdf/OCRmyPDF.git
|
||||||
python3 -m venv
|
python3 -m venv venv
|
||||||
source venv/bin/activate
|
source venv/bin/activate
|
||||||
cd OCRmyPDF
|
cd OCRmyPDF
|
||||||
pip install .
|
pip install .
|
||||||
|
|||||||
@@ -85,7 +85,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
|
resolution (DPI) needed to capture all of the information on that page
|
||||||
without losing content. It uses
|
without losing content. It uses
|
||||||
`Ghostscript <http://ghostscript.com/>`__ to rasterize the page, and
|
`Ghostscript <http://ghostscript.com/>`__ to rasterize the page, and
|
||||||
then performs on OCR the rasterized image to create an OCR "layer".
|
then performs OCR on the rasterized image to create an OCR "layer".
|
||||||
The layer is then grafted back onto the original PDF.
|
The layer is then grafted back onto the original PDF.
|
||||||
|
|
||||||
While one can use a program like Ghostscript or ImageMagick to get an
|
While one can use a program like Ghostscript or ImageMagick to get an
|
||||||
@@ -190,8 +190,7 @@ Ghostscript also imposes some limitations:
|
|||||||
behavior can be suppressed by setting ``--pdfa-image-compression`` to
|
behavior can be suppressed by setting ``--pdfa-image-compression`` to
|
||||||
``jpeg`` or ``lossless`` to set all images to one type or the other.
|
``jpeg`` or ``lossless`` to set all images to one type or the other.
|
||||||
Ghostscript has no option to maintain the input image's format.
|
Ghostscript has no option to maintain the input image's format.
|
||||||
(Ghostscript 9.25+ can copy JPEG images without transcoding them;
|
(Modern Ghostscript can copy JPEG images without transcoding them.)
|
||||||
earlier versions will transcode.)
|
|
||||||
- Ghostscript's PDF/A conversion removes any XMP metadata that is not
|
- Ghostscript's PDF/A conversion removes any XMP metadata that is not
|
||||||
one of the standard XMP metadata namespaces for PDFs. In particular,
|
one of the standard XMP metadata namespaces for PDFs. In particular,
|
||||||
PRISM Metdata is removed.
|
PRISM Metdata is removed.
|
||||||
|
|||||||
+2
-1
@@ -37,7 +37,8 @@ For all other Linux, you must build a JBIG2 encoder from source:
|
|||||||
.. _jbig2-lossy:
|
.. _jbig2-lossy:
|
||||||
|
|
||||||
Dependencies include libtoolize and libleptonica, which on Ubuntu systems
|
Dependencies include libtoolize and libleptonica, which on Ubuntu systems
|
||||||
are packaged as libtool and libleptonica-dev.
|
are packaged as libtool and libleptonica-dev. On Fedora (35) they are packaged
|
||||||
|
as libtool and leptonica-devel.
|
||||||
|
|
||||||
Lossy mode JBIG2
|
Lossy mode JBIG2
|
||||||
================
|
================
|
||||||
|
|||||||
+6
-10
@@ -76,20 +76,16 @@ Setuptools plugins
|
|||||||
You can also create a plugin that OCRmyPDF will always automatically load if both are
|
You can also create a plugin that OCRmyPDF will always automatically load if both are
|
||||||
installed in the same virtual environment, using a setuptools entrypoint.
|
installed in the same virtual environment, using a setuptools entrypoint.
|
||||||
|
|
||||||
Your package's ``setup.py`` would need to contain the following, for a plugin
|
Your package's ``pyproject.toml`` would need to contain the following, for a plugin
|
||||||
named ``ocrmypdf-exampleplugin``:
|
named ``ocrmypdf-exampleplugin``:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: toml
|
||||||
|
|
||||||
# sample ./setup.py file
|
[project]
|
||||||
from setuptools import setup
|
name = "ocrmypdf-exampleplugin"
|
||||||
|
|
||||||
setup(
|
[project.entry-points."ocrmypdf"]
|
||||||
name="ocrmypdf-exampleplugin",
|
exampleplugin = "exampleplugin.pluginmodule"
|
||||||
packages=["exampleplugin"],
|
|
||||||
# the following makes a plugin available to pytest
|
|
||||||
entry_points={"ocrmypdf": ["exampleplugin = exampleplugin.pluginmodule"]},
|
|
||||||
)
|
|
||||||
|
|
||||||
.. code-block:: ini
|
.. code-block:: ini
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,36 @@ tagged yet.
|
|||||||
|
|
||||||
.. |OCRmyPDF PyPI| image:: https://img.shields.io/pypi/v/ocrmypdf.svg
|
.. |OCRmyPDF PyPI| image:: https://img.shields.io/pypi/v/ocrmypdf.svg
|
||||||
|
|
||||||
|
v14.0.2
|
||||||
|
=======
|
||||||
|
|
||||||
|
- Fixed :issue:`1052`, an exception on attempting to process certain nonconforming PDFs.
|
||||||
|
- Explicitly documented that Windows 32-bit is no longer supported.
|
||||||
|
- Fixed source installation instructions.
|
||||||
|
- Other documentation fixes.
|
||||||
|
|
||||||
|
v14.0.1
|
||||||
|
=======
|
||||||
|
|
||||||
|
- Fixed some version checks done with smart version comparison.
|
||||||
|
- Added missing jbig2dec to Docker image.
|
||||||
|
|
||||||
|
v14.0.0
|
||||||
|
=======
|
||||||
|
|
||||||
|
- Dropped support for Python 3.7.
|
||||||
|
- Dropped support generally speaking, all dependencies older than what Ubuntu 20.04
|
||||||
|
provides.
|
||||||
|
- Ghostscript 9.50 or newer is now required. Shims to support old versions were
|
||||||
|
removed.
|
||||||
|
- Tesseract 4.1.1 or newer is now required. Shims to support old versions were
|
||||||
|
removed.
|
||||||
|
- Docker image now uses Tesseract 5.
|
||||||
|
- Dropped setup.cfg configuration for pyproject.toml.
|
||||||
|
- Removed deprecation exception PdfMergeFailedError.
|
||||||
|
- A few more public domain test files were removed or replaced. We are aiming for
|
||||||
|
100% compliance with SPDX and generally towards simplifying copyright.
|
||||||
|
|
||||||
v13.7.0
|
v13.7.0
|
||||||
=======
|
=======
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# SPDX-FileCopyrightText: 2022 James R. Barlow
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
---
|
---
|
||||||
version: "3.3"
|
version: "3.3"
|
||||||
services:
|
services:
|
||||||
|
|||||||
+3
-4
@@ -2,6 +2,8 @@
|
|||||||
# SPDX-FileCopyrightText: 2017 Enantiomerie
|
# SPDX-FileCopyrightText: 2017 Enantiomerie
|
||||||
# SPDX-License-Identifier: MIT
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
"""Example OCRmyPDF for Synology NAS"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
# This script must be edited to meet your needs.
|
# This script must be edited to meet your needs.
|
||||||
@@ -25,10 +27,7 @@ logging.basicConfig(
|
|||||||
filemode='w',
|
filemode='w',
|
||||||
)
|
)
|
||||||
|
|
||||||
if len(sys.argv) > 1:
|
start_dir = sys.argv[1] if len(sys.argv) > 1 else '.'
|
||||||
start_dir = 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)
|
logging.info(dir_name)
|
||||||
|
|||||||
@@ -103,6 +103,8 @@ def execute_ocrmypdf(file_path):
|
|||||||
elif ON_SUCCESS_ARCHIVE:
|
elif ON_SUCCESS_ARCHIVE:
|
||||||
log.info(f'OCR is done. Archiving {file_path.name} to {ARCHIVE_DIRECTORY}')
|
log.info(f'OCR is done. Archiving {file_path.name} to {ARCHIVE_DIRECTORY}')
|
||||||
shutil.move(file_path, f'{ARCHIVE_DIRECTORY}/{file_path.name}')
|
shutil.move(file_path, f'{ARCHIVE_DIRECTORY}/{file_path.name}')
|
||||||
|
else:
|
||||||
|
log.info('OCR is done')
|
||||||
else:
|
else:
|
||||||
log.info('OCR is done')
|
log.info('OCR is done')
|
||||||
|
|
||||||
|
|||||||
+89
-4
@@ -1,16 +1,102 @@
|
|||||||
|
# SPDX-FileCopyrightText: 2022 James R. Barlow
|
||||||
|
# SPDX-License-Identifier: MPL-2.0
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = [
|
requires = [
|
||||||
"setuptools >= 52",
|
"setuptools >= 61",
|
||||||
"setuptools_scm[toml] >= 7.0.5",
|
"setuptools_scm[toml] >= 7.0.5",
|
||||||
"wheel"
|
"wheel"
|
||||||
]
|
]
|
||||||
build-backend = "setuptools.build_meta"
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "ocrmypdf"
|
||||||
|
dynamic = ["version"]
|
||||||
|
description = "OCRmyPDF adds an OCR text layer to scanned PDF files, allowing them to be searched"
|
||||||
|
readme = "README.md"
|
||||||
|
license = {text = "MPL-2.0"}
|
||||||
|
requires-python = ">=3.8"
|
||||||
|
dependencies = [
|
||||||
|
"Pillow>=8.2.0",
|
||||||
|
"coloredlogs>=14.0",
|
||||||
|
"deprecation>=2.1.0",
|
||||||
|
"img2pdf>=0.3.0", # pure Python
|
||||||
|
"packaging>=20",
|
||||||
|
"pdfminer.six>=20201018",
|
||||||
|
"pikepdf>=5.0.1",
|
||||||
|
"pluggy>=0.13.0",
|
||||||
|
"reportlab>=3.5.66",
|
||||||
|
"tqdm>=4",
|
||||||
|
"importlib-resources>=5;python_version<'3.9'", # until Python 3.9
|
||||||
|
"typing-extensions>=4;python_version<'3.10'",
|
||||||
|
]
|
||||||
|
authors = [{name = "James R. Barlow", email="james@purplerock.ca"}]
|
||||||
|
classifiers = [
|
||||||
|
"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",
|
||||||
|
"Programming Language :: Python :: 3",
|
||||||
|
"Programming Language :: Python :: 3 :: Only",
|
||||||
|
"Programming Language :: Python :: 3.8",
|
||||||
|
"Programming Language :: Python :: 3.9",
|
||||||
|
"Programming Language :: Python :: 3.10",
|
||||||
|
"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/ocrmypdf/OCRmyPDF"
|
||||||
|
Tracker = "https://github.com/ocrmypdf/OCRmyPDF/issues"
|
||||||
|
|
||||||
|
[project.optional-dependencies]
|
||||||
|
docs = ["sphinx", "sphinx-issues", "sphinx-rtd-theme"]
|
||||||
|
extended_test = ["PyMuPDF==1.19.1"]
|
||||||
|
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
|
||||||
|
"types-Pillow",
|
||||||
|
"types-humanfriendly",
|
||||||
|
]
|
||||||
|
watcher = ["watchdog>=1.0.2"]
|
||||||
|
webservice = ["Flask>=1"]
|
||||||
|
|
||||||
|
[project.scripts]
|
||||||
|
ocrmypdf = "ocrmypdf.__main__:run"
|
||||||
|
|
||||||
|
[tool.setuptools.package-data]
|
||||||
|
ocrmypdf = ["data/sRGB.icc", "py.typed"]
|
||||||
|
|
||||||
|
[tool.setuptools.packages.find]
|
||||||
|
where = ["src"]
|
||||||
|
namespaces = false
|
||||||
|
|
||||||
[tool.setuptools_scm]
|
[tool.setuptools_scm]
|
||||||
|
|
||||||
|
[tool.distutils.bdist_wheel]
|
||||||
|
python-tag = "py38"
|
||||||
|
|
||||||
[tool.black]
|
[tool.black]
|
||||||
line-length = 88
|
line-length = 88
|
||||||
target-version = ["py37", "py38"]
|
target-version = ["py38", "py39", "py310", "py311"]
|
||||||
skip-string-normalization = true
|
skip-string-normalization = true
|
||||||
include = '\.pyi?$'
|
include = '\.pyi?$'
|
||||||
exclude = '''
|
exclude = '''
|
||||||
@@ -94,8 +180,7 @@ module = [
|
|||||||
'pdfminer.*',
|
'pdfminer.*',
|
||||||
'reportlab.*',
|
'reportlab.*',
|
||||||
'fitz',
|
'fitz',
|
||||||
'libxmp.utils',
|
'libxmp.utils'
|
||||||
'importlib_metadata'
|
|
||||||
]
|
]
|
||||||
ignore_missing_imports = true
|
ignore_missing_imports = true
|
||||||
|
|
||||||
|
|||||||
@@ -1,116 +0,0 @@
|
|||||||
[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
|
|
||||||
url = https://github.com/ocrmypdf/OCRmyPDF
|
|
||||||
author = James R. Barlow
|
|
||||||
author_email = james@purplerock.ca
|
|
||||||
license = MPL-2.0
|
|
||||||
license_file = LICENSE
|
|
||||||
license_files =
|
|
||||||
LICENSE
|
|
||||||
classifiers =
|
|
||||||
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
|
|
||||||
Programming Language :: Python :: 3
|
|
||||||
Programming Language :: Python :: 3 :: Only
|
|
||||||
Programming Language :: Python :: 3.7
|
|
||||||
Programming Language :: Python :: 3.8
|
|
||||||
Programming Language :: Python :: 3.9
|
|
||||||
Programming Language :: Python :: 3.10
|
|
||||||
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/ocrmypdf/OCRmyPDF
|
|
||||||
Tracker = https://github.com/ocrmypdf/OCRmyPDF/issues
|
|
||||||
|
|
||||||
[options]
|
|
||||||
packages = find:
|
|
||||||
install_requires =
|
|
||||||
Pillow>=8.2.0
|
|
||||||
coloredlogs>=14.0 # strictly optional
|
|
||||||
img2pdf>=0.3.0 # pure Python
|
|
||||||
packaging>=20
|
|
||||||
pdfminer.six!=20200720,>=20191110
|
|
||||||
pikepdf!=5.0.0,>=4.0.0
|
|
||||||
pluggy>=0.13.0
|
|
||||||
reportlab>=3.5.66
|
|
||||||
tqdm>=4
|
|
||||||
importlib-metadata>=4;python_version<'3.8' # until Python 3.8
|
|
||||||
importlib-resources>=5;python_version<'3.9' # until Python 3.9
|
|
||||||
typing-extensions>=4;python_version<'3.10'
|
|
||||||
python_requires = >=3.7
|
|
||||||
include_package_data = True
|
|
||||||
package_dir =
|
|
||||||
=src
|
|
||||||
platforms = any
|
|
||||||
setup_requires =
|
|
||||||
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.19.1
|
|
||||||
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
|
|
||||||
types-Pillow
|
|
||||||
types-humanfriendly
|
|
||||||
watcher =
|
|
||||||
watchdog>=1.0.2
|
|
||||||
webservice =
|
|
||||||
Flask>=1
|
|
||||||
|
|
||||||
[options.package_data]
|
|
||||||
ocrmypdf =
|
|
||||||
data/sRGB.icc
|
|
||||||
py.typed
|
|
||||||
|
|
||||||
[bdist_wheel]
|
|
||||||
python-tag = py37
|
|
||||||
|
|
||||||
[aliases]
|
|
||||||
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
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
# SPDX-FileCopyrightText: 2022 James R. Barlow
|
|
||||||
# SPDX-License-Identifier: MPL-2.0
|
|
||||||
|
|
||||||
"""setup.py to support older setuptools and pip."""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from setuptools import setup
|
|
||||||
|
|
||||||
setup()
|
|
||||||
@@ -1,3 +1,7 @@
|
|||||||
|
# SPDX-FileCopyrightText: 2022 Alexander Langanke
|
||||||
|
# SPDX-FileCopyrightText: 2022 James R. Barlow
|
||||||
|
# SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
name: ocrmypdf
|
name: ocrmypdf
|
||||||
title: OCRmyPDF
|
title: OCRmyPDF
|
||||||
base: core20
|
base: core20
|
||||||
@@ -41,6 +45,13 @@ parts:
|
|||||||
stage-packages:
|
stage-packages:
|
||||||
- lib32stdc++6
|
- lib32stdc++6
|
||||||
|
|
||||||
|
jbig2enc:
|
||||||
|
plugin: autotools
|
||||||
|
source: https://github.com/agl/jbig2enc.git
|
||||||
|
source-tag: '0.29'
|
||||||
|
build-packages:
|
||||||
|
- libleptonica-dev
|
||||||
|
|
||||||
ocrmypdf:
|
ocrmypdf:
|
||||||
plugin: python
|
plugin: python
|
||||||
source: https://github.com/ocrmypdf/OCRmyPDF.git
|
source: https://github.com/ocrmypdf/OCRmyPDF.git
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
<!-- SPDX-FileCopyrightText: 2022 James R. Barlow -->
|
||||||
|
<!-- SPDX-License-Identifier: CC-BY-SA-4.0 -->
|
||||||
|
|
||||||
# Release checklist
|
# Release checklist
|
||||||
|
|
||||||
## Patch release
|
## Patch release
|
||||||
@@ -14,11 +17,11 @@
|
|||||||
|
|
||||||
- Check README.md
|
- Check README.md
|
||||||
|
|
||||||
- Check setup.py
|
- Check pyproject.toml
|
||||||
|
|
||||||
- Are classifiers up to date?
|
- Are classifiers up to date?
|
||||||
- Is `python_requires` correct?
|
- Is `python_requires` correct?
|
||||||
- Python 3.6 is EOL on December 2021-12. Could drop support then.
|
- Is it to drop support for older Pythons?
|
||||||
- Can we tighten any `install_requires` dependencies?
|
- Can we tighten any `install_requires` dependencies?
|
||||||
|
|
||||||
- Search for old version shims we can remove
|
- Search for old version shims we can remove
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ from ocrmypdf.exceptions import (
|
|||||||
InputFileError,
|
InputFileError,
|
||||||
MissingDependencyError,
|
MissingDependencyError,
|
||||||
OutputFileAccessError,
|
OutputFileAccessError,
|
||||||
PdfMergeFailedError,
|
|
||||||
PriorOcrFoundError,
|
PriorOcrFoundError,
|
||||||
SubprocessOutputError,
|
SubprocessOutputError,
|
||||||
TesseractConfigError,
|
TesseractConfigError,
|
||||||
|
|||||||
@@ -71,6 +71,4 @@ def run(args=None):
|
|||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
if sys.platform == 'darwin' and sys.version_info < (3, 8):
|
|
||||||
set_start_method('spawn') # see python bpo-33725
|
|
||||||
sys.exit(run())
|
sys.exit(run())
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ from os import fspath
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from subprocess import PIPE, CalledProcessError
|
from subprocess import PIPE, CalledProcessError
|
||||||
|
|
||||||
|
from packaging.version import Version
|
||||||
from PIL import Image, UnidentifiedImageError
|
from PIL import Image, UnidentifiedImageError
|
||||||
|
|
||||||
from ocrmypdf.exceptions import SubprocessOutputError
|
from ocrmypdf.exceptions import SubprocessOutputError
|
||||||
@@ -47,21 +48,6 @@ def version():
|
|||||||
return get_version(GS)
|
return get_version(GS)
|
||||||
|
|
||||||
|
|
||||||
def jpeg_passthrough_available() -> bool:
|
|
||||||
"""Returns True if the installed version of Ghostscript supports JPEG passthru
|
|
||||||
|
|
||||||
Prior to 9.23, Ghostscript decoded and re-encoded JPEGs internally. In 9.23
|
|
||||||
it gained the ability to keep JPEGs unmodified. However, the 9.23
|
|
||||||
implementation was buggy and would deletes the last two bytes of images in
|
|
||||||
some cases, as reported here.
|
|
||||||
https://bugs.ghostscript.com/show_bug.cgi?id=699216
|
|
||||||
|
|
||||||
The issue was fixed for 9.24, hence that is the first version we consider
|
|
||||||
the feature available. (Ghostscript 9.24 has its own problems is blacklisted.)
|
|
||||||
"""
|
|
||||||
return version() >= '9.24'
|
|
||||||
|
|
||||||
|
|
||||||
def _gs_error_reported(stream) -> bool:
|
def _gs_error_reported(stream) -> bool:
|
||||||
match = re.search(r'error', stream, flags=re.IGNORECASE)
|
match = re.search(r'error', stream, flags=re.IGNORECASE)
|
||||||
return bool(match)
|
return bool(match)
|
||||||
@@ -201,20 +187,10 @@ def generate_pdfa(
|
|||||||
]
|
]
|
||||||
|
|
||||||
strategy = 'LeaveColorUnchanged'
|
strategy = 'LeaveColorUnchanged'
|
||||||
# Older versions of Ghostscript expect a leading slash in
|
gs_version = Version(version())
|
||||||
# sColorConversionStrategy, newer ones should not have it. See Ghostscript
|
if gs_version == Version('9.56.0'):
|
||||||
# git commit fe1c025d.
|
# 9.56.0 breaks our OCR, should be fixed in 9.56.1
|
||||||
gs_version = version()
|
# https://bugs.ghostscript.com/show_bug.cgi?id=705187
|
||||||
strategy = ('/' + strategy) if gs_version < '9.19' else strategy
|
|
||||||
|
|
||||||
if gs_version == '9.23':
|
|
||||||
# 9.23: added JPEG passthrough as a new feature, but with a bug that
|
|
||||||
# incorrectly formats some images. Fixed as of 9.24. So we disable this
|
|
||||||
# feature for 9.23.
|
|
||||||
# https://bugs.ghostscript.com/show_bug.cgi?id=699216
|
|
||||||
compression_args.append('-dPassThroughJPEGImages=false')
|
|
||||||
elif gs_version == '9.56.0':
|
|
||||||
# 9.56.0 breaks our OCR...?
|
|
||||||
compression_args.append('-dNEWPDF=false')
|
compression_args.append('-dNEWPDF=false')
|
||||||
|
|
||||||
# nb no need to specify ProcessColorModel when ColorConversionStrategy
|
# nb no need to specify ProcessColorModel when ColorConversionStrategy
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ HOCR_TEMPLATE = """<?xml version="1.0" encoding="UTF-8"?>
|
|||||||
<head>
|
<head>
|
||||||
<title></title>
|
<title></title>
|
||||||
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
|
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
|
||||||
<meta name='ocr-system' content='tesseract 4.0.0' />
|
<meta name='ocr-system' content='tesseract 4.1.1' />
|
||||||
<meta name='ocr-capabilities' content='ocr_page ocr_carea ocr_par ocr_line ocrx_word ocrp_wconf'/>
|
<meta name='ocr-capabilities' content='ocr_page ocr_carea ocr_par ocr_line ocrx_word ocrp_wconf'/>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -114,15 +114,6 @@ def version() -> str:
|
|||||||
return get_version('tesseract', regex=r'tesseract\s(.+)')
|
return get_version('tesseract', regex=r'tesseract\s(.+)')
|
||||||
|
|
||||||
|
|
||||||
def has_user_words() -> bool:
|
|
||||||
"""Does Tesseract have --user-words capability?
|
|
||||||
|
|
||||||
Not available in 4.0, but available in 4.1. Also available in 3.x, but
|
|
||||||
we no longer support 3.x.
|
|
||||||
"""
|
|
||||||
return version() >= '4.1'
|
|
||||||
|
|
||||||
|
|
||||||
def has_thresholding() -> bool:
|
def has_thresholding() -> bool:
|
||||||
"""Does Tesseract have -c thresholding method capability?"""
|
"""Does Tesseract have -c thresholding method capability?"""
|
||||||
return version() >= '5.0'
|
return version() >= '5.0'
|
||||||
@@ -244,7 +235,7 @@ def get_deskew(
|
|||||||
|
|
||||||
def tesseract_log_output(stream: bytes) -> None:
|
def tesseract_log_output(stream: bytes) -> None:
|
||||||
tlog = TesseractLoggerAdapter(
|
tlog = TesseractLoggerAdapter(
|
||||||
log, extra=log.extra if hasattr(log, 'extra') else None
|
log, extra=log.extra if hasattr(log, 'extra') else None # type: ignore
|
||||||
)
|
)
|
||||||
|
|
||||||
if not stream:
|
if not stream:
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
# SPDX-FileCopyrightText: 2022 James R. Barlow
|
# SPDX-FileCopyrightText: 2022 James R. Barlow
|
||||||
# SPDX-License-Identifier: MPL-2.0
|
# SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
# unpaper documentation:
|
|
||||||
# https://github.com/Flameeyes/unpaper/blob/master/doc/basic-concepts.md
|
|
||||||
"""Interface to unpaper executable"""
|
"""Interface to unpaper executable"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import shlex
|
import shlex
|
||||||
@@ -22,6 +20,10 @@ from PIL import Image
|
|||||||
from ocrmypdf.exceptions import MissingDependencyError, SubprocessOutputError
|
from ocrmypdf.exceptions import MissingDependencyError, SubprocessOutputError
|
||||||
from ocrmypdf.subprocess import get_version, run
|
from ocrmypdf.subprocess import get_version, run
|
||||||
|
|
||||||
|
# unpaper documentation:
|
||||||
|
# https://github.com/Flameeyes/unpaper/blob/master/doc/basic-concepts.md
|
||||||
|
|
||||||
|
|
||||||
if sys.version_info >= (3, 10):
|
if sys.version_info >= (3, 10):
|
||||||
from tempfile import TemporaryDirectory
|
from tempfile import TemporaryDirectory
|
||||||
else:
|
else:
|
||||||
|
|||||||
+47
-42
@@ -1,4 +1,5 @@
|
|||||||
# SPDX-FileCopyrightText: 2022 James R. Barlow
|
# SPDX-FileCopyrightText: 2018-2022 James R. Barlow
|
||||||
|
# SPDX-FileCopyrightText: 2019 Martin Wind
|
||||||
# SPDX-License-Identifier: MPL-2.0
|
# SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
"""OCRmyPDF page processing pipeline functions."""
|
"""OCRmyPDF page processing pipeline functions."""
|
||||||
@@ -13,7 +14,7 @@ from contextlib import suppress
|
|||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from shutil import copyfileobj
|
from shutil import copyfileobj
|
||||||
from typing import Iterable
|
from typing import Any, Iterable, Sequence
|
||||||
|
|
||||||
import img2pdf
|
import img2pdf
|
||||||
import pikepdf
|
import pikepdf
|
||||||
@@ -35,7 +36,7 @@ from ocrmypdf.exceptions import (
|
|||||||
from ocrmypdf.helpers import IMG2PDF_KWARGS, Resolution, safe_symlink
|
from ocrmypdf.helpers import IMG2PDF_KWARGS, Resolution, safe_symlink
|
||||||
from ocrmypdf.hocrtransform import HocrTransform
|
from ocrmypdf.hocrtransform import HocrTransform
|
||||||
from ocrmypdf.pdfa import generate_pdfa_ps
|
from ocrmypdf.pdfa import generate_pdfa_ps
|
||||||
from ocrmypdf.pdfinfo import Colorspace, Encoding, PdfInfo
|
from ocrmypdf.pdfinfo import Colorspace, Encoding, PageInfo, PdfInfo
|
||||||
|
|
||||||
# Remove this workaround when we require Pillow >= 10
|
# Remove this workaround when we require Pillow >= 10
|
||||||
try:
|
try:
|
||||||
@@ -49,7 +50,7 @@ log = logging.getLogger(__name__)
|
|||||||
VECTOR_PAGE_DPI = 400
|
VECTOR_PAGE_DPI = 400
|
||||||
|
|
||||||
|
|
||||||
def triage_image_file(input_file, output_file, options):
|
def triage_image_file(input_file: Path, output_file: Path, options) -> None:
|
||||||
log.info("Input file is not a PDF, checking if it is an image...")
|
log.info("Input file is not a PDF, checking if it is an image...")
|
||||||
try:
|
try:
|
||||||
im = Image.open(input_file)
|
im = Image.open(input_file)
|
||||||
@@ -113,7 +114,7 @@ def triage_image_file(input_file, output_file, options):
|
|||||||
raise UnsupportedImageFormatError() from e
|
raise UnsupportedImageFormatError() from e
|
||||||
|
|
||||||
|
|
||||||
def _pdf_guess_version(input_file, search_window=1024):
|
def _pdf_guess_version(input_file: Path, search_window=1024) -> str:
|
||||||
"""Try to find version signature at start of file.
|
"""Try to find version signature at start of file.
|
||||||
|
|
||||||
Not robust enough to deal with appended files.
|
Not robust enough to deal with appended files.
|
||||||
@@ -125,11 +126,13 @@ def _pdf_guess_version(input_file, search_window=1024):
|
|||||||
signature = f.read(search_window)
|
signature = f.read(search_window)
|
||||||
m = re.search(br'%PDF-(\d\.\d)', signature)
|
m = re.search(br'%PDF-(\d\.\d)', signature)
|
||||||
if m:
|
if m:
|
||||||
return m.group(1)
|
return m.group(1).decode('ascii')
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
|
|
||||||
def triage(original_filename, input_file, output_file, options):
|
def triage(
|
||||||
|
original_filename: str, input_file: Path, output_file: Path, options
|
||||||
|
) -> Path:
|
||||||
try:
|
try:
|
||||||
if _pdf_guess_version(input_file):
|
if _pdf_guess_version(input_file):
|
||||||
if options.image_dpi:
|
if options.image_dpi:
|
||||||
@@ -153,9 +156,9 @@ def get_pdfinfo(
|
|||||||
input_file,
|
input_file,
|
||||||
*,
|
*,
|
||||||
executor: Executor,
|
executor: Executor,
|
||||||
detailed_analysis=False,
|
detailed_analysis: bool = False,
|
||||||
progbar=False,
|
progbar: bool = False,
|
||||||
max_workers=None,
|
max_workers: int | None = None,
|
||||||
check_pages=None,
|
check_pages=None,
|
||||||
) -> PdfInfo:
|
) -> PdfInfo:
|
||||||
try:
|
try:
|
||||||
@@ -173,7 +176,7 @@ def get_pdfinfo(
|
|||||||
raise InputFileError() from e
|
raise InputFileError() from e
|
||||||
|
|
||||||
|
|
||||||
def validate_pdfinfo_options(context: PdfContext):
|
def validate_pdfinfo_options(context: PdfContext) -> None:
|
||||||
pdfinfo = context.pdfinfo
|
pdfinfo = context.pdfinfo
|
||||||
options = context.options
|
options = context.options
|
||||||
|
|
||||||
@@ -214,11 +217,11 @@ def validate_pdfinfo_options(context: PdfContext):
|
|||||||
context.plugin_manager.hook.validate(pdfinfo=pdfinfo, options=options)
|
context.plugin_manager.hook.validate(pdfinfo=pdfinfo, options=options)
|
||||||
|
|
||||||
|
|
||||||
def _vector_page_dpi(pageinfo):
|
def _vector_page_dpi(pageinfo: PageInfo) -> int:
|
||||||
return VECTOR_PAGE_DPI if pageinfo.has_vector or pageinfo.has_text else 0.0
|
return VECTOR_PAGE_DPI if pageinfo.has_vector or pageinfo.has_text else 0
|
||||||
|
|
||||||
|
|
||||||
def get_page_dpi(pageinfo, options):
|
def get_page_dpi(pageinfo: PageInfo, options) -> Resolution:
|
||||||
"Get the DPI when nonsquare DPI is tolerable"
|
"Get the DPI when nonsquare DPI is tolerable"
|
||||||
xres = max(
|
xres = max(
|
||||||
pageinfo.dpi.x or VECTOR_PAGE_DPI,
|
pageinfo.dpi.x or VECTOR_PAGE_DPI,
|
||||||
@@ -233,7 +236,7 @@ def get_page_dpi(pageinfo, options):
|
|||||||
return Resolution(float(xres), float(yres))
|
return Resolution(float(xres), float(yres))
|
||||||
|
|
||||||
|
|
||||||
def get_page_square_dpi(pageinfo, options) -> Resolution:
|
def get_page_square_dpi(pageinfo: PageInfo, options) -> Resolution:
|
||||||
"Get the DPI when we require xres == yres, scaled to physical units"
|
"Get the DPI when we require xres == yres, scaled to physical units"
|
||||||
xres = pageinfo.dpi.x or 0.0
|
xres = pageinfo.dpi.x or 0.0
|
||||||
yres = pageinfo.dpi.y or 0.0
|
yres = pageinfo.dpi.y or 0.0
|
||||||
@@ -249,7 +252,7 @@ def get_page_square_dpi(pageinfo, options) -> Resolution:
|
|||||||
return Resolution(units, units)
|
return Resolution(units, units)
|
||||||
|
|
||||||
|
|
||||||
def get_canvas_square_dpi(pageinfo, options) -> Resolution:
|
def get_canvas_square_dpi(pageinfo: PageInfo, options) -> Resolution:
|
||||||
"""Get the DPI when we require xres == yres, in Postscript units"""
|
"""Get the DPI when we require xres == yres, in Postscript units"""
|
||||||
units = float(
|
units = float(
|
||||||
max(
|
max(
|
||||||
@@ -262,7 +265,7 @@ def get_canvas_square_dpi(pageinfo, options) -> Resolution:
|
|||||||
return Resolution(units, units)
|
return Resolution(units, units)
|
||||||
|
|
||||||
|
|
||||||
def is_ocr_required(page_context: PageContext):
|
def is_ocr_required(page_context: PageContext) -> bool:
|
||||||
pageinfo = page_context.pageinfo
|
pageinfo = page_context.pageinfo
|
||||||
options = page_context.options
|
options = page_context.options
|
||||||
|
|
||||||
@@ -337,7 +340,7 @@ def is_ocr_required(page_context: PageContext):
|
|||||||
return ocr_required
|
return ocr_required
|
||||||
|
|
||||||
|
|
||||||
def rasterize_preview(input_file: Path, page_context: PageContext):
|
def rasterize_preview(input_file: Path, page_context: PageContext) -> Path:
|
||||||
output_file = page_context.get_path('rasterize_preview.jpg')
|
output_file = page_context.get_path('rasterize_preview.jpg')
|
||||||
canvas_dpi = get_canvas_square_dpi(page_context.pageinfo, page_context.options)
|
canvas_dpi = get_canvas_square_dpi(page_context.pageinfo, page_context.options)
|
||||||
page_dpi = get_page_square_dpi(page_context.pageinfo, page_context.options)
|
page_dpi = get_page_square_dpi(page_context.pageinfo, page_context.options)
|
||||||
@@ -354,7 +357,7 @@ def rasterize_preview(input_file: Path, page_context: PageContext):
|
|||||||
return output_file
|
return output_file
|
||||||
|
|
||||||
|
|
||||||
def describe_rotation(page_context: PageContext, orient_conf, correction: int):
|
def describe_rotation(page_context: PageContext, orient_conf, correction: int) -> str:
|
||||||
"""
|
"""
|
||||||
Describe the page rotation we are going to perform.
|
Describe the page rotation we are going to perform.
|
||||||
"""
|
"""
|
||||||
@@ -383,7 +386,7 @@ def describe_rotation(page_context: PageContext, orient_conf, correction: int):
|
|||||||
return f"{facing}, confidence {orient_conf.confidence:.2f} - {action}"
|
return f"{facing}, confidence {orient_conf.confidence:.2f} - {action}"
|
||||||
|
|
||||||
|
|
||||||
def get_orientation_correction(preview: Path, page_context: PageContext):
|
def get_orientation_correction(preview: Path, page_context: PageContext) -> int:
|
||||||
"""Work out orientation correct for each page.
|
"""Work out orientation correct for each page.
|
||||||
|
|
||||||
We ask Ghostscript to draw a preview page, which will rasterize with the
|
We ask Ghostscript to draw a preview page, which will rasterize with the
|
||||||
@@ -469,18 +472,17 @@ def rasterize(
|
|||||||
return output_file
|
return output_file
|
||||||
|
|
||||||
|
|
||||||
def preprocess_remove_background(input_file: Path, page_context: PageContext):
|
def preprocess_remove_background(input_file: Path, page_context: PageContext) -> Path:
|
||||||
if any(image.bpc > 1 for image in page_context.pageinfo.images):
|
if any(image.bpc > 1 for image in page_context.pageinfo.images):
|
||||||
raise NotImplementedError("--remove-background is temporarily not implemented")
|
raise NotImplementedError("--remove-background is temporarily not implemented")
|
||||||
# output_file = page_context.get_path('pp_rm_bg.png')
|
# output_file = page_context.get_path('pp_rm_bg.png')
|
||||||
# leptonica.remove_background(input_file, output_file)
|
# leptonica.remove_background(input_file, output_file)
|
||||||
# return output_file
|
# return output_file
|
||||||
else:
|
log.info("background removal skipped on mono page")
|
||||||
log.info("background removal skipped on mono page")
|
return input_file
|
||||||
return input_file
|
|
||||||
|
|
||||||
|
|
||||||
def preprocess_deskew(input_file: Path, page_context: PageContext):
|
def preprocess_deskew(input_file: Path, page_context: PageContext) -> Path:
|
||||||
output_file = page_context.get_path('pp_deskew.png')
|
output_file = page_context.get_path('pp_deskew.png')
|
||||||
dpi = get_page_square_dpi(page_context.pageinfo, page_context.options)
|
dpi = get_page_square_dpi(page_context.pageinfo, page_context.options)
|
||||||
|
|
||||||
@@ -500,7 +502,7 @@ def preprocess_deskew(input_file: Path, page_context: PageContext):
|
|||||||
return output_file
|
return output_file
|
||||||
|
|
||||||
|
|
||||||
def preprocess_clean(input_file: Path, page_context: PageContext):
|
def preprocess_clean(input_file: Path, page_context: PageContext) -> Path:
|
||||||
output_file = page_context.get_path('pp_clean.png')
|
output_file = page_context.get_path('pp_clean.png')
|
||||||
dpi = get_page_square_dpi(page_context.pageinfo, page_context.options)
|
dpi = get_page_square_dpi(page_context.pageinfo, page_context.options)
|
||||||
return unpaper.clean(
|
return unpaper.clean(
|
||||||
@@ -511,7 +513,7 @@ def preprocess_clean(input_file: Path, page_context: PageContext):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def create_ocr_image(image: Path, page_context: PageContext):
|
def create_ocr_image(image: Path, page_context: PageContext) -> Path:
|
||||||
"""Create the image we send for OCR. May not be the same as the display
|
"""Create the image we send for OCR. May not be the same as the display
|
||||||
image depending on preprocessing. This image will never be shown to the
|
image depending on preprocessing. This image will never be shown to the
|
||||||
user."""
|
user."""
|
||||||
@@ -559,7 +561,7 @@ def create_ocr_image(image: Path, page_context: PageContext):
|
|||||||
return output_file
|
return output_file
|
||||||
|
|
||||||
|
|
||||||
def ocr_engine_hocr(input_file: Path, page_context: PageContext):
|
def ocr_engine_hocr(input_file: Path, page_context: PageContext) -> tuple[Path, Path]:
|
||||||
hocr_out = page_context.get_path('ocr_hocr.hocr')
|
hocr_out = page_context.get_path('ocr_hocr.hocr')
|
||||||
hocr_text_out = page_context.get_path('ocr_hocr.txt')
|
hocr_text_out = page_context.get_path('ocr_hocr.txt')
|
||||||
options = page_context.options
|
options = page_context.options
|
||||||
@@ -574,7 +576,7 @@ def ocr_engine_hocr(input_file: Path, page_context: PageContext):
|
|||||||
return (hocr_out, hocr_text_out)
|
return (hocr_out, hocr_text_out)
|
||||||
|
|
||||||
|
|
||||||
def should_visible_page_image_use_jpg(pageinfo):
|
def should_visible_page_image_use_jpg(pageinfo: PageInfo) -> bool:
|
||||||
# If all images were JPEGs originally, produce a JPEG as output
|
# If all images were JPEGs originally, produce a JPEG as output
|
||||||
return pageinfo.images and all(im.enc == Encoding.jpeg for im in pageinfo.images)
|
return pageinfo.images and all(im.enc == Encoding.jpeg for im in pageinfo.images)
|
||||||
|
|
||||||
@@ -599,8 +601,8 @@ def create_visible_page_jpg(image: Path, page_context: PageContext) -> Path:
|
|||||||
|
|
||||||
|
|
||||||
def create_pdf_page_from_image(
|
def create_pdf_page_from_image(
|
||||||
image: Path, page_context: PageContext, orientation_correction
|
image: Path, page_context: PageContext, orientation_correction: int
|
||||||
):
|
) -> Path:
|
||||||
# We rasterize a square DPI version of each page because most image
|
# We rasterize a square DPI version of each page because most image
|
||||||
# processing tools don't support rectangular DPI. Use the square DPI as it
|
# processing tools don't support rectangular DPI. Use the square DPI as it
|
||||||
# accurately describes the image. It would be possible to resample the image
|
# accurately describes the image. It would be possible to resample the image
|
||||||
@@ -628,11 +630,10 @@ def create_pdf_page_from_image(
|
|||||||
output_file = page_context.plugin_manager.hook.filter_pdf_page(
|
output_file = page_context.plugin_manager.hook.filter_pdf_page(
|
||||||
page=page_context, image_filename=image, output_pdf=output_file
|
page=page_context, image_filename=image, output_pdf=output_file
|
||||||
)
|
)
|
||||||
|
|
||||||
return output_file
|
return output_file
|
||||||
|
|
||||||
|
|
||||||
def render_hocr_page(hocr: Path, page_context: PageContext):
|
def render_hocr_page(hocr: Path, page_context: PageContext) -> Path:
|
||||||
options = page_context.options
|
options = page_context.options
|
||||||
output_file = page_context.get_path('ocr_hocr.pdf')
|
output_file = page_context.get_path('ocr_hocr.pdf')
|
||||||
dpi = get_page_square_dpi(page_context.pageinfo, options)
|
dpi = get_page_square_dpi(page_context.pageinfo, options)
|
||||||
@@ -649,7 +650,9 @@ def render_hocr_page(hocr: Path, page_context: PageContext):
|
|||||||
return output_file
|
return output_file
|
||||||
|
|
||||||
|
|
||||||
def ocr_engine_textonly_pdf(input_image: Path, page_context: PageContext):
|
def ocr_engine_textonly_pdf(
|
||||||
|
input_image: Path, page_context: PageContext
|
||||||
|
) -> tuple[Path, Path]:
|
||||||
output_pdf = page_context.get_path('ocr_tess.pdf')
|
output_pdf = page_context.get_path('ocr_tess.pdf')
|
||||||
output_text = page_context.get_path('ocr_tess.txt')
|
output_text = page_context.get_path('ocr_tess.txt')
|
||||||
options = page_context.options
|
options = page_context.options
|
||||||
@@ -695,13 +698,13 @@ def get_docinfo(base_pdf: pikepdf.Pdf, context: PdfContext) -> dict[str, str]:
|
|||||||
return pdfmark
|
return pdfmark
|
||||||
|
|
||||||
|
|
||||||
def generate_postscript_stub(context: PdfContext):
|
def generate_postscript_stub(context: PdfContext) -> Path:
|
||||||
output_file = context.get_path('pdfa.ps')
|
output_file = context.get_path('pdfa.ps')
|
||||||
generate_pdfa_ps(output_file)
|
generate_pdfa_ps(output_file)
|
||||||
return output_file
|
return output_file
|
||||||
|
|
||||||
|
|
||||||
def convert_to_pdfa(input_pdf: Path, input_ps_stub: Path, context: PdfContext):
|
def convert_to_pdfa(input_pdf: Path, input_ps_stub: Path, context: PdfContext) -> Path:
|
||||||
options = context.options
|
options = context.options
|
||||||
input_pdfinfo = context.pdfinfo
|
input_pdfinfo = context.pdfinfo
|
||||||
fix_docinfo_file = context.get_path('fix_docinfo.pdf')
|
fix_docinfo_file = context.get_path('fix_docinfo.pdf')
|
||||||
@@ -748,14 +751,14 @@ def convert_to_pdfa(input_pdf: Path, input_ps_stub: Path, context: PdfContext):
|
|||||||
return output_file
|
return output_file
|
||||||
|
|
||||||
|
|
||||||
def should_linearize(working_file: Path, context: PdfContext):
|
def should_linearize(working_file: Path, context: PdfContext) -> bool:
|
||||||
filesize = os.stat(working_file).st_size
|
filesize = os.stat(working_file).st_size
|
||||||
if filesize > (context.options.fast_web_view * 1_000_000):
|
if filesize > (context.options.fast_web_view * 1_000_000):
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def get_pdf_save_settings(output_type: str):
|
def get_pdf_save_settings(output_type: str) -> dict[str, Any]:
|
||||||
if output_type == 'pdfa-1':
|
if output_type == 'pdfa-1':
|
||||||
# Trigger recompression to ensure object streams are removed, because
|
# Trigger recompression to ensure object streams are removed, because
|
||||||
# Acrobat complains about them in PDF/A-1b validation.
|
# Acrobat complains about them in PDF/A-1b validation.
|
||||||
@@ -773,7 +776,7 @@ def get_pdf_save_settings(output_type: str):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def metadata_fixup(working_file: Path, context: PdfContext):
|
def metadata_fixup(working_file: Path, context: PdfContext) -> Path:
|
||||||
output_file = context.get_path('metafix.pdf')
|
output_file = context.get_path('metafix.pdf')
|
||||||
options = context.options
|
options = context.options
|
||||||
|
|
||||||
@@ -829,7 +832,9 @@ def metadata_fixup(working_file: Path, context: PdfContext):
|
|||||||
return output_file
|
return output_file
|
||||||
|
|
||||||
|
|
||||||
def optimize_pdf(input_file: Path, context: PdfContext, executor: Executor):
|
def optimize_pdf(
|
||||||
|
input_file: Path, context: PdfContext, executor: Executor
|
||||||
|
) -> tuple[Path, Sequence[str]]:
|
||||||
output_file = context.get_path('optimize.pdf')
|
output_file = context.get_path('optimize.pdf')
|
||||||
output_pdf, messages = context.plugin_manager.hook.optimize_pdf(
|
output_pdf, messages = context.plugin_manager.hook.optimize_pdf(
|
||||||
input_pdf=input_file,
|
input_pdf=input_file,
|
||||||
@@ -865,7 +870,7 @@ def enumerate_compress_ranges(iterable):
|
|||||||
yield (skipped_from, index), None
|
yield (skipped_from, index), None
|
||||||
|
|
||||||
|
|
||||||
def merge_sidecars(txt_files: Iterable[Path | None], context: PdfContext):
|
def merge_sidecars(txt_files: Iterable[Path | None], context: PdfContext) -> Path:
|
||||||
output_file = context.get_path('sidecar.txt')
|
output_file = context.get_path('sidecar.txt')
|
||||||
with open(output_file, 'w', encoding="utf-8") as stream:
|
with open(output_file, 'w', encoding="utf-8") as stream:
|
||||||
for (from_, to_), txt_file in enumerate_compress_ranges(txt_files):
|
for (from_, to_), txt_file in enumerate_compress_ranges(txt_files):
|
||||||
@@ -890,7 +895,7 @@ def merge_sidecars(txt_files: Iterable[Path | None], context: PdfContext):
|
|||||||
return output_file
|
return output_file
|
||||||
|
|
||||||
|
|
||||||
def copy_final(input_file, output_file, _context: PdfContext):
|
def copy_final(input_file, output_file, _context: PdfContext) -> None:
|
||||||
log.debug('%s -> %s', input_file, output_file)
|
log.debug('%s -> %s', input_file, output_file)
|
||||||
with open(input_file, 'rb') as input_stream:
|
with open(input_file, 'rb') as input_stream:
|
||||||
if output_file == '-':
|
if output_file == '-':
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
# SPDX-FileCopyrightText: 2022 James R. Barlow
|
# SPDX-FileCopyrightText: 2019-2022 James R. Barlow
|
||||||
|
# SPDX-FileCopyrightText: 2019 Martin Wind
|
||||||
# SPDX-License-Identifier: MPL-2.0
|
# SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
"""Implements the concurrent and page synchronous parts of the pipeline."""
|
"""Implements the concurrent and page synchronous parts of the pipeline."""
|
||||||
@@ -206,7 +207,7 @@ def exec_page_sync(page_context: PageContext) -> PageResult:
|
|||||||
filtered_image = page_context.plugin_manager.hook.filter_page_image(
|
filtered_image = page_context.plugin_manager.hook.filter_page_image(
|
||||||
page=page_context, image_filename=visible_image_out
|
page=page_context, image_filename=visible_image_out
|
||||||
)
|
)
|
||||||
if filtered_image:
|
if filtered_image is not None: # None if no hook is present
|
||||||
visible_image_out = filtered_image
|
visible_image_out = filtered_image
|
||||||
pdf_page_from_image_out = create_pdf_page_from_image(
|
pdf_page_from_image_out = create_pdf_page_from_image(
|
||||||
visible_image_out, page_context, orientation_correction
|
visible_image_out, page_context, orientation_correction
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ def check_options_preprocessing(options: Namespace) -> None:
|
|||||||
package='unpaper',
|
package='unpaper',
|
||||||
version_checker=unpaper.version,
|
version_checker=unpaper.version,
|
||||||
need_version='6.1',
|
need_version='6.1',
|
||||||
required_for=['--clean, --clean-final'],
|
required_for="--clean, --clean-final", # Problem arguments
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
if options.unpaper_args:
|
if options.unpaper_args:
|
||||||
@@ -221,7 +221,7 @@ def check_options_metadata(options: Namespace) -> None:
|
|||||||
def check_options_pillow(options: Namespace) -> None:
|
def check_options_pillow(options: Namespace) -> None:
|
||||||
PIL.Image.MAX_IMAGE_PIXELS = int(options.max_image_mpixels * 1_000_000)
|
PIL.Image.MAX_IMAGE_PIXELS = int(options.max_image_mpixels * 1_000_000)
|
||||||
if PIL.Image.MAX_IMAGE_PIXELS == 0:
|
if PIL.Image.MAX_IMAGE_PIXELS == 0:
|
||||||
PIL.Image.MAX_IMAGE_PIXELS = None
|
PIL.Image.MAX_IMAGE_PIXELS = None # type: ignore
|
||||||
|
|
||||||
|
|
||||||
def _check_plugin_invariant_options(options: Namespace) -> None:
|
def _check_plugin_invariant_options(options: Namespace) -> None:
|
||||||
|
|||||||
@@ -8,10 +8,7 @@ OCRmyPDF uses setuptools_scm to derive version from git tags.
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
try:
|
from importlib.metadata import version as _package_version
|
||||||
from importlib.metadata import version as _package_version
|
|
||||||
except ImportError:
|
|
||||||
from importlib_metadata import version as _package_version # type: ignore
|
|
||||||
|
|
||||||
PROGRAM_NAME = 'ocrmypdf'
|
PROGRAM_NAME = 'ocrmypdf'
|
||||||
|
|
||||||
|
|||||||
+4
-12
@@ -15,6 +15,9 @@ from pathlib import Path
|
|||||||
from typing import AnyStr, BinaryIO, Iterable, Union
|
from typing import AnyStr, BinaryIO, Iterable, Union
|
||||||
from warnings import warn
|
from warnings import warn
|
||||||
|
|
||||||
|
import coloredlogs
|
||||||
|
from humanfriendly.terminal import enable_ansi_support
|
||||||
|
|
||||||
from ocrmypdf._logging import PageNumberFilter, TqdmConsole
|
from ocrmypdf._logging import PageNumberFilter, TqdmConsole
|
||||||
from ocrmypdf._plugin_manager import get_plugin_manager
|
from ocrmypdf._plugin_manager import get_plugin_manager
|
||||||
from ocrmypdf._sync import run_pipeline
|
from ocrmypdf._sync import run_pipeline
|
||||||
@@ -22,15 +25,6 @@ from ocrmypdf._validation import check_options
|
|||||||
from ocrmypdf.cli import ArgumentParser, get_parser
|
from ocrmypdf.cli import ArgumentParser, get_parser
|
||||||
from ocrmypdf.helpers import is_iterable_notstr
|
from ocrmypdf.helpers import is_iterable_notstr
|
||||||
|
|
||||||
try:
|
|
||||||
import coloredlogs
|
|
||||||
except ModuleNotFoundError:
|
|
||||||
coloredlogs = None # pylint: disable=invalid-name
|
|
||||||
|
|
||||||
if coloredlogs:
|
|
||||||
from humanfriendly.terminal import enable_ansi_support
|
|
||||||
|
|
||||||
|
|
||||||
StrPath = Union[Path, AnyStr]
|
StrPath = Union[Path, AnyStr]
|
||||||
PathOrIO = Union[BinaryIO, StrPath]
|
PathOrIO = Union[BinaryIO, StrPath]
|
||||||
|
|
||||||
@@ -121,7 +115,7 @@ def configure_logging(
|
|||||||
|
|
||||||
use_colors = progress_bar_friendly
|
use_colors = progress_bar_friendly
|
||||||
formatter = None
|
formatter = None
|
||||||
if coloredlogs and use_colors:
|
if use_colors:
|
||||||
use_colors = enable_ansi_support()
|
use_colors = enable_ansi_support()
|
||||||
if use_colors:
|
if use_colors:
|
||||||
use_colors = coloredlogs.terminal_supports_colors()
|
use_colors = coloredlogs.terminal_supports_colors()
|
||||||
@@ -284,8 +278,6 @@ def ocr( # pylint: disable=unused-argument
|
|||||||
``"-"``, some final validation steps are not performed (we do not read
|
``"-"``, some final validation steps are not performed (we do not read
|
||||||
back the stream after it is written).
|
back the stream after it is written).
|
||||||
Raises:
|
Raises:
|
||||||
ocrmypdf.PdfMergeFailedError: If the input PDF is malformed, preventing merging
|
|
||||||
with the OCR layer.
|
|
||||||
ocrmypdf.MissingDependencyError: If a required dependency program is missing or
|
ocrmypdf.MissingDependencyError: If a required dependency program is missing or
|
||||||
was not found on PATH.
|
was not found on PATH.
|
||||||
ocrmypdf.UnsupportedImageFormatError: If the input file type was an image that
|
ocrmypdf.UnsupportedImageFormatError: If the input file type was an image that
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
# SPDX-FileCopyrightText: 2022 James R. Barlow
|
# SPDX-FileCopyrightText: 2022 James R. Barlow
|
||||||
# SPDX-License-Identifier: MPL-2.0
|
# SPDX-License-Identifier: MPL-2.0
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
"""OCRmyPDF's multiprocessing/multithreading abstraction layer."""
|
"""OCRmyPDF's multiprocessing/multithreading abstraction layer."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import logging.handlers
|
import logging.handlers
|
||||||
import multiprocessing
|
import multiprocessing
|
||||||
|
|||||||
@@ -21,37 +21,19 @@ def check_options(options):
|
|||||||
program='gs',
|
program='gs',
|
||||||
package='ghostscript',
|
package='ghostscript',
|
||||||
version_checker=ghostscript.version,
|
version_checker=ghostscript.version,
|
||||||
need_version='9.15', # limited by Travis CI / Ubuntu 14.04 backports
|
need_version='9.50', # Ubuntu 20.04's version
|
||||||
)
|
)
|
||||||
gs_version = ghostscript.version()
|
gs_version = ghostscript.version()
|
||||||
if gs_version in ('9.24', '9.51'):
|
if gs_version in ('9.51',):
|
||||||
raise MissingDependencyError(
|
raise MissingDependencyError(
|
||||||
f"Ghostscript {gs_version} contains serious regressions and is not "
|
f"Ghostscript {gs_version} contains serious regressions and is not "
|
||||||
"supported. Please upgrade to a newer version, or downgrade to the "
|
"supported. Please upgrade to a newer version, or downgrade to the "
|
||||||
"previous version."
|
"previous version."
|
||||||
)
|
)
|
||||||
|
|
||||||
# We have these constraints to check for.
|
|
||||||
# 1. Ghostscript < 9.20 mangles multibyte Unicode
|
|
||||||
# 2. hocr doesn't work on non-Latin languages (so don't select it)
|
|
||||||
is_latin = options.languages.issubset(HOCR_OK_LANGS)
|
|
||||||
if gs_version < '9.20' and options.output_type != 'pdf' and not is_latin:
|
|
||||||
# https://bugs.ghostscript.com/show_bug.cgi?id=696874
|
|
||||||
# Ghostscript < 9.20 fails to encode multibyte characters properly
|
|
||||||
log.warning(
|
|
||||||
f"The installed version of Ghostscript ({gs_version}) does not work "
|
|
||||||
"correctly with the OCR languages you specified. Use --output-type pdf or "
|
|
||||||
"upgrade to Ghostscript 9.20 or later to avoid this issue."
|
|
||||||
)
|
|
||||||
|
|
||||||
if options.output_type == 'pdfa':
|
if options.output_type == 'pdfa':
|
||||||
options.output_type = 'pdfa-2'
|
options.output_type = 'pdfa-2'
|
||||||
|
|
||||||
if options.output_type == 'pdfa-3' and ghostscript.version() < '9.19':
|
|
||||||
raise MissingDependencyError(
|
|
||||||
"--output-type pdfa-3 requires Ghostscript 9.19 or later"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@hookimpl
|
@hookimpl
|
||||||
def rasterize_pdf_page(
|
def rasterize_pdf_page(
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ def add_options(parser):
|
|||||||
metavar='MODE',
|
metavar='MODE',
|
||||||
choices=range(0, 4),
|
choices=range(0, 4),
|
||||||
help=(
|
help=(
|
||||||
"Set Tesseract 4.0+ OCR engine mode: "
|
"Set Tesseract 4+ OCR engine mode: "
|
||||||
"0 - original Tesseract only; "
|
"0 - original Tesseract only; "
|
||||||
"1 - neural nets LSTM only; "
|
"1 - neural nets LSTM only; "
|
||||||
"2 - Tesseract + LSTM; "
|
"2 - Tesseract + LSTM; "
|
||||||
@@ -93,7 +93,7 @@ def check_options(options):
|
|||||||
program='tesseract',
|
program='tesseract',
|
||||||
package={'linux': 'tesseract-ocr'},
|
package={'linux': 'tesseract-ocr'},
|
||||||
version_checker=tesseract.version,
|
version_checker=tesseract.version,
|
||||||
need_version='4.0.0-beta.1', # using backport for Travis CI
|
need_version='4.1.1', # Ubuntu 20.04 version
|
||||||
version_parser=tesseract.TesseractVersion,
|
version_parser=tesseract.TesseractVersion,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -101,11 +101,6 @@ def check_options(options):
|
|||||||
if options.pdf_renderer == 'auto':
|
if options.pdf_renderer == 'auto':
|
||||||
options.pdf_renderer = 'sandwich'
|
options.pdf_renderer = 'sandwich'
|
||||||
|
|
||||||
if not tesseract.has_user_words() and (options.user_words or options.user_patterns):
|
|
||||||
log.warning(
|
|
||||||
"Tesseract 4.0 (which you have installed) ignores --user-words and "
|
|
||||||
"--user-patterns, so these arguments have no effect."
|
|
||||||
)
|
|
||||||
if not tesseract.has_thresholding() and options.tesseract_thresholding != 0:
|
if not tesseract.has_thresholding() and options.tesseract_thresholding != 0:
|
||||||
log.warning(
|
log.warning(
|
||||||
"The installed version of Tesseract does not support changes to its "
|
"The installed version of Tesseract does not support changes to its "
|
||||||
|
|||||||
@@ -47,26 +47,6 @@ class BadArgsError(ExitCodeException):
|
|||||||
exit_code = ExitCode.bad_args
|
exit_code = ExitCode.bad_args
|
||||||
|
|
||||||
|
|
||||||
class PdfMergeFailedError(ExitCodeException): # deprecated
|
|
||||||
"""An intermediate PDF can't be merged.
|
|
||||||
|
|
||||||
No longer in use.
|
|
||||||
"""
|
|
||||||
|
|
||||||
exit_code = ExitCode.input_file
|
|
||||||
message = dedent(
|
|
||||||
'''\
|
|
||||||
Failed to merge PDF image layer with OCR layer
|
|
||||||
|
|
||||||
Usually this happens because the input PDF file is malformed and
|
|
||||||
ocrmypdf cannot correct the problem on its own.
|
|
||||||
|
|
||||||
Try using
|
|
||||||
ocrmypdf --pdf-renderer sandwich [..other args..]
|
|
||||||
'''
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class MissingDependencyError(ExitCodeException):
|
class MissingDependencyError(ExitCodeException):
|
||||||
"""A third-party dependency is missing."""
|
"""A third-party dependency is missing."""
|
||||||
|
|
||||||
|
|||||||
+25
-27
@@ -10,14 +10,12 @@ import multiprocessing
|
|||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import warnings
|
import warnings
|
||||||
from collections import namedtuple
|
|
||||||
from collections.abc import Iterable
|
from collections.abc import Iterable
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
from functools import wraps
|
|
||||||
from io import StringIO
|
from io import StringIO
|
||||||
from math import isclose, isfinite
|
from math import isclose, isfinite
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Sequence
|
from typing import Any, Generic, Sequence, SupportsFloat, SupportsRound, TypeVar
|
||||||
|
|
||||||
import img2pdf
|
import img2pdf
|
||||||
import pikepdf
|
import pikepdf
|
||||||
@@ -35,23 +33,33 @@ else:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class Resolution(namedtuple('Resolution', ('x', 'y'))):
|
T = TypeVar('T', bound=SupportsRound[Any])
|
||||||
|
|
||||||
|
|
||||||
|
class Resolution(Generic[T]):
|
||||||
"""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
|
Resolution objects are considered "equal" for == purposes if they are
|
||||||
equal to a reasonable tolerance.
|
equal to a reasonable tolerance.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__slots__ = ()
|
x: T
|
||||||
|
y: T
|
||||||
|
|
||||||
|
__slots__ = ('x', 'y')
|
||||||
|
|
||||||
|
def __init__(self, x: T, y: T):
|
||||||
|
self.x = x
|
||||||
|
self.y = y
|
||||||
|
|
||||||
# rel_tol after converting from dpi to pixels per meter and saving
|
# rel_tol after converting from dpi to pixels per meter and saving
|
||||||
# as integer with rounding, as many file formats
|
# as integer with rounding, as many file formats
|
||||||
CONVERSION_ERROR = 0.002
|
CONVERSION_ERROR = 0.002
|
||||||
|
|
||||||
def round(self, ndigits: int):
|
def round(self, ndigits: int) -> Resolution:
|
||||||
return Resolution(round(self.x, ndigits), round(self.y, ndigits))
|
return Resolution(round(self.x, ndigits), round(self.y, ndigits))
|
||||||
|
|
||||||
def to_int(self):
|
def to_int(self) -> Resolution[int]:
|
||||||
return Resolution(int(round(self.x)), int(round(self.y)))
|
return Resolution(int(round(self.x)), int(round(self.y)))
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -64,9 +72,13 @@ class Resolution(namedtuple('Resolution', ('x', 'y'))):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def is_finite(self) -> bool:
|
def is_finite(self) -> bool:
|
||||||
return isfinite(self.x) and isfinite(self.y)
|
if isinstance(self.x, SupportsFloat) and isinstance(self.y, SupportsFloat):
|
||||||
|
return isfinite(self.x) and isfinite(self.y)
|
||||||
|
return True
|
||||||
|
|
||||||
def take_max(self, vals, yvals=None):
|
def take_max(
|
||||||
|
self, vals: Iterable[Any], yvals: Iterable[Any] | None = None
|
||||||
|
) -> Resolution:
|
||||||
if yvals is not None:
|
if yvals is not None:
|
||||||
return Resolution(max(self.x, *vals), max(self.y, *yvals))
|
return Resolution(max(self.x, *vals), max(self.y, *yvals))
|
||||||
max_x, max_y = self.x, self.y
|
max_x, max_y = self.x, self.y
|
||||||
@@ -75,9 +87,12 @@ class Resolution(namedtuple('Resolution', ('x', 'y'))):
|
|||||||
max_y = max(y, max_y)
|
max_y = max(y, max_y)
|
||||||
return Resolution(max_x, max_y)
|
return Resolution(max_x, max_y)
|
||||||
|
|
||||||
def flip_axis(self):
|
def flip_axis(self) -> Resolution[T]:
|
||||||
return Resolution(self.y, self.x)
|
return Resolution(self.y, self.x)
|
||||||
|
|
||||||
|
def __getitem__(self, idx: int | slice) -> T:
|
||||||
|
return (self.x, self.y)[idx]
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.x:f}x{self.y:f}"
|
return f"{self.x:f}x{self.y:f}"
|
||||||
|
|
||||||
@@ -275,20 +290,3 @@ def pikepdf_enable_mmap():
|
|||||||
# Fix is not in pybind11 2.6.0
|
# Fix is not in pybind11 2.6.0
|
||||||
# log.debug("pikepdf mmap disabled")
|
# log.debug("pikepdf mmap disabled")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
def deprecated(func):
|
|
||||||
"""Warn that function is deprecated."""
|
|
||||||
|
|
||||||
@wraps(func)
|
|
||||||
def new_func(*args, **kwargs):
|
|
||||||
warnings.simplefilter('always', DeprecationWarning) # turn off filter
|
|
||||||
warnings.warn(
|
|
||||||
f"Call to deprecated function {func.__name__}.",
|
|
||||||
category=DeprecationWarning,
|
|
||||||
stacklevel=2,
|
|
||||||
)
|
|
||||||
warnings.simplefilter('default', DeprecationWarning) # reset filter
|
|
||||||
return func(*args, **kwargs)
|
|
||||||
|
|
||||||
return new_func
|
|
||||||
|
|||||||
@@ -14,10 +14,11 @@ import re
|
|||||||
import warnings
|
import warnings
|
||||||
from math import atan, cos, sin
|
from math import atan, cos, sin
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, NamedTuple, Optional, Tuple, Union
|
from typing import Any, NamedTuple
|
||||||
from xml.etree import ElementTree
|
from xml.etree import ElementTree
|
||||||
|
|
||||||
with warnings.catch_warnings():
|
with warnings.catch_warnings():
|
||||||
|
# reportlab uses deprecated load_module
|
||||||
warnings.filterwarnings(
|
warnings.filterwarnings(
|
||||||
'ignore', category=DeprecationWarning, message=r".*load_module.*"
|
'ignore', category=DeprecationWarning, message=r".*load_module.*"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -6,4 +6,4 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from ocrmypdf.pdfinfo.info import Colorspace, Encoding, PdfInfo
|
from ocrmypdf.pdfinfo.info import Colorspace, Encoding, PageInfo, PdfInfo
|
||||||
|
|||||||
@@ -17,16 +17,7 @@ from functools import partial
|
|||||||
from math import hypot, inf, isclose
|
from math import hypot, inf, isclose
|
||||||
from os import PathLike
|
from os import PathLike
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import (
|
from typing import Container, Iterable, Iterator, Mapping, NamedTuple, Sequence, Tuple
|
||||||
Container,
|
|
||||||
Iterable,
|
|
||||||
Iterator,
|
|
||||||
Mapping,
|
|
||||||
NamedTuple,
|
|
||||||
Optional,
|
|
||||||
Sequence,
|
|
||||||
Tuple,
|
|
||||||
)
|
|
||||||
from warnings import warn
|
from warnings import warn
|
||||||
|
|
||||||
from pikepdf import (
|
from pikepdf import (
|
||||||
@@ -486,14 +477,12 @@ def _image_xobjects(container) -> Iterator[tuple[Object, str]]:
|
|||||||
resources = container['/Resources']
|
resources = container['/Resources']
|
||||||
if '/XObject' not in resources:
|
if '/XObject' not in resources:
|
||||||
return
|
return
|
||||||
xobjs = resources['/XObject'].as_dict()
|
for key, candidate in resources['/XObject'].items():
|
||||||
for xobj in xobjs:
|
if candidate is None or '/Subtype' not in candidate:
|
||||||
candidate: Object = xobjs[xobj]
|
|
||||||
if '/Subtype' not in candidate:
|
|
||||||
continue
|
continue
|
||||||
if candidate['/Subtype'] == '/Image':
|
if candidate['/Subtype'] == '/Image':
|
||||||
pdfimage = candidate
|
pdfimage = candidate
|
||||||
yield (pdfimage, xobj)
|
yield (pdfimage, key)
|
||||||
|
|
||||||
|
|
||||||
def _find_regular_images(
|
def _find_regular_images(
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
# SPDX-FileCopyrightText: 2022 James R. Barlow
|
# SPDX-FileCopyrightText: 2022 James R. Barlow
|
||||||
# SPDX-License-Identifier: MPL-2.0
|
# SPDX-License-Identifier: MPL-2.0
|
||||||
|
"""Detailed text position and layout analysis, building on pdfminer.six."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import re
|
import re
|
||||||
@@ -23,14 +25,14 @@ from ocrmypdf.exceptions import EncryptedPdfError, InputFileError
|
|||||||
STRIP_NAME = re.compile(r'[0-9]+')
|
STRIP_NAME = re.compile(r'[0-9]+')
|
||||||
|
|
||||||
|
|
||||||
original_PDFSimpleFont_init = PDFSimpleFont.__init__
|
original_pdfsimplefont_init = PDFSimpleFont.__init__
|
||||||
|
|
||||||
|
|
||||||
def PDFSimpleFont__init__(self, descriptor, widths, spec):
|
def pdfsimplefont__init__(self, descriptor, widths, spec):
|
||||||
# Font encoding is specified either by a name of
|
# Font encoding is specified either by a name of
|
||||||
# built-in encoding or a dictionary that describes
|
# built-in encoding or a dictionary that describes
|
||||||
# the differences.
|
# the differences.
|
||||||
original_PDFSimpleFont_init(self, descriptor, widths, spec)
|
original_pdfsimplefont_init(self, descriptor, widths, spec)
|
||||||
# pdfminer is incorrect. If there is no ToUnicode and no Encoding, do not
|
# pdfminer is incorrect. If there is no ToUnicode and no Encoding, do not
|
||||||
# assume Unicode conversion is possible. RM 9.10.2
|
# assume Unicode conversion is possible. RM 9.10.2
|
||||||
if not self.unicode_map and 'Encoding' not in spec:
|
if not self.unicode_map and 'Encoding' not in spec:
|
||||||
@@ -38,25 +40,25 @@ def PDFSimpleFont__init__(self, descriptor, widths, spec):
|
|||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
PDFSimpleFont.__init__ = PDFSimpleFont__init__
|
PDFSimpleFont.__init__ = pdfsimplefont__init__
|
||||||
|
|
||||||
#
|
#
|
||||||
# pdfminer patches when creator is PScript5.dll
|
# pdfminer patches when creator is PScript5.dll
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|
||||||
def PDFType3Font__PScript5_get_height(self):
|
def pdftype3font__pscript5_get_height(self):
|
||||||
h = self.bbox[3] - self.bbox[1]
|
h = self.bbox[3] - self.bbox[1]
|
||||||
if h == 0:
|
if h == 0:
|
||||||
h = self.ascent - self.descent
|
h = self.ascent - self.descent
|
||||||
return h * copysign(1.0, self.vscale)
|
return h * copysign(1.0, self.vscale)
|
||||||
|
|
||||||
|
|
||||||
def PDFType3Font__PScript5_get_descent(self):
|
def pdftype3font__pscript5_get_descent(self):
|
||||||
return self.descent * copysign(1.0, self.vscale)
|
return self.descent * copysign(1.0, self.vscale)
|
||||||
|
|
||||||
|
|
||||||
def PDFType3Font__PScript5_get_ascent(self):
|
def pdftype3font__pscript5_get_ascent(self):
|
||||||
return self.ascent * copysign(1.0, self.vscale)
|
return self.ascent * copysign(1.0, self.vscale)
|
||||||
|
|
||||||
|
|
||||||
@@ -132,14 +134,14 @@ class LTStateAwareChar(LTChar):
|
|||||||
return self._text
|
return self._text
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return '<{} {} matrix={} rendermode={!r} font={!r} adv={} text={!r}>'.format(
|
return (
|
||||||
self.__class__.__name__,
|
f"<{self.__class__.__name__} "
|
||||||
bbox2str(self.bbox),
|
f"{bbox2str(self.bbox)} "
|
||||||
matrix2str(self.matrix),
|
f"matrix={matrix2str(self.matrix)} "
|
||||||
self.rendermode,
|
f"rendermode={self.rendermode!r} "
|
||||||
self.fontname,
|
f"font={self.fontname!r} "
|
||||||
self.adv,
|
f"adv={self.adv} "
|
||||||
self.get_text(),
|
f"text={self.get_text()!r}>"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -207,11 +209,7 @@ class TextPositionTracker(PDFLayoutAnalyzer):
|
|||||||
|
|
||||||
def get_page_analysis(infile, pageno, pscript5_mode):
|
def get_page_analysis(infile, pageno, pscript5_mode):
|
||||||
rman = pdfminer.pdfinterp.PDFResourceManager(caching=True)
|
rman = pdfminer.pdfinterp.PDFResourceManager(caching=True)
|
||||||
if pdfminer.__version__ < '20200402':
|
disable_boxes_flow = None
|
||||||
# Workaround for https://github.com/pdfminer/pdfminer.six/issues/395
|
|
||||||
disable_boxes_flow = 2
|
|
||||||
else:
|
|
||||||
disable_boxes_flow = None
|
|
||||||
dev = TextPositionTracker(
|
dev = TextPositionTracker(
|
||||||
rman,
|
rman,
|
||||||
laparams=LAParams(
|
laparams=LAParams(
|
||||||
@@ -225,9 +223,9 @@ def get_page_analysis(infile, pageno, pscript5_mode):
|
|||||||
patcher = patch.multiple(
|
patcher = patch.multiple(
|
||||||
'pdfminer.pdffont.PDFType3Font',
|
'pdfminer.pdffont.PDFType3Font',
|
||||||
spec=True,
|
spec=True,
|
||||||
get_ascent=PDFType3Font__PScript5_get_ascent,
|
get_ascent=pdftype3font__pscript5_get_ascent,
|
||||||
get_descent=PDFType3Font__PScript5_get_descent,
|
get_descent=pdftype3font__pscript5_get_descent,
|
||||||
get_height=PDFType3Font__PScript5_get_height,
|
get_height=pdftype3font__pscript5_get_height,
|
||||||
)
|
)
|
||||||
patcher.start()
|
patcher.start()
|
||||||
|
|
||||||
|
|||||||
@@ -278,10 +278,6 @@ def filter_page_image(page: PageContext, image_filename: Path) -> Path:
|
|||||||
will occur. The return value should be a path to a file in the same folder
|
will occur. The return value should be a path to a file in the same folder
|
||||||
as ``image_filename``.
|
as ``image_filename``.
|
||||||
|
|
||||||
Implementation detail: If the value returned is falsy, OCRmyPDF will ignore
|
|
||||||
the return value and assume the input file was unmodified. This is deprecated.
|
|
||||||
To leave the image unmodified, ``image_filename`` should be returned.
|
|
||||||
|
|
||||||
Note:
|
Note:
|
||||||
This hook will be called from child processes. Modifying global state
|
This hook will be called from child processes. Modifying global state
|
||||||
will not affect the main process or other child processes.
|
will not affect the main process or other child processes.
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import os
|
|||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
from functools import lru_cache
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from subprocess import PIPE, STDOUT, CalledProcessError, CompletedProcess, Popen
|
from subprocess import PIPE, STDOUT, CalledProcessError, CompletedProcess, Popen
|
||||||
from subprocess import run as subprocess_run
|
from subprocess import run as subprocess_run
|
||||||
@@ -135,7 +134,6 @@ def _fix_process_args(
|
|||||||
return args, env, process_log, text
|
return args, env, process_log, text
|
||||||
|
|
||||||
|
|
||||||
@lru_cache(maxsize=None)
|
|
||||||
def get_version(
|
def get_version(
|
||||||
program: str,
|
program: str,
|
||||||
*,
|
*,
|
||||||
@@ -316,7 +314,7 @@ def check_external_program(
|
|||||||
program: The name of the program to test.
|
program: The name of the program to test.
|
||||||
package: The name of a software package that typically supplies this program.
|
package: The name of a software package that typically supplies this program.
|
||||||
Usually the same as program.
|
Usually the same as program.
|
||||||
version_check: A callable without arguments that retrieves the installed
|
version_checker: A callable without arguments that retrieves the installed
|
||||||
version of program.
|
version of program.
|
||||||
need_version: The minimum required version.
|
need_version: The minimum required version.
|
||||||
required_for: The name of an argument of feature that requires this program.
|
required_for: The name of an argument of feature that requires this program.
|
||||||
@@ -327,10 +325,7 @@ def check_external_program(
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if callable(version_checker):
|
found_version = version_checker()
|
||||||
found_version = version_checker()
|
|
||||||
else: # deprecated
|
|
||||||
found_version = version_checker
|
|
||||||
except (CalledProcessError, FileNotFoundError) as e:
|
except (CalledProcessError, FileNotFoundError) as e:
|
||||||
_error_missing_program(program, package, required_for, recommended)
|
_error_missing_program(program, package, required_for, recommended)
|
||||||
if not recommended:
|
if not recommended:
|
||||||
|
|||||||
@@ -6,12 +6,15 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
import sys
|
import sys
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Callable, Iterable, Iterator, TypeVar
|
from typing import Any, Callable, Iterable, Iterator, TypeVar
|
||||||
|
|
||||||
|
from packaging.version import InvalidVersion, Version
|
||||||
|
|
||||||
if sys.version_info >= (3, 10):
|
if sys.version_info >= (3, 10):
|
||||||
from typing import TypeAlias
|
from typing import TypeAlias
|
||||||
else:
|
else:
|
||||||
@@ -35,6 +38,7 @@ else:
|
|||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
T = TypeVar('T')
|
T = TypeVar('T')
|
||||||
|
Tkey = TypeVar('Tkey')
|
||||||
|
|
||||||
|
|
||||||
def ghostscript_version_key(s: str) -> tuple[int, int, int]:
|
def ghostscript_version_key(s: str) -> tuple[int, int, int]:
|
||||||
@@ -99,6 +103,30 @@ def registry_path_tesseract(env=None) -> Iterator[Path]:
|
|||||||
log.warning(e)
|
log.warning(e)
|
||||||
|
|
||||||
|
|
||||||
|
def _gs_version_in_path_key(path: Path) -> tuple[str, Version | None]:
|
||||||
|
"""Key function for comparing Ghostscript and Tesseract paths.
|
||||||
|
|
||||||
|
Ghostscript installs on Windows:
|
||||||
|
%PROGRAMFILES%/gs/gs9.56.1/bin -> ('gs', Version('9.56.1'))
|
||||||
|
%PROGRAMFILES%/gs/9.24/bin -> ('gs', Version('9.24'))
|
||||||
|
|
||||||
|
Tesseract looks like:
|
||||||
|
%PROGRAMFILES%/Tesseract-OCR -> ('Tesseract-OCR', None)
|
||||||
|
|
||||||
|
Thus ensuring the resulting tuple will order the alternatives correctly,
|
||||||
|
e.g. gs10.0 > gs9.99.
|
||||||
|
"""
|
||||||
|
match = re.search(r'gs[/\\]?([0-9.]+)[/\\]bin', str(path))
|
||||||
|
if match:
|
||||||
|
try:
|
||||||
|
version_str = match.group(1)
|
||||||
|
version = Version(version_str)
|
||||||
|
return 'gs', version
|
||||||
|
except InvalidVersion:
|
||||||
|
pass
|
||||||
|
return path.name, None
|
||||||
|
|
||||||
|
|
||||||
def program_files_paths(env=None) -> Iterator[Path]:
|
def program_files_paths(env=None) -> Iterator[Path]:
|
||||||
if not env:
|
if not env:
|
||||||
env = os.environ
|
env = os.environ
|
||||||
@@ -116,7 +144,7 @@ def program_files_paths(env=None) -> Iterator[Path]:
|
|||||||
return iter(
|
return iter(
|
||||||
sorted(
|
sorted(
|
||||||
(p for p in path_walker()),
|
(p for p in path_walker()),
|
||||||
key=lambda p: (p.name, p.parent.name),
|
key=_gs_version_in_path_key,
|
||||||
reverse=True,
|
reverse=True,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -143,11 +171,6 @@ SHIMS = [
|
|||||||
def fix_windows_args(program: str, args, env):
|
def fix_windows_args(program: str, args, env):
|
||||||
"""Adjust our desired program and command line arguments for use on Windows"""
|
"""Adjust our desired program and command line arguments for use on Windows"""
|
||||||
|
|
||||||
if sys.version_info < (3, 8):
|
|
||||||
# bpo-33617 - Windows needs manual Path -> str conversion
|
|
||||||
args = [os.fspath(arg) for arg in args]
|
|
||||||
program = os.fspath(program)
|
|
||||||
|
|
||||||
# If we are running a .py on Windows, ensure we call it with this Python
|
# If we are running a .py on Windows, ensure we call it with this Python
|
||||||
# (to support test suite shims)
|
# (to support test suite shims)
|
||||||
if program.lower().endswith('.py'):
|
if program.lower().endswith('.py'):
|
||||||
@@ -164,11 +187,11 @@ def fix_windows_args(program: str, args, env):
|
|||||||
return args
|
return args
|
||||||
|
|
||||||
|
|
||||||
def unique_everseen(iterable: Iterable[T], key: Callable[[T], T]) -> Iterator[T]:
|
def unique_everseen(iterable: Iterable[T], key: Callable[[T], Tkey]) -> Iterator[T]:
|
||||||
"List unique elements, preserving order."
|
"List unique elements, preserving order."
|
||||||
# unique_everseen('AAAABBBCCDAABBB') --> A B C D
|
# unique_everseen('AAAABBBCCDAABBB') --> A B C D
|
||||||
# unique_everseen('ABBCcAD', str.lower) --> A B C D
|
# unique_everseen('ABBCcAD', str.lower) --> A B C D
|
||||||
seen: set[T] = set()
|
seen: set[Tkey] = set()
|
||||||
seen_add = seen.add
|
seen_add = seen.add
|
||||||
for element in iterable:
|
for element in iterable:
|
||||||
k = key(element)
|
k = key(element)
|
||||||
@@ -177,11 +200,15 @@ def unique_everseen(iterable: Iterable[T], key: Callable[[T], T]) -> Iterator[T]
|
|||||||
yield element
|
yield element
|
||||||
|
|
||||||
|
|
||||||
|
def _casefold_path(path: Path) -> str:
|
||||||
|
return str.casefold(str(path))
|
||||||
|
|
||||||
|
|
||||||
def shim_env_path(env=None):
|
def shim_env_path(env=None):
|
||||||
if env is None:
|
if env is None:
|
||||||
env = os.environ
|
env = os.environ
|
||||||
|
|
||||||
shim_paths = chain.from_iterable(shim(env) for shim in SHIMS)
|
shim_paths = chain.from_iterable(shim(env) for shim in SHIMS)
|
||||||
return os.pathsep.join(
|
return os.pathsep.join(
|
||||||
str(p) for p in unique_everseen(shim_paths, key=lambda p: str.casefold(str(p)))
|
str(p) for p in unique_everseen(shim_paths, key=_casefold_path)
|
||||||
)
|
)
|
||||||
|
|||||||
+6
-1
@@ -44,7 +44,7 @@ TESTS_ROOT = Path(__file__).parent.resolve()
|
|||||||
PROJECT_ROOT = TESTS_ROOT
|
PROJECT_ROOT = TESTS_ROOT
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture(scope="session")
|
||||||
def resources() -> Path:
|
def resources() -> Path:
|
||||||
return Path(TESTS_ROOT) / 'resources'
|
return Path(TESTS_ROOT) / 'resources'
|
||||||
|
|
||||||
@@ -78,6 +78,11 @@ def no_outpdf(tmp_path) -> Path:
|
|||||||
return tmp_path / 'no_output.pdf'
|
return tmp_path / 'no_output.pdf'
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session")
|
||||||
|
def multipage(resources):
|
||||||
|
return resources / 'multipage.pdf'
|
||||||
|
|
||||||
|
|
||||||
def check_ocrmypdf(input_file: Path, output_file: Path, *args) -> Path:
|
def check_ocrmypdf(input_file: Path, output_file: Path, *args) -> Path:
|
||||||
"""Run ocrmypdf and confirm that a valid plausible PDF was created."""
|
"""Run ocrmypdf and confirm that a valid plausible PDF was created."""
|
||||||
api_args = [str(input_file), str(output_file)] + [
|
api_args = [str(input_file), str(output_file)] + [
|
||||||
|
|||||||
@@ -9,13 +9,13 @@ from ocrmypdf import hookimpl
|
|||||||
from ocrmypdf.builtin_plugins import ghostscript
|
from ocrmypdf.builtin_plugins import ghostscript
|
||||||
from ocrmypdf.subprocess import run_polling_stderr
|
from ocrmypdf.subprocess import run_polling_stderr
|
||||||
|
|
||||||
elision_warning = """GPL Ghostscript 9.20: Setting Overprint Mode to 1
|
ELISION_WARNING = """GPL Ghostscript 9.50: Setting Overprint Mode to 1
|
||||||
not permitted in PDF/A-2, overprint mode not set"""
|
not permitted in PDF/A-2, overprint mode not set"""
|
||||||
|
|
||||||
|
|
||||||
def run_append_stderr(*args, **kwargs):
|
def run_append_stderr(*args, **kwargs):
|
||||||
proc = run_polling_stderr(*args, **kwargs)
|
proc = run_polling_stderr(*args, **kwargs)
|
||||||
proc.stderr += '\n' + elision_warning + '\n'
|
proc.stderr += '\n' + ELISION_WARNING + '\n'
|
||||||
return proc
|
return proc
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ were produced.
|
|||||||
|
|
||||||
Certain operations are not cached and routed to Tesseract OCR directly.
|
Certain operations are not cached and routed to Tesseract OCR directly.
|
||||||
|
|
||||||
Assumes Tesseract 4.0.0-alpha or higher.
|
Assumes Tesseract 4+.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -140,18 +140,19 @@ def cached_run(options, run_args, **run_kwargs):
|
|||||||
tessfile = args.outputbase + '.' + configfile
|
tessfile = args.outputbase + '.' + configfile
|
||||||
shutil.copy(tessfile, str(cache_folder / configfile) + '.bin')
|
shutil.copy(tessfile, str(cache_folder / configfile) + '.bin')
|
||||||
|
|
||||||
manifest = {}
|
|
||||||
manifest['tesseract_version'] = TesseractOcrEngine.version().replace('\n', ' ')
|
|
||||||
manifest['system'] = platform.system()
|
|
||||||
manifest['python'] = platform.python_version()
|
|
||||||
manifest['argv_slug'] = cache_folder.name
|
|
||||||
manifest['sourcefile'] = str(Path(source_file).relative_to(TESTS_ROOT))
|
|
||||||
|
|
||||||
def clean_sys_argv():
|
def clean_sys_argv():
|
||||||
for arg in run_args[1:]:
|
for arg in run_args[1:]:
|
||||||
yield re.sub(r'.*/ocrmypdf[.]io[.][^/]+[/](.*)', r'$TMPDIR/\1', arg)
|
yield re.sub(r'.*/ocrmypdf[.]io[.][^/]+[/](.*)', r'$TMPDIR/\1', arg)
|
||||||
|
|
||||||
manifest['args'] = list(clean_sys_argv())
|
manifest = {
|
||||||
|
'tesseract_version': TesseractOcrEngine.version().replace('\n', ' '),
|
||||||
|
'system': platform.system(),
|
||||||
|
'python': platform.python_version(),
|
||||||
|
'argv_slug': cache_folder.name,
|
||||||
|
'sourcefile': str(Path(source_file).relative_to(TESTS_ROOT)),
|
||||||
|
'args': list(clean_sys_argv()),
|
||||||
|
}
|
||||||
|
|
||||||
with (Path(CACHE_ROOT) / 'manifest.jsonl').open('a') as f:
|
with (Path(CACHE_ROOT) / 'manifest.jsonl').open('a') as f:
|
||||||
json.dump(manifest, f)
|
json.dump(manifest, f)
|
||||||
f.write('\n')
|
f.write('\n')
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ HOCR_TEMPLATE = '''<?xml version="1.0" encoding="UTF-8"?>
|
|||||||
<head>
|
<head>
|
||||||
<title></title>
|
<title></title>
|
||||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||||
<meta name='ocr-system' content='tesseract 4.0.0' />
|
<meta name='ocr-system' content='tesseract 4.1.1' />
|
||||||
<meta name='ocr-capabilities' content='ocr_page ocr_carea ocr_par ocr_line ocrx_word'/>
|
<meta name='ocr-capabilities' content='ocr_page ocr_carea ocr_par ocr_line ocrx_word'/>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -46,7 +46,7 @@ HOCR_TEMPLATE = '''<?xml version="1.0" encoding="UTF-8"?>
|
|||||||
class FixedRotateNoopOcrEngine(OcrEngine):
|
class FixedRotateNoopOcrEngine(OcrEngine):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def version():
|
def version():
|
||||||
return '4.0.0'
|
return '4.1.1'
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def creator_tag(options):
|
def creator_tag(options):
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ HOCR_TEMPLATE = '''<?xml version="1.0" encoding="UTF-8"?>
|
|||||||
<head>
|
<head>
|
||||||
<title></title>
|
<title></title>
|
||||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||||
<meta name='ocr-system' content='tesseract 4.0.0' />
|
<meta name='ocr-system' content='tesseract 4.1.1' />
|
||||||
<meta name='ocr-capabilities' content='ocr_page ocr_carea ocr_par ocr_line ocrx_word'/>
|
<meta name='ocr-capabilities' content='ocr_page ocr_carea ocr_par ocr_line ocrx_word'/>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -44,7 +44,7 @@ HOCR_TEMPLATE = '''<?xml version="1.0" encoding="UTF-8"?>
|
|||||||
class NoopOcrEngine(OcrEngine):
|
class NoopOcrEngine(OcrEngine):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def version():
|
def version():
|
||||||
return '4.0.0'
|
return '4.1.1'
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def creator_tag(options):
|
def creator_tag(options):
|
||||||
|
|||||||
+41
-106
@@ -1,168 +1,105 @@
|
|||||||
|
.. SPDX-FileCopyrightText: 2022 James R. Barlow
|
||||||
|
.. SPDX-License-Identifier: CC-BY-SA-4.0
|
||||||
|
|
||||||
These test files are used in OCRmyPDF's test suite. They do not necessarily produce OCR results
|
These test files are used in OCRmyPDF's test suite. They do not necessarily produce OCR results
|
||||||
at all and are not meant as examples of OCR output. Some are even invalid PDFs that might
|
at all and are not necessarily meant as examples of OCR output. Some are even invalid PDFs that might
|
||||||
crash certain PDF viewers.
|
crash certain PDF viewers.
|
||||||
|
|
||||||
|
Some of these images were obtained from the public domain. Others are copyrighted and may have
|
||||||
Files derived from free sources
|
licenses associated. Refer to ``.reuse/dep5`` file in OCRmyPDF's Git repository for information on
|
||||||
===============================
|
the copyright holder(s) and license(s) applicable to these resources.
|
||||||
|
|
||||||
These test resources come from free sources, under either public domain or Creative Commons licenses.
|
|
||||||
In some cases they were converted from one image format to another without other changes.
|
|
||||||
|
|
||||||
.. list-table::
|
.. list-table::
|
||||||
:widths: 20 50 30
|
:widths: 15 35 50
|
||||||
:header-rows: 1
|
:header-rows: 1
|
||||||
|
|
||||||
* - File
|
* - File
|
||||||
- Source
|
- Source
|
||||||
- License
|
- Purpose
|
||||||
* - c02-22.pdf
|
* - c02-22.pdf
|
||||||
- `Project Gutenberg`_, Adventures of Huckleberry Finn, page 22
|
- `Project Gutenberg`_, Adventures of Huckleberry Finn, page 22
|
||||||
- Public Domain
|
- difficult OCR image (obscure fonts and illustrations)
|
||||||
* - congress.jpg
|
* - congress.jpg
|
||||||
- `US Congressional Records`_
|
- `US Congressional Records`_
|
||||||
- Public Domain
|
- difficult OCR image (color background)
|
||||||
* - graph.pdf
|
* - graph.pdf
|
||||||
- `Wikimedia: Pandas text analysis.png`_
|
- `Wikimedia:Simple_line_graph_of_ACE_2012_results_by_candidate_sj01.png`_
|
||||||
- Public Domain
|
- image with slanted text
|
||||||
* - lichtenstein.pdf
|
* - lichtenstein.pdf
|
||||||
- `Wikimedia: JPEG2000 Lichtenstein`_
|
- `Wikimedia: JPEG2000 Lichtenstein`_
|
||||||
- Creative Commons BY-SA 3.0
|
- JPEG2000 image
|
||||||
* - linn.png, linn.pdf, linn.txt
|
* - linn.png, linn.pdf, linn.txt
|
||||||
- `Wikimedia: LinnSequencer`_
|
- `Wikimedia: LinnSequencer`_
|
||||||
- Creative Commons BY-SA 3.0
|
- image with two columns
|
||||||
* - typewriter.png, 2400dpi.pdf
|
* - typewriter.png, 2400dpi.pdf
|
||||||
- `Wikimedia: Triumph typewrtier text Linzensoep`_
|
- `Wikimedia: Triumph typewrtier text Linzensoep`_
|
||||||
- Creative Commons BY-SA 2.5
|
- simple text
|
||||||
* - baiona.png
|
* - baiona.png
|
||||||
- `Wikimedia: Baionako udalerri mugakideak`_
|
- `Wikimedia: Baionako udalerri mugakideak`_
|
||||||
- Creative Commons BY-SA 4.0
|
- multilingual text and images
|
||||||
* - enron1.pdf
|
|
||||||
- EnronData.org
|
|
||||||
- Creative Commons BY 3.0
|
|
||||||
|
|
||||||
|
|
||||||
Files generated for this project
|
|
||||||
================================
|
|
||||||
|
|
||||||
The following test resources were crafted specifically for this project, and are
|
|
||||||
licensed under the specified license.
|
|
||||||
|
|
||||||
.. list-table::
|
|
||||||
:widths: 20 40 15 15 10
|
|
||||||
:header-rows: 1
|
|
||||||
|
|
||||||
* - File
|
|
||||||
- Purpose
|
|
||||||
- Contributor
|
|
||||||
- Copyright Holder
|
|
||||||
- License
|
|
||||||
* - aspect.pdf
|
* - aspect.pdf
|
||||||
|
- synthetic
|
||||||
- test image with 200 x 100 DPI resolution
|
- test image with 200 x 100 DPI resolution
|
||||||
- @jbarlow83
|
|
||||||
- @jbarlow83
|
|
||||||
- CC-BY-SA 4.0
|
|
||||||
* - blank.pdf
|
* - blank.pdf
|
||||||
|
- synthetic
|
||||||
- blank PDF generated by Adobe Illustrator CC 17, containing a lot of application-specific metadata/bloat
|
- blank PDF generated by Adobe Illustrator CC 17, containing a lot of application-specific metadata/bloat
|
||||||
- @jbarlow83
|
|
||||||
- @jbarlow83
|
|
||||||
- CC-BY-SA 4.0
|
|
||||||
* - cmyk.pdf
|
* - cmyk.pdf
|
||||||
|
- synthetic
|
||||||
- a CMYK image created in Photoshop
|
- a CMYK image created in Photoshop
|
||||||
- @jbarlow83
|
|
||||||
- @jbarlow83
|
|
||||||
- CC-BY-SA 4.0
|
|
||||||
* - crom.png
|
* - crom.png
|
||||||
|
- synthetic
|
||||||
- test for non-dictionary words
|
- test for non-dictionary words
|
||||||
- @jbarlow83
|
|
||||||
- @jbarlow83
|
|
||||||
- CC-BY-SA 4.0
|
|
||||||
* - enormous.pdf
|
* - enormous.pdf
|
||||||
|
- synthetic
|
||||||
- very large PDF page
|
- very large PDF page
|
||||||
- @jbarlow83
|
|
||||||
- @jbarlow83
|
|
||||||
- CC-BY-SA 4.0
|
|
||||||
* - epson.pdf
|
* - epson.pdf
|
||||||
|
- synthetic
|
||||||
- a linearized PDF containing some unusual indirect objects, created by an Epson printer; printout of a Wikipedia article (CC-BY-SA)
|
- a linearized PDF containing some unusual indirect objects, created by an Epson printer; printout of a Wikipedia article (CC-BY-SA)
|
||||||
- @lowesjam
|
|
||||||
- Wikipedia authors
|
|
||||||
- CC-BY-SA 3.0
|
|
||||||
* - formxobject.pdf
|
* - formxobject.pdf
|
||||||
|
- synthetic
|
||||||
- hand-crafted PDF containing an image inside a Form XObject
|
- hand-crafted PDF containing an image inside a Form XObject
|
||||||
- @jbarlow83
|
|
||||||
- @jbarlow83
|
|
||||||
- CC-BY-SA 4.0
|
|
||||||
* - francais.pdf
|
* - francais.pdf
|
||||||
|
- synthetic
|
||||||
- a page containing French accents (diacritics)
|
- a page containing French accents (diacritics)
|
||||||
- @jbarlow83
|
|
||||||
- @jbarlow83
|
|
||||||
- CC-BY-SA 4.0
|
|
||||||
* - hugemono.pdf
|
* - hugemono.pdf
|
||||||
|
- synthetic
|
||||||
- large monochrome 35000x35000 image in JBIG2 encoding
|
- large monochrome 35000x35000 image in JBIG2 encoding
|
||||||
- @jbarlow83
|
|
||||||
- @jbarlow83
|
|
||||||
- CC-BY-SA 4.0
|
|
||||||
* - invalid.pdf
|
* - invalid.pdf
|
||||||
|
- synthetic
|
||||||
- a PDF file header followed by EOF marker
|
- a PDF file header followed by EOF marker
|
||||||
- @jbarlow83
|
|
||||||
- @jbarlow83
|
|
||||||
- CC-BY-SA 4.0
|
|
||||||
* - kcs.pdf
|
* - kcs.pdf
|
||||||
|
- synthetic
|
||||||
- PDF file generated by Kodak Capture Desktop Software 1.2; has invalid table of contents
|
- PDF file generated by Kodak Capture Desktop Software 1.2; has invalid table of contents
|
||||||
- @jbarlow83
|
|
||||||
- @jbarlow83
|
|
||||||
- CC-BY-SA 4.0
|
|
||||||
* - livecycle.pdf
|
* - livecycle.pdf
|
||||||
|
- synthetic
|
||||||
- a minimal PDF that claims to use dynamic XFA forms
|
- a minimal PDF that claims to use dynamic XFA forms
|
||||||
- @jbarlow83
|
|
||||||
- @jbarlow83
|
|
||||||
- CC-BY-SA 4.0
|
|
||||||
* - masks.pdf
|
* - masks.pdf
|
||||||
|
- synthetic
|
||||||
- file containing explicit masks and a stencil mask drawn without a proper transformation matrix; printout of a German Wikipedia article (CC-BY-SA)
|
- file containing explicit masks and a stencil mask drawn without a proper transformation matrix; printout of a German Wikipedia article (CC-BY-SA)
|
||||||
- @supergrobi
|
|
||||||
- Wikipedia authors
|
|
||||||
- CC-BY-SA 3.0
|
|
||||||
* - missing_docinfo.pdf
|
* - missing_docinfo.pdf
|
||||||
|
- synthetic
|
||||||
- PDF file with no /DocumentInfo section
|
- PDF file with no /DocumentInfo section
|
||||||
- @jbarlow83
|
|
||||||
- @jbarlow83
|
|
||||||
- CC-BY-SA 4.0
|
|
||||||
* - overlay.pdf
|
* - overlay.pdf
|
||||||
|
- synthetic
|
||||||
- PDF file generated by PDFPen pro that triggered content stream parse errors
|
- PDF file generated by PDFPen pro that triggered content stream parse errors
|
||||||
- @maxandersen
|
|
||||||
- @maxandersen
|
|
||||||
- CC-BY-SA 4.0
|
|
||||||
* - negzero.pdf
|
* - negzero.pdf
|
||||||
|
- synthetic
|
||||||
- copy of formxobject.pdf with token that qpdf doesn't like
|
- copy of formxobject.pdf with token that qpdf doesn't like
|
||||||
- @jbarlow83
|
|
||||||
- @jbarlow83
|
|
||||||
- CC-BY-SA 4.0
|
|
||||||
* - no_contents.pdf
|
* - no_contents.pdf
|
||||||
|
- synthetic
|
||||||
- synthetic PDF with a blank page that has no /Contents entry
|
- synthetic PDF with a blank page that has no /Contents entry
|
||||||
- @jbarlow83
|
|
||||||
- @jbarlow83
|
|
||||||
- CC-BY-SA 4.0
|
|
||||||
* - truetype_font_nomapping.pdf
|
* - truetype_font_nomapping.pdf
|
||||||
|
- synthetic
|
||||||
- example of a PDF with an embedded subsetted TrueType font with no Unicode mapping
|
- example of a PDF with an embedded subsetted TrueType font with no Unicode mapping
|
||||||
- @jbarlow83
|
|
||||||
- @jbarlow83
|
|
||||||
- CC-BY-SA 4.0
|
|
||||||
* - trivial.pdf
|
* - trivial.pdf
|
||||||
|
- synthetic
|
||||||
- smallest possible valid PDF-1.3 with all required fields
|
- smallest possible valid PDF-1.3 with all required fields
|
||||||
- @jbarlow83
|
|
||||||
- @jbarlow83
|
|
||||||
- CC-BY-SA 4.0
|
|
||||||
* - type3_font_nomapping.pdf
|
* - type3_font_nomapping.pdf
|
||||||
|
- synthetic
|
||||||
- example of a PDF with an embedded subsetted TrueType font with no Unicode mapping
|
- example of a PDF with an embedded subsetted TrueType font with no Unicode mapping
|
||||||
- @jbarlow83
|
|
||||||
- @jbarlow83
|
|
||||||
- CC-BY-SA 4.0
|
|
||||||
* - vector.pdf
|
* - vector.pdf
|
||||||
|
- synthetic
|
||||||
- a PDF with vector art and text rendered as curves with no fonts
|
- a PDF with vector art and text rendered as curves with no fonts
|
||||||
- @Catscratch
|
|
||||||
- @Catscratch
|
|
||||||
- CC-BY-SA 4.0
|
|
||||||
|
|
||||||
|
|
||||||
Assemblies
|
Assemblies
|
||||||
==========
|
==========
|
||||||
@@ -174,14 +111,12 @@ These test resources are assemblies or derivatives from other previously mention
|
|||||||
- baiona_alpha.png (from baiona.png, RGB+A version)
|
- baiona_alpha.png (from baiona.png, RGB+A version)
|
||||||
- cardinal.pdf (four cardinal directions, baked-in rotated copies of linn.png)
|
- cardinal.pdf (four cardinal directions, baked-in rotated copies of linn.png)
|
||||||
- ccitt.pdf (linn.png, converted to CCITT encoding)
|
- ccitt.pdf (linn.png, converted to CCITT encoding)
|
||||||
- encrypted_algo4.pdf (congress.jpg, encrypted with algorithm 4 - not supported by PyPDF2)
|
|
||||||
- graph_ocred.pdf (from graph.pdf)
|
- graph_ocred.pdf (from graph.pdf)
|
||||||
- jbig2.pdf (congress.jpg, converted to JBIG2 encoding)
|
- jbig2.pdf (from linn.png)
|
||||||
- multipage.pdf (from several other files)
|
- multipage.pdf (from several other files)
|
||||||
- palette.pdf (congress.jpg, converted to a 256-color palette)
|
- palette.pdf (from baiona_colormapped.png)
|
||||||
- poster.pdf (from linn.png)
|
- poster.pdf (from linn.png)
|
||||||
- rotated_skew.pdf (a /Rotate'd and skewed document from linn.png)
|
- rotated_skew.pdf (a /Rotate'd and skewed document from linn.png)
|
||||||
- skew-encrypted.pdf (skew.pdf with encryption - access supported by PyPDF2, password is "password")
|
|
||||||
- skew.pdf (from linn.png, skew simulated by adjusting the transformation matrix)
|
- skew.pdf (from linn.png, skew simulated by adjusting the transformation matrix)
|
||||||
- toc.pdf (from formxobject.pdf, trivial.pdf)
|
- toc.pdf (from formxobject.pdf, trivial.pdf)
|
||||||
|
|
||||||
@@ -192,7 +127,7 @@ These test resources are assemblies or derivatives from other previously mention
|
|||||||
|
|
||||||
.. _`US Congressional Records`: http://www.baxleystamps.com/litho/meiji/courts_1871.jpg
|
.. _`US Congressional Records`: http://www.baxleystamps.com/litho/meiji/courts_1871.jpg
|
||||||
|
|
||||||
.. _`Wikimedia: Pandas text analysis.png`: https://en.wikipedia.org/wiki/File:Pandas_text_analysis.png
|
.. _`Wikimedia: Simple_line_graph_of_ACE_2012_results_by_candidate_sj01.png`: https://en.wikipedia.org/wiki/File:Simple_line_graph_of_ACE_2012_results_by_candidate_sj01.png
|
||||||
|
|
||||||
.. _`Wikimedia: JPEG2000 Lichtenstein`: https://en.wikipedia.org/wiki/JPEG_2000#/media/File:Jpeg2000_2-level_wavelet_transform-lichtenstein.png
|
.. _`Wikimedia: JPEG2000 Lichtenstein`: https://en.wikipedia.org/wiki/JPEG_2000#/media/File:Jpeg2000_2-level_wavelet_transform-lichtenstein.png
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Regular → Executable
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -14,9 +14,9 @@ from .conftest import run_ocrmypdf_api
|
|||||||
|
|
||||||
@pytest.mark.skipif(True, reason="--use-threads is currently default")
|
@pytest.mark.skipif(True, reason="--use-threads is currently default")
|
||||||
@pytest.mark.skipif(os.name == 'nt', reason="Windows doesn't have SIGKILL")
|
@pytest.mark.skipif(os.name == 'nt', reason="Windows doesn't have SIGKILL")
|
||||||
def test_simulate_oom_killer(resources, no_outpdf):
|
def test_simulate_oom_killer(multipage, no_outpdf):
|
||||||
exitcode = run_ocrmypdf_api(
|
exitcode = run_ocrmypdf_api(
|
||||||
resources / 'multipage.pdf',
|
multipage,
|
||||||
no_outpdf,
|
no_outpdf,
|
||||||
'--force-ocr',
|
'--force-ocr',
|
||||||
'--plugin',
|
'--plugin',
|
||||||
|
|||||||
+15
-11
@@ -6,9 +6,11 @@ from __future__ import annotations
|
|||||||
import logging
|
import logging
|
||||||
import multiprocessing
|
import multiprocessing
|
||||||
import os
|
import os
|
||||||
|
from pathlib import Path
|
||||||
from unittest.mock import MagicMock
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
from packaging.version import Version
|
||||||
|
|
||||||
from ocrmypdf import helpers
|
from ocrmypdf import helpers
|
||||||
|
|
||||||
@@ -52,15 +54,6 @@ def test_no_cpu_count(monkeypatch):
|
|||||||
assert invoked, "Patched function called during test"
|
assert invoked, "Patched function called during test"
|
||||||
|
|
||||||
|
|
||||||
def test_deprecated():
|
|
||||||
@helpers.deprecated
|
|
||||||
def old_function():
|
|
||||||
return 42
|
|
||||||
|
|
||||||
with pytest.deprecated_call():
|
|
||||||
assert old_function() == 42
|
|
||||||
|
|
||||||
|
|
||||||
skipif_docker = pytest.mark.skipif(running_in_docker(), reason="fails on Docker")
|
skipif_docker = pytest.mark.skipif(running_in_docker(), reason="fails on Docker")
|
||||||
|
|
||||||
|
|
||||||
@@ -100,6 +93,17 @@ class TestFileIsWritable:
|
|||||||
assert not helpers.is_file_writable(pathmock)
|
assert not helpers.is_file_writable(pathmock)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skipif(os.name != 'nt', reason="Windows test")
|
||||||
|
def test_gs_install_locations():
|
||||||
|
# pylint: disable=import-outside-toplevel
|
||||||
|
from ocrmypdf.subprocess._windows import _gs_version_in_path_key
|
||||||
|
|
||||||
|
assert _gs_version_in_path_key(Path("C:\\Program Files\\gs\\gs9.52\\bin")) == (
|
||||||
|
'gs',
|
||||||
|
Version('9.52'),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(os.name != 'nt', reason="Windows test")
|
@pytest.mark.skipif(os.name != 'nt', reason="Windows test")
|
||||||
def test_shim_paths(tmp_path):
|
def test_shim_paths(tmp_path):
|
||||||
# pylint: disable=import-outside-toplevel
|
# pylint: disable=import-outside-toplevel
|
||||||
@@ -109,7 +113,7 @@ def test_shim_paths(tmp_path):
|
|||||||
progfiles.mkdir()
|
progfiles.mkdir()
|
||||||
(progfiles / 'tesseract-ocr').mkdir()
|
(progfiles / 'tesseract-ocr').mkdir()
|
||||||
(progfiles / 'gs' / '9.51' / 'bin').mkdir(parents=True)
|
(progfiles / 'gs' / '9.51' / 'bin').mkdir(parents=True)
|
||||||
(progfiles / 'gs' / '9.52' / 'bin').mkdir(parents=True)
|
(progfiles / 'gs' / 'gs9.52.3' / 'bin').mkdir(parents=True)
|
||||||
syspath = tmp_path / 'bin'
|
syspath = tmp_path / 'bin'
|
||||||
env = {'PROGRAMFILES': str(progfiles), 'PATH': str(syspath)}
|
env = {'PROGRAMFILES': str(progfiles), 'PATH': str(syspath)}
|
||||||
|
|
||||||
@@ -117,7 +121,7 @@ def test_shim_paths(tmp_path):
|
|||||||
results = result_str.split(os.pathsep)
|
results = result_str.split(os.pathsep)
|
||||||
assert results[0] == str(syspath), results
|
assert results[0] == str(syspath), results
|
||||||
assert results[-3].endswith('tesseract-ocr'), results
|
assert results[-3].endswith('tesseract-ocr'), results
|
||||||
assert results[-2].endswith(os.path.join('gs', '9.52', 'bin')), results
|
assert results[-2].endswith(os.path.join('gs9.52.3', 'bin')), results
|
||||||
assert results[-1].endswith(os.path.join('gs', '9.51', 'bin')), results
|
assert results[-1].endswith(os.path.join('gs', '9.51', 'bin')), results
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+26
-25
@@ -7,16 +7,15 @@ import os
|
|||||||
import shutil
|
import shutil
|
||||||
from math import isclose
|
from math import isclose
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from subprocess import PIPE, run
|
from subprocess import run
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
import pikepdf
|
import pikepdf
|
||||||
import PIL
|
|
||||||
import pytest
|
import pytest
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
import ocrmypdf
|
import ocrmypdf
|
||||||
from ocrmypdf._exec import ghostscript, tesseract
|
from ocrmypdf._exec import tesseract
|
||||||
from ocrmypdf.exceptions import ExitCode, MissingDependencyError
|
from ocrmypdf.exceptions import ExitCode, MissingDependencyError
|
||||||
from ocrmypdf.pdfa import file_claims_pdfa
|
from ocrmypdf.pdfa import file_claims_pdfa
|
||||||
from ocrmypdf.pdfinfo import Colorspace, Encoding, PdfInfo
|
from ocrmypdf.pdfinfo import Colorspace, Encoding, PdfInfo
|
||||||
@@ -153,9 +152,9 @@ def test_skip_big(resources, outpdf):
|
|||||||
|
|
||||||
@pytest.mark.parametrize('renderer', RENDERERS)
|
@pytest.mark.parametrize('renderer', RENDERERS)
|
||||||
@pytest.mark.parametrize('output_type', ['pdf', 'pdfa'])
|
@pytest.mark.parametrize('output_type', ['pdf', 'pdfa'])
|
||||||
def test_maximum_options(renderer, output_type, resources, outpdf):
|
def test_maximum_options(renderer, output_type, multipage, outpdf):
|
||||||
check_ocrmypdf(
|
check_ocrmypdf(
|
||||||
resources / 'multipage.pdf',
|
multipage,
|
||||||
outpdf,
|
outpdf,
|
||||||
'-d',
|
'-d',
|
||||||
'-ci' if have_unpaper() else None,
|
'-ci' if have_unpaper() else None,
|
||||||
@@ -179,7 +178,8 @@ def test_maximum_options(renderer, output_type, resources, outpdf):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(
|
@pytest.mark.skipif(
|
||||||
tesseract.version() >= '5', reason="tess 5 tries harder to find its files"
|
tesseract.TesseractVersion(tesseract.version()) >= tesseract.TesseractVersion('5'),
|
||||||
|
reason="tess 5 tries harder to find its files",
|
||||||
)
|
)
|
||||||
def test_tesseract_missing_tessdata(monkeypatch, resources, no_outpdf, tmpdir):
|
def test_tesseract_missing_tessdata(monkeypatch, resources, no_outpdf, tmpdir):
|
||||||
monkeypatch.setenv("TESSDATA_PREFIX", os.fspath(tmpdir))
|
monkeypatch.setenv("TESSDATA_PREFIX", os.fspath(tmpdir))
|
||||||
@@ -292,12 +292,6 @@ def test_input_file_not_a_pdf(caplog, no_outpdf):
|
|||||||
assert input_file in caplog.text
|
assert input_file in caplog.text
|
||||||
|
|
||||||
|
|
||||||
def test_encrypted(resources, caplog, no_outpdf):
|
|
||||||
result = run_ocrmypdf_api(resources / 'skew-encrypted.pdf', no_outpdf)
|
|
||||||
assert result == ExitCode.encrypted_pdf
|
|
||||||
assert 'encryption must be removed' in caplog.text
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('renderer', RENDERERS)
|
@pytest.mark.parametrize('renderer', RENDERERS)
|
||||||
def test_pagesegmode(renderer, resources, outpdf):
|
def test_pagesegmode(renderer, resources, outpdf):
|
||||||
check_ocrmypdf(
|
check_ocrmypdf(
|
||||||
@@ -398,14 +392,27 @@ def test_tesseract_image_too_big(renderer, resources, outpdf):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_algo4(resources, outpdf):
|
@pytest.mark.parametrize('encryption_level', [2, 3, 4, 6])
|
||||||
p = run_ocrmypdf(
|
def test_encrypted(resources, outpdf, encryption_level, caplog):
|
||||||
resources / 'encrypted_algo4.pdf',
|
encryption = pikepdf.models.encryption.Encryption(
|
||||||
|
owner='ocrmypdf',
|
||||||
|
user='ocrmypdf',
|
||||||
|
R=encryption_level,
|
||||||
|
aes=(encryption_level >= 4),
|
||||||
|
metadata=(encryption_level == 6),
|
||||||
|
)
|
||||||
|
|
||||||
|
with pikepdf.open(resources / 'jbig2.pdf') as pdf:
|
||||||
|
pdf.save(outpdf, encryption=encryption)
|
||||||
|
|
||||||
|
exitcode = run_ocrmypdf_api(
|
||||||
|
outpdf,
|
||||||
outpdf,
|
outpdf,
|
||||||
'--plugin',
|
'--plugin',
|
||||||
'tests/plugins/tesseract_noop.py',
|
'tests/plugins/tesseract_noop.py',
|
||||||
)
|
)
|
||||||
assert p.returncode == ExitCode.encrypted_pdf
|
assert exitcode == ExitCode.encrypted_pdf
|
||||||
|
assert 'encryption must be removed' in caplog.text
|
||||||
|
|
||||||
|
|
||||||
def test_jbig2_passthrough(resources, outpdf):
|
def test_jbig2_passthrough(resources, outpdf):
|
||||||
@@ -539,7 +546,6 @@ def test_tesseract_config_invalid(renderer, resources, invalid_tess_config, outp
|
|||||||
assert p.returncode == ExitCode.invalid_config
|
assert p.returncode == ExitCode.invalid_config
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(not tesseract.has_user_words(), reason='not functional until 4.1.0')
|
|
||||||
def test_user_words_ocr(resources, outdir):
|
def test_user_words_ocr(resources, outdir):
|
||||||
# Does not actually test if --user-words causes output to differ
|
# Does not actually test if --user-words causes output to differ
|
||||||
word_list = outdir / 'wordlist.txt'
|
word_list = outdir / 'wordlist.txt'
|
||||||
@@ -716,11 +722,9 @@ def test_compression_changed(ocrmypdf_exec, resources, image, compression, outpd
|
|||||||
if compression == "jpeg":
|
if compression == "jpeg":
|
||||||
assert pdfimage.enc == Encoding.jpeg
|
assert pdfimage.enc == Encoding.jpeg
|
||||||
else:
|
else:
|
||||||
if ghostscript.jpeg_passthrough_available():
|
if image.endswith('jpg'):
|
||||||
# Ghostscript 9.23 adds JPEG passthrough, which allows a JPEG to be
|
# Ghostscript JPEG passthrough - no issue
|
||||||
# copied without transcoding - so report
|
assert pdfimage.enc == Encoding.jpeg
|
||||||
if image.endswith('jpg'):
|
|
||||||
assert pdfimage.enc == Encoding.jpeg
|
|
||||||
else:
|
else:
|
||||||
assert pdfimage.enc not in (Encoding.jpeg, Encoding.jpeg2000)
|
assert pdfimage.enc not in (Encoding.jpeg, Encoding.jpeg2000)
|
||||||
|
|
||||||
@@ -774,9 +778,6 @@ def test_sidecar_nonempty(resources, outpdf):
|
|||||||
|
|
||||||
@pytest.mark.parametrize('pdfa_level', ['1', '2', '3'])
|
@pytest.mark.parametrize('pdfa_level', ['1', '2', '3'])
|
||||||
def test_pdfa_n(pdfa_level, resources, outpdf):
|
def test_pdfa_n(pdfa_level, resources, outpdf):
|
||||||
if pdfa_level == '3' and ghostscript.version() < '9.19':
|
|
||||||
pytest.xfail(reason='Ghostscript >= 9.19 required')
|
|
||||||
|
|
||||||
check_ocrmypdf(
|
check_ocrmypdf(
|
||||||
resources / 'ccitt.pdf',
|
resources / 'ccitt.pdf',
|
||||||
outpdf,
|
outpdf,
|
||||||
|
|||||||
@@ -28,9 +28,6 @@ except ImportError:
|
|||||||
fitz = None
|
fitz = None
|
||||||
|
|
||||||
|
|
||||||
pytestmark = pytest.mark.filterwarnings('ignore:.*XMLParser.*:DeprecationWarning')
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("output_type", ['pdfa', 'pdf'])
|
@pytest.mark.parametrize("output_type", ['pdfa', 'pdf'])
|
||||||
def test_preserve_docinfo(output_type, resources, outpdf):
|
def test_preserve_docinfo(output_type, resources, outpdf):
|
||||||
pdf_before = pikepdf.open(resources / 'graph.pdf')
|
pdf_before = pikepdf.open(resources / 'graph.pdf')
|
||||||
@@ -174,7 +171,12 @@ def test_creation_date_preserved(output_type, resources, infile, outpdf):
|
|||||||
def libxmp_file_to_dict():
|
def libxmp_file_to_dict():
|
||||||
try:
|
try:
|
||||||
with warnings.catch_warnings():
|
with warnings.catch_warnings():
|
||||||
warnings.simplefilter("ignore", DeprecationWarning)
|
# libxmp imports distutils.Version, which is deprecated
|
||||||
|
warnings.filterwarnings(
|
||||||
|
"ignore",
|
||||||
|
category=DeprecationWarning,
|
||||||
|
message=r".*distutils Version classes are deprecated.*",
|
||||||
|
)
|
||||||
from libxmp.utils import (
|
from libxmp.utils import (
|
||||||
file_to_dict, # pylint: disable=import-outside-toplevel
|
file_to_dict, # pylint: disable=import-outside-toplevel
|
||||||
)
|
)
|
||||||
|
|||||||
+11
-3
@@ -27,10 +27,18 @@ needs_jbig2enc = pytest.mark.skipif(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# pylint:disable=redefined-outer-name
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session")
|
||||||
|
def palette(resources):
|
||||||
|
return resources / 'palette.pdf'
|
||||||
|
|
||||||
|
|
||||||
@needs_pngquant
|
@needs_pngquant
|
||||||
@pytest.mark.parametrize('pdf', ['multipage.pdf', 'palette.pdf'])
|
@pytest.mark.parametrize('pdf', ['multipage', 'palette'])
|
||||||
def test_basic(resources, pdf, outpdf):
|
def test_basic(multipage, palette, pdf, outpdf):
|
||||||
infile = resources / pdf
|
infile = multipage if pdf == 'multipage' else palette
|
||||||
opt.main(infile, outpdf, level=3)
|
opt.main(infile, outpdf, level=3)
|
||||||
|
|
||||||
assert 0.98 * Path(outpdf).stat().st_size <= Path(infile).stat().st_size
|
assert 0.98 * Path(outpdf).stat().st_size <= Path(infile).stat().st_size
|
||||||
|
|||||||
@@ -48,10 +48,9 @@ def test_nonmonotonic_warning(caplog):
|
|||||||
assert 'out of order' in caplog.text
|
assert 'out of order' in caplog.text
|
||||||
|
|
||||||
|
|
||||||
def test_limited_pages(resources, outpdf):
|
def test_limited_pages(multipage, outpdf):
|
||||||
multi = resources / 'multipage.pdf'
|
|
||||||
ocrmypdf.ocr(
|
ocrmypdf.ocr(
|
||||||
multi,
|
multipage,
|
||||||
outpdf,
|
outpdf,
|
||||||
pages='5-6',
|
pages='5-6',
|
||||||
optimize=0,
|
optimize=0,
|
||||||
|
|||||||
@@ -276,7 +276,8 @@ def test_rasterize_rotates(resources, tmp_path):
|
|||||||
rotation=90,
|
rotation=90,
|
||||||
filter_vector=False,
|
filter_vector=False,
|
||||||
)
|
)
|
||||||
assert Image.open(img).size == (123, 151), "Image not rotated"
|
with Image.open(img) as im:
|
||||||
|
assert im.size == (83, 200), "Image not rotated"
|
||||||
|
|
||||||
img = tmp_path / 'img180.png'
|
img = tmp_path / 'img180.png'
|
||||||
pm.hook.rasterize_pdf_page(
|
pm.hook.rasterize_pdf_page(
|
||||||
@@ -289,7 +290,7 @@ def test_rasterize_rotates(resources, tmp_path):
|
|||||||
rotation=180,
|
rotation=180,
|
||||||
filter_vector=False,
|
filter_vector=False,
|
||||||
)
|
)
|
||||||
assert Image.open(img).size == (151, 123), "Image not rotated"
|
assert Image.open(img).size == (200, 83), "Image not rotated"
|
||||||
|
|
||||||
|
|
||||||
def test_simulated_scan(outdir):
|
def test_simulated_scan(outdir):
|
||||||
|
|||||||
@@ -51,10 +51,6 @@ def test_stdout(ocrmypdf_exec, resources, outpdf):
|
|||||||
assert check_pdf(output_file)
|
assert check_pdf(output_file)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.xfail(
|
|
||||||
os.name == 'nt' and sys.version_info < (3, 8),
|
|
||||||
reason="Windows does not like this; not sure how to fix",
|
|
||||||
)
|
|
||||||
def test_dev_null(resources):
|
def test_dev_null(resources):
|
||||||
if 'COV_CORE_DATAFILE' in os.environ:
|
if 'COV_CORE_DATAFILE' in os.environ:
|
||||||
pytest.skip("Coverage uses stdout")
|
pytest.skip("Coverage uses stdout")
|
||||||
|
|||||||
+39
-56
@@ -48,28 +48,22 @@ def test_hocr_notlatin_warning(caplog):
|
|||||||
assert 'PDF renderer is known to cause' in caplog.text
|
assert 'PDF renderer is known to cause' in caplog.text
|
||||||
|
|
||||||
|
|
||||||
def test_old_ghostscript(caplog):
|
|
||||||
with patch('ocrmypdf._exec.ghostscript.version', return_value='9.19'), patch(
|
|
||||||
'ocrmypdf._exec.tesseract.get_languages', return_value={'eng', 'chi_sim'}
|
|
||||||
):
|
|
||||||
vd.check_options(*make_opts_pm(language='chi_sim', output_type='pdfa'))
|
|
||||||
assert 'does not work correctly' in caplog.text
|
|
||||||
|
|
||||||
with patch('ocrmypdf._exec.ghostscript.version', return_value='9.18'):
|
|
||||||
with pytest.raises(MissingDependencyError):
|
|
||||||
vd.check_options(*make_opts_pm(output_type='pdfa-3'))
|
|
||||||
|
|
||||||
with patch('ocrmypdf._exec.ghostscript.version', return_value='9.24'):
|
|
||||||
with pytest.raises(MissingDependencyError):
|
|
||||||
vd.check_options(*make_opts_pm())
|
|
||||||
|
|
||||||
|
|
||||||
def test_old_tesseract_error():
|
def test_old_tesseract_error():
|
||||||
with patch('ocrmypdf._exec.tesseract.version', return_value='4.00.00alpha'):
|
with patch('ocrmypdf._exec.tesseract.version', return_value='4.00.00alpha'):
|
||||||
with pytest.raises(MissingDependencyError):
|
with pytest.raises(MissingDependencyError):
|
||||||
opts = make_opts(pdf_renderer='sandwich', language='eng')
|
vd.check_options(*make_opts_pm(pdf_renderer='sandwich', language='eng'))
|
||||||
plugin_manager = get_plugin_manager(opts.plugins)
|
|
||||||
vd.check_options(opts, plugin_manager)
|
|
||||||
|
def test_tesseract_not_installed(caplog):
|
||||||
|
with patch('ocrmypdf.subprocess.run') as not_found:
|
||||||
|
not_found.side_effect = FileNotFoundError('tesseract')
|
||||||
|
with pytest.raises(MissingDependencyError, match="Could not find program"):
|
||||||
|
vd.check_options(*make_opts_pm())
|
||||||
|
assert (
|
||||||
|
"'tesseract' could not be executed" in caplog.text
|
||||||
|
), "Error message not printed"
|
||||||
|
assert 'install' in caplog.text, "Install advice not printed"
|
||||||
|
not_found.assert_called()
|
||||||
|
|
||||||
|
|
||||||
def test_lossless_redo():
|
def test_lossless_redo():
|
||||||
@@ -87,32 +81,12 @@ def test_mutex_options():
|
|||||||
|
|
||||||
|
|
||||||
def test_optimizing(caplog):
|
def test_optimizing(caplog):
|
||||||
opts = make_opts(optimize=0, jbig2_lossy=True, png_quality=18, jpeg_quality=10)
|
vd.check_options(
|
||||||
plugin_manager = get_plugin_manager(opts.plugins)
|
*make_opts_pm(optimize=0, jbig2_lossy=True, png_quality=18, jpeg_quality=10)
|
||||||
vd.check_options(opts, plugin_manager)
|
)
|
||||||
assert 'will be ignored because' in caplog.text
|
assert 'will be ignored because' in caplog.text
|
||||||
|
|
||||||
|
|
||||||
def test_user_words(caplog):
|
|
||||||
with patch('ocrmypdf._exec.tesseract.has_user_words', return_value=False):
|
|
||||||
opts = make_opts(user_words='foo')
|
|
||||||
plugin_manager = get_plugin_manager(opts.plugins)
|
|
||||||
vd.check_options(opts, plugin_manager)
|
|
||||||
assert (
|
|
||||||
'Tesseract 4.0 (which you have installed) ignores --user-words'
|
|
||||||
in caplog.text
|
|
||||||
)
|
|
||||||
caplog.clear()
|
|
||||||
with patch('ocrmypdf._exec.tesseract.has_user_words', return_value=True):
|
|
||||||
opts = make_opts(user_patterns='foo')
|
|
||||||
plugin_manager = get_plugin_manager(opts.plugins)
|
|
||||||
vd.check_options(opts, plugin_manager)
|
|
||||||
assert (
|
|
||||||
'Tesseract 4.0 (which you have installed) ignores --user-words'
|
|
||||||
not in caplog.text
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_pillow_options():
|
def test_pillow_options():
|
||||||
vd.check_options_pillow(make_opts(max_image_mpixels=0))
|
vd.check_options_pillow(make_opts(max_image_mpixels=0))
|
||||||
|
|
||||||
@@ -124,6 +98,8 @@ def test_output_tty():
|
|||||||
|
|
||||||
|
|
||||||
def test_report_file_size(tmp_path, caplog):
|
def test_report_file_size(tmp_path, caplog):
|
||||||
|
logging.getLogger('pikepdf._qpdf').setLevel(logging.CRITICAL) # Suppress logging
|
||||||
|
|
||||||
in_ = tmp_path / 'a.pdf'
|
in_ = tmp_path / 'a.pdf'
|
||||||
out = tmp_path / 'b.pdf'
|
out = tmp_path / 'b.pdf'
|
||||||
pdf = pikepdf.new()
|
pdf = pikepdf.new()
|
||||||
@@ -163,10 +139,10 @@ def test_false_action_store_true():
|
|||||||
|
|
||||||
@pytest.mark.parametrize('progress_bar', [True, False])
|
@pytest.mark.parametrize('progress_bar', [True, False])
|
||||||
def test_no_progress_bar(progress_bar, resources):
|
def test_no_progress_bar(progress_bar, resources):
|
||||||
opts = make_opts(progress_bar=progress_bar, input_file=(resources / 'trivial.pdf'))
|
opts, pm = make_opts_pm(
|
||||||
plugin_manager = get_plugin_manager(opts.plugins)
|
progress_bar=progress_bar, input_file=(resources / 'trivial.pdf')
|
||||||
|
)
|
||||||
vd.check_options(opts, plugin_manager)
|
vd.check_options(opts, pm)
|
||||||
|
|
||||||
pbar_disabled = None
|
pbar_disabled = None
|
||||||
|
|
||||||
@@ -223,37 +199,44 @@ def test_version_comparison():
|
|||||||
program="tesseract",
|
program="tesseract",
|
||||||
package="tesseract",
|
package="tesseract",
|
||||||
version_checker=lambda: '4.0.0-beta.1',
|
version_checker=lambda: '4.0.0-beta.1',
|
||||||
need_version='4.0.0',
|
need_version='4.1.1',
|
||||||
version_parser=TesseractVersion,
|
version_parser=TesseractVersion,
|
||||||
)
|
)
|
||||||
vd.check_external_program(
|
vd.check_external_program(
|
||||||
program="tesseract",
|
program="tesseract",
|
||||||
package="tesseract",
|
package="tesseract",
|
||||||
version_checker=lambda: 'v5.0.0-alpha.20200201',
|
version_checker=lambda: 'v5.0.0-alpha.20200201',
|
||||||
need_version='4.0.0',
|
need_version='4.1.1',
|
||||||
version_parser=TesseractVersion,
|
version_parser=TesseractVersion,
|
||||||
)
|
)
|
||||||
vd.check_external_program(
|
vd.check_external_program(
|
||||||
program="tesseract",
|
program="tesseract",
|
||||||
package="tesseract",
|
package="tesseract",
|
||||||
version_checker=lambda: '5.0.0-rc1.20211030',
|
version_checker=lambda: '5.0.0-rc1.20211030',
|
||||||
need_version='4.0.0',
|
need_version='4.1.1',
|
||||||
version_parser=TesseractVersion,
|
version_parser=TesseractVersion,
|
||||||
)
|
)
|
||||||
vd.check_external_program(
|
vd.check_external_program(
|
||||||
program="tesseract",
|
program="tesseract",
|
||||||
package="tesseract",
|
package="tesseract",
|
||||||
version_checker=lambda: 'v4.0.0.20181030', # Some Windows builds use this format
|
version_checker=lambda: 'v4.1.1.20181030', # Some Windows builds use this format
|
||||||
need_version='4.0.0',
|
need_version='4.1.1',
|
||||||
version_parser=TesseractVersion,
|
version_parser=TesseractVersion,
|
||||||
)
|
)
|
||||||
vd.check_external_program(
|
vd.check_external_program(
|
||||||
program="tesseract",
|
program="gs",
|
||||||
package="tesseract",
|
package="ghostscript",
|
||||||
version_checker=lambda: '4.1.1-rc2-25-g9707',
|
version_checker=lambda: '10.0',
|
||||||
need_version='4.0.0',
|
need_version='9.50',
|
||||||
version_parser=TesseractVersion,
|
|
||||||
)
|
)
|
||||||
|
with pytest.raises(MissingDependencyError):
|
||||||
|
vd.check_external_program(
|
||||||
|
program="tesseract",
|
||||||
|
package="tesseract",
|
||||||
|
version_checker=lambda: '4.1.1-rc2-25-g9707',
|
||||||
|
need_version='4.1.1',
|
||||||
|
version_parser=TesseractVersion,
|
||||||
|
)
|
||||||
with pytest.raises(MissingDependencyError):
|
with pytest.raises(MissingDependencyError):
|
||||||
vd.check_external_program(
|
vd.check_external_program(
|
||||||
program="dummy_fails",
|
program="dummy_fails",
|
||||||
|
|||||||
Reference in New Issue
Block a user