Compare commits

..
29 Commits
Author SHA1 Message Date
James R. Barlow 199de96cff Ghostcript 9.21 seems to have a regression related to Unicode metadata 2017-03-24 15:15:46 -07:00
James R. Barlow 8ddbe81513 Fix issue #147: unpaper loses DPI information, affects —pdf-renderer tess4 2017-03-24 13:23:03 -07:00
James R. Barlow a3e26e0498 Make —pdf-renderer tess4 more informative, less FUD 2017-03-24 12:58:40 -07:00
James R. Barlow 4ad129d8d8 docs: Don't recommend system-site-packages anymore (Ubuntu 16.04)
Not needed since reportlab 3.4 comes with a wheel, and that was the main difficulty.
2017-03-23 08:13:28 -07:00
James R. Barlow dfb9fa0736 autobrew: missing deps 2017-03-16 00:18:07 -07:00
James R. Barlow eb036898e9 Update documentation with macOS homebrew tap
[ci skip]
2017-03-13 22:25:25 -07:00
James R. Barlow 7c6aa76a2a Fix brew audit failure 2017-03-13 17:58:31 -07:00
James R. Barlow f035cb1088 Fixed issue #142 — closed streams raise an exception on fork attempt 2017-03-13 15:52:57 -07:00
James R. Barlow 35162166c5 Fix UnboundLocalError in autobrew.py 2017-03-13 15:51:39 -07:00
James R. Barlow 107f6abcb1 Fix autobrew build issues - missing deps 2017-03-12 17:07:38 -07:00
James R. Barlow 760a939e7d Further autobrew tweaks 2017-03-11 17:05:26 -08:00
James R. Barlow 72660d0dec MacOS skip the one test that needs poppler, to save installing poppler 2017-03-11 17:03:26 -08:00
James R. Barlow 8444a8f211 before_deploy doesn’t run unless something is going to be deployed 2017-03-11 17:03:20 -08:00
James R. Barlow 4a1fec8328 Improvements to macOS test and work on homebrew tap autobrew
Squashed commits:
[3f06c1e] Try setting up homebrew tap autobuilding
[01532f1] Strict mode error in brew
2017-03-11 17:00:54 -08:00
James R. Barlow 42547f6017 Revert "Finalize Dockerfile move; unfortunately not supported by Docker Hub"
Unfortunately because of this issue
https://github.com/docker/hub-feedback/issues/292

Docker Hub effectively automatically changes directory to the location
of Dockerfile, meaning needed files are not in the right places, so
this has to be reverted.

This reverts commit 65c9a07dde.

[ci skip]
2017-03-10 22:49:08 -08:00
James R. Barlow 0ccf564f03 Revert "Move Dockerfiles out of root"
This reverts commit 3d3b3abc1b.
2017-03-10 22:46:03 -08:00
James R. Barlow 65c9a07dde Finalize Dockerfile move; unfortunately not supported by Docker Hub
Unfortunately because of this issue
https://github.com/docker/hub-feedback/issues/292

Docker Hub effectively automatically changes directory to the location
of Dockerfile, meaning needed files are not in the right places, so
this has to be reverted.
2017-03-10 22:45:31 -08:00
James R. Barlow 4700a19322 Move pipeline.svg out of root 2017-03-10 17:18:38 -08:00
James R. Barlow 3d3b3abc1b Move Dockerfiles out of root 2017-03-10 17:17:36 -08:00
James R. Barlow 7cd2770a13 Fix issue #137 - proportions of non-square resolution distorted
Distortion mainly affected —force-ocr
2017-02-26 17:13:16 -08:00
James R. Barlow 7b94129d9e v4.5 notes 2017-02-14 13:03:48 -08:00
James R. Barlow d1a0065ef8 Create test case for Form XObjects 2017-02-14 12:51:15 -08:00
James R. Barlow 5a817370fd Warn more strongly about —pdf-renderer tesseract until fix is widely propagated 2017-02-14 11:33:07 -08:00
James R. Barlow ab0a210763 Update dockerfile.tess4 yet again
Installing Tess4 PPA over Tess3 proved too much pain, so sever the link
between this and the jbarlow83/ocrmypdf image, starting each from
scratch. Also the complete set of language packs proves too much - the
build seems likely to fail when trying to install so many.
2017-02-14 11:32:05 -08:00
James R. Barlow 9f800736bc Fix running_in_docker() check failing on newer Docker
This test has to work to ensure spoof/tesseract_cache.py has a writable
directory to put cache into. Otherwise those tests fail.
2017-02-13 02:16:06 -08:00
James R. Barlow c9a83afad6 Improve batch processing examples 2017-02-13 02:14:32 -08:00
James R. Barlow 5e14274f10 pageinfo: learn to extract image information from Form XObjects 2017-02-11 16:48:59 -08:00
James R. Barlow 167470b4bd Re-fix Dockerfile.tess4
[ci skip]
2017-02-10 15:43:49 -08:00
James R. Barlow f06d3c2ec2 Fix tesseract 3.04 on tesseract 4 on image
[skip ci]
2017-02-10 08:38:22 -08:00
23 changed files with 658 additions and 104 deletions
+5
View File
@@ -4,6 +4,8 @@
venv*/ venv*/
pyvenv.cfg pyvenv.cfg
tasks.py tasks.py
.bash_history
.ruffus_history.sqlite
# Package building # Package building
*.egg-info/ *.egg-info/
@@ -27,8 +29,11 @@ htmlcov/
# Testing # Testing
log/ log/
/*.pdf /*.pdf
/*.qdf
*.ipynb
.ipynb_checkpoints/ .ipynb_checkpoints/
tests/cache/ tests/cache/
tests/output/ tests/output/
tests/resources/private/ tests/resources/private/
tmp/ tmp/
pdfbox-app*.jar
+26 -21
View File
@@ -1,4 +1,3 @@
sudo: required
dist: trusty dist: trusty
language: python language: python
cache: cache:
@@ -6,39 +5,45 @@ cache:
directories: directories:
- packages - packages
- tests/cache - tests/cache
- $HOME/Library/Caches/Homebrew
python: matrix:
- '3.4' include:
- '3.5' - os: linux
- '3.6' sudo: required
language: python
os: python: 3.4
- linux - os: linux
sudo: required
language: python
python: 3.5
- os: linux
sudo: required
language: python
python: 3.6
- os: osx
osx_image: xcode8
language: generic
before_cache: before_cache:
- rm -f $HOME/.cache/pip/log/debug.log - rm -f $HOME/.cache/pip/log/debug.log
before_install: before_install:
- sudo add-apt-repository ppa:vshn/ghostscript -y - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then bash .travis/linux_before_install.sh ; fi
- sudo add-apt-repository ppa:heyarje/libav-11 -y - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then bash .travis/osx_before_install.sh ; fi
- sudo apt-get update -qq
- sudo apt-get install -y ghostscript tesseract-ocr tesseract-ocr-deu tesseract-ocr-eng
tesseract-ocr-fra qpdf poppler-utils libavformat56 libavcodec56 libavutil54 libffi-dev
- pip install --upgrade pip
- mkdir -p packages
- "[ -f packages/unpaper_6.1-1.deb ] || wget -q https://dl.dropboxusercontent.com/u/28971240/unpaper_6.1-1.deb
-O packages/unpaper_6.1-1.deb"
- sudo dpkg -i packages/unpaper_6.1-1.deb
install: install:
- pip install . - pip3 install .
- pip install -r requirements.txt -r test_requirements.txt - pip3 install -r requirements.txt -r test_requirements.txt
script: script:
- mv ocrmypdf dont_import_this_ocrmypdf - mv ocrmypdf dont_import_this_ocrmypdf
- pytest - pytest
- mv dont_import_this_ocrmypdf ocrmypdf - mv dont_import_this_ocrmypdf ocrmypdf
after_success:
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then bash .travis/osx_brew.sh ; fi
# See https://www.appneta.com/blog/pypi-deployment-with-travis-ci/ for # See https://www.appneta.com/blog/pypi-deployment-with-travis-ci/ for
# steps to set up testpypi deploy for untagged builds if desired # steps to set up testpypi deploy for untagged builds if desired
@@ -51,5 +56,5 @@ deploy:
on: on:
branch: master branch: master
tags: true tags: true
condition: $TRAVIS_PYTHON_VERSION = "3.6" condition: $TRAVIS_PYTHON_VERSION == "3.6" && $TRAVIS_OS_NAME == "linux"
skip_upload_docs: true skip_upload_docs: true
+75
View File
@@ -0,0 +1,75 @@
from string import Template
from subprocess import run, PIPE
import re
recipe_template = Template("""
class Ocrmypdf < Formula
include Language::Python::Virtualenv
desc "Adds an OCR text layer to scanned PDF files"
homepage "https://github.com/jbarlow83/OCRmyPDF"
${ocrmypdf_url}
${ocrmypdf_sha256}
depends_on :python3
depends_on "pkg-config" => :build
depends_on "zlib"
depends_on "libffi"
depends_on "tesseract"
depends_on "ghostscript"
depends_on "unpaper"
depends_on "qpdf"
# For Pillow source install
depends_on "openjpeg"
depends_on "freetype"
depends_on "libpng"
${resources}
def install
ENV.append ["SETUPTOOLS_SCM_PRETEND_VERSION"], "v${ocrmypdf_version}"
ENV.each do |key, value|
puts "#{key}:#{value}"
end
virtualenv_install_with_resources
end
test do
# `test do` will create, run in and delete a temporary directory.
#
# The installed folder is not in the path, so use the entire path to any
# executables being tested: `system "#{bin}/program", "do", "something"`.
system "#{bin}/ocrmypdf", "--version"
end
end
""")
def main():
p = run(['poet', '--single', 'ocrmypdf'],
encoding='utf-8', stdout=PIPE, check=True)
ocrmypdf_lines = p.stdout.splitlines()
ocrmypdf_url = ocrmypdf_lines[1].strip()
ocrmypdf_sha256 = ocrmypdf_lines[2].strip()
ocrmypdf_version = re.search(
r'ocrmypdf-(.+)\.tar.*', ocrmypdf_url).group(1)
print(f"Autobrewing {ocrmypdf_version}")
p = run(['poet', '--resources', 'ocrmypdf'],
encoding='utf-8', stdout=PIPE, check=True)
poet_resources = p.stdout
# Remove the duplicate "ocrmypdf" resource block
all_resources = poet_resources.split('resource')
kept_resources = [block for block in all_resources if 'ocrmypdf' not in block]
resources = 'resource'.join(kept_resources)
with open('ocrmypdf.rb', 'w') as out:
out.write(recipe_template.substitute(**locals()))
if __name__ == '__main__':
main()
+24
View File
@@ -0,0 +1,24 @@
#!/bin/bash
set -euo pipefail
set -x
sudo add-apt-repository ppa:vshn/ghostscript -y
sudo add-apt-repository ppa:heyarje/libav-11 -y
sudo apt-get update -qq
sudo apt-get install -y \
ghostscript \
tesseract-ocr \
tesseract-ocr-deu \
tesseract-ocr-eng \
tesseract-ocr-fra \
qpdf \
poppler-utils \
libavformat56 \
libavcodec56 \
libavutil54 \
libffi-dev
pip install --upgrade pip
mkdir -p packages
[ -f packages/unpaper_6.1-1.deb ] || wget -q https://dl.dropboxusercontent.com/u/28971240/unpaper_6.1-1.deb -O packages/unpaper_6.1-1.deb
sudo dpkg -i packages/unpaper_6.1-1.deb
+16
View File
@@ -0,0 +1,16 @@
#!/bin/bash
set -euo pipefail
set -x
brew update
brew install openjpeg jbig2dec libtiff # image libraries
brew install qpdf
brew install ghostscript
brew install python3
brew install libxml2 libffi leptonica
brew install unpaper # optional
brew install tesseract
pip3 install --upgrade pip
pip3 install wheel
+8
View File
@@ -0,0 +1,8 @@
#!/bin/bash
set -euo pipefail
set -x
pip3 install homebrew-pypi-poet
python3 .travis/autobrew.py
brew audit ocrmypdf.rb
cat ocrmypdf.rb
+49 -6
View File
@@ -1,20 +1,63 @@
# OCRmyPDF # OCRmyPDF
# #
# VERSION 4.4.2 # VERSION 4.4.2
FROM jbarlow83/ocrmypdf:latest FROM ubuntu:16.10
MAINTAINER James R. Barlow <jim@purplerock.ca> MAINTAINER James R. Barlow <jim@purplerock.ca>
USER root RUN apt-get update && apt-get install -y --no-install-recommends \
software-properties-common python-software-properties \
python3-wheel \
python3-reportlab \
python3-venv \
ghostscript \
qpdf \
poppler-utils \
unpaper \
libffi-dev
RUN add-apt-repository ppa:alex-p/tesseract-ocr RUN add-apt-repository ppa:alex-p/tesseract-ocr
RUN apt-get update && apt-get install -y --no-install-recommends \ RUN apt-get update \
tesseract-ocr-all && apt-get autoremove -y \
&& apt-get install -y --no-install-recommends \
tesseract-ocr \
tesseract-ocr-eng \
tesseract-ocr-fra \
tesseract-ocr-deu \
tesseract-ocr-spa \
tesseract-ocr-por \
tesseract-ocr-ara \
tesseract-ocr-rus \
tesseract-ocr-chi-sim
RUN apt-get autoremove -y && apt-get clean -y RUN python3 -m venv --system-site-packages /appenv
# This installs the latest binary wheel instead of the code in the current
# folder. Installing from source will fail, apparently because cffi needs
# build-essentials (gcc) to do a source installation
# (i.e. "pip install ."). It's unclear to me why this is the case.
RUN . /appenv/bin/activate; \
pip install --upgrade pip \
&& pip install ocrmypdf
# Now copy the application in, mainly to get the test suite.
# Do this now to make the best use of Docker cache.
COPY . /application
RUN . /appenv/bin/activate; \
pip install -r /application/test_requirements.txt
# Remove the junk, including the source version of application since it was
# already installed
RUN rm -rf /tmp/* /var/tmp/* /root/* /application/ocrmypdf \
&& apt-get autoremove -y \
&& apt-get autoclean -y
RUN useradd docker \
&& mkdir /home/docker \
&& chown docker:docker /home/docker
USER docker USER docker
WORKDIR /home/docker
# Must use array form of ENTRYPOINT # Must use array form of ENTRYPOINT
# Non-array form does not append other arguments, because that is "intuitive" # Non-array form does not append other arguments, because that is "intuitive"
+11 -1
View File
@@ -60,7 +60,17 @@ Installation
Linux, UNIX, and macOS are supported. Windows is not directly supported but there is a Docker image available that runs on Windows. Linux, UNIX, and macOS are supported. Windows is not directly supported but there is a Docker image available that runs on Windows.
Users of Debian 9 or later or Ubuntu 16.10 or later may simply Users of Debian 9 or later or Ubuntu 16.10 or later may simply
``apt-get install ocrmypdf``.
.. code-block:: bash
apt-get install ocrmypdf
and macOS users may simply
.. code-block:: bash
brew tap jbarlow83/ocrmypdf
brew install ocrmypdf
For everyone else, `see our documentation <https://ocrmypdf.readthedocs.io/en/latest/installation.html>`_ for installation steps. For everyone else, `see our documentation <https://ocrmypdf.readthedocs.io/en/latest/installation.html>`_ for installation steps.
+22
View File
@@ -4,6 +4,28 @@ RELEASE NOTES
OCRmyPDF uses `semantic versioning <http://semver.org/>`_. OCRmyPDF uses `semantic versioning <http://semver.org/>`_.
v4.5.2
======
- Fix issue #147. ``--pdf-renderer tess4 --clean`` will produce an oversized page containing the original image in the bottom left corner, due to loss DPI information.
- Make "using Tesseract 4.0" warning less ominous
- Set up machinery for homebrew OCRmyPDF tap
v4.5.1
======
- Fix issue #137, proportions of images with a non-square pixel aspect ratio would be distorted in output for ``--force-ocr`` and some other combinations of flags
v4.5
====
- Exotic PDFs containing "Form XObjects" are now supported (issue #134; PDF reference manual 8.10), and images they contain are taken into account when determining the resolution for rasterizing
- The Tesseract 4 Docker image no longer includes all languages, because it took so long to build something would tend to fail
- OCRmyPDF now warns about using ``--pdf-renderer tesseract`` with Tesseract 3.04 or lower due to issues with Ghostscript corrupting the OCR text in these cases
v4.4.2 v4.4.2
====== ======
+20 -2
View File
@@ -10,11 +10,29 @@ Consider using the excellent `GNU Parallel <https://www.gnu.org/software/paralle
Both ``parallel`` and ``ocrmypdf`` will try to use all available processors. To maximize parallelism without overloading your system with processes, consider using ``parallel -j 2`` to limit parallel to running two jobs at once. Both ``parallel`` and ``ocrmypdf`` will try to use all available processors. To maximize parallelism without overloading your system with processes, consider using ``parallel -j 2`` to limit parallel to running two jobs at once.
This command will run all ocrmypdf all files named ``*.pdf`` in the current directory and write them to the previous created ``output/`` folder. This command will run all ocrmypdf all files named ``*.pdf`` in the current directory and write them to the previous created ``output/`` folder. It will not search subdirectories.
The ``--tag`` argument tells parallel to print the filename as a prefix whenever a message is printed, so that one can trace any errors to the file that produced them.
.. code-block:: bash .. code-block:: bash
parallel -j 2 ocrmypdf '{}' 'output/{}' ::: *.pdf parallel --tag -j 2 ocrmypdf '{}' 'output/{}' ::: *.pdf
Directory trees
---------------
This will walk through a directory tree and run OCR on all files in place, printing the output in a way that makes
.. code-block:: bash
find . --printf '%p' -name '*.pdf' -exec ocrmypdf '{}' '{}' \;
This only runs one ``ocrmypdf`` process at a time. This variation uses ``find`` to create a directory list and ``parallel`` to parallelize runs of ``ocrmypdf``, again updating files in place.
.. code-block:: bash
find . -name '*.pdf' | parallel --tag -j 2 ocrmypdf '{}' '{}'
Sample script Sample script
""""""""""""" """""""""""""
+17 -8
View File
@@ -10,6 +10,18 @@ Users of Debian 9 ("stretch") or later or Ubuntu 16.10 or later may simply
apt-get install ocrmypdf apt-get install ocrmypdf
Installing on macOS
-------------------
.. code-block:: bash
brew tap jbarlow83/ocrmypdf
brew install ocrmypdf
Users who previously installed OCRmyPDF on macOS may need to remove the ``pip`` based installation (``pip3 uninstall ocrmypdf``).
.. _Docker: .. _Docker:
Installing the Docker image Installing the Docker image
@@ -46,7 +58,8 @@ Assuming you have a Docker engine running, you can download one of the three ava
+-----------------------------+---------------------------------------------+---------------------------------------------------------------------------------+ +-----------------------------+---------------------------------------------+---------------------------------------------------------------------------------+
| ocrmypdf-polyglot | ``docker pull jbarlow83/ocrmypdf-polyglot`` | As above, with all available language packs. | | ocrmypdf-polyglot | ``docker pull jbarlow83/ocrmypdf-polyglot`` | As above, with all available language packs. |
+-----------------------------+---------------------------------------------+---------------------------------------------------------------------------------+ +-----------------------------+---------------------------------------------+---------------------------------------------------------------------------------+
| ocrmypdf-tess4 | ``docker pull jbarlow83/ocrmypdf-tess4`` | Latest ocrmypdf with Tesseract 4.00.00alpha and all language packs. | | ocrmypdf-tess4 | ``docker pull jbarlow83/ocrmypdf-tess4`` | Latest ocrmypdf with Tesseract 4.00.00alpha and English, French, German, |
| | | Spanish, Portuguese, Chinese Simplified, Arabic and Russian (the top 8). |
+-----------------------------+---------------------------------------------+---------------------------------------------------------------------------------+ +-----------------------------+---------------------------------------------+---------------------------------------------------------------------------------+
For example: For example:
@@ -102,8 +115,8 @@ Or in the wonderful `fish shell <https://fishshell.com/>`_:
funcsave ocrmypdf funcsave ocrmypdf
Installing on macOS Manual installation on macOS
------------------- ----------------------------
These instructions probably work on all macOS supported by Homebrew. These instructions probably work on all macOS supported by Homebrew.
@@ -174,10 +187,6 @@ No package is currently available for Ubuntu 16.04, but you can install the depe
tesseract-ocr \ tesseract-ocr \
qpdf \ qpdf \
python3-pip \ python3-pip \
python3-pil \
python3-pytest \
python3-reportlab \
python3-ruffus \
python3-cffi python3-cffi
If you wish install OCRmyPDF to the system Python, then install as follows (note this installs new packages If you wish install OCRmyPDF to the system Python, then install as follows (note this installs new packages
@@ -192,7 +201,7 @@ follow these steps.
.. code-block:: bash .. code-block:: bash
python3 -m venv --system-site-packages venv-ocrmypdf python3 -m venv venv-ocrmypdf
source venv-ocrmypdf/bin/activate source venv-ocrmypdf/bin/activate
pip3 install ocrmypdf pip3 install ocrmypdf
View File

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

+63 -13
View File
@@ -272,13 +272,18 @@ def check_options_output(options, log):
if options.pdf_renderer == 'auto': if options.pdf_renderer == 'auto':
options.pdf_renderer = 'hocr' options.pdf_renderer = 'hocr'
if options.pdf_renderer == 'tesseract' and \ if options.pdf_renderer in ('tesseract', 'tess4'):
tesseract.version() < '3.04.01' and \ if tesseract.version() < '3.05':
os.environ.get('OCRMYPDF_SHARP_TTF', '') != '1': log.warning(
log.warning( "The setting --pdf-renderer=tesseract is not recommend for "
"Your version of tesseract has problems with PDF output." " use with tesseract versions less than 3.05, because it "
" Some PDF viewers will fail to find searchable text.\n" " produces OCR text that is incompatible with Ghostscript and "
"--pdf-renderer=tesseract is not recommended.") " some other software.")
elif tesseract.version() == '4.00.00alpha':
log.warning(
"The setting --pdf-renderer={tesseract,tess4} is not"
" recommended for builds of tesseract 4.00.00alpha older than"
" February 2017. Make sure you are using a recent build.")
if options.debug_rendering and options.pdf_renderer == 'tesseract': if options.debug_rendering and options.pdf_renderer == 'tesseract':
log.info( log.info(
@@ -354,9 +359,6 @@ def check_options(options, log):
sys.exit(ExitCode.missing_dependency) sys.exit(ExitCode.missing_dependency)
# ---------- # ----------
# Logging # Logging
@@ -479,10 +481,58 @@ def traverse_ruffus_exception(e_args, options, log):
return traverse_ruffus_exception(exc, options, log) return traverse_ruffus_exception(exc, options, log)
def check_closed_streams(options):
"""Work around Python issue with multiprocessing forking on closed streams
https://bugs.python.org/issue28326
Attempting to a fork/exec a new Python process when any of std{in,out,err}
are closed or not flushable for some reason may raise an exception.
Fix this by opening devnull if the handle seems to be closed. Do this
globally to avoid tracking places all places that fork.
Seems to be specific to multiprocessing.Process not all Python process
forkers.
The error actually occurs when the stream object is not flushable,
but replacing an open stream object that is not flushable with
/dev/null is a bad idea since it will create a silent failure. Replacing
a closed handle with /dev/null seems safe.
"""
if sys.stderr is None:
sys.stderr = open(os.devnull, 'w')
if sys.stdin is None:
if options.input_file == '-':
print("Trying to read from stdin but stdin seems closed",
file=sys.stderr)
return False
sys.stdin = open(os.devnull, 'r')
if sys.stdout is None:
if options.output_file == '-':
# Can't replace stdout if the user is piping
# If this case can even happen, it must be some kind of weird
# stream.
print(textwrap.dedent("""\
Output was set to stdout '-' but the stream attached to
stdout does not support the flush() system call. This
will fail."""), file=sys.stderr)
return False
sys.stdout = open(os.devnull, 'w')
return True
def run_pipeline(): def run_pipeline():
options = parser.parse_args() options = parser.parse_args()
options.verbose_abbreviated_path = 1 options.verbose_abbreviated_path = 1
if not check_closed_streams(options):
return ExitCode.bad_args
_log, _log_mutex = proxy_logger.make_shared_logger_and_proxy( _log, _log_mutex = proxy_logger.make_shared_logger_and_proxy(
logging_factory, __name__, [None, options.verbose]) logging_factory, __name__, [None, options.verbose])
_log.debug('ocrmypdf ' + VERSION) _log.debug('ocrmypdf ' + VERSION)
@@ -523,9 +573,9 @@ def run_pipeline():
file.""")) file."""))
return ExitCode.bad_args return ExitCode.bad_args
elif not is_file_writable(options.output_file): elif not is_file_writable(options.output_file):
_log.error(textwrap.dedent("""\ _log.error(textwrap.dedent("""\
Cutput file location is not writable.""")) Cutput file location is not writable."""))
return ExitCode.file_access_error return ExitCode.file_access_error
manager = JobContextManager() manager = JobContextManager()
manager.register('JobContext', JobContext) manager.register('JobContext', JobContext)
+21 -1
View File
@@ -2,12 +2,32 @@
# © 2015 James R. Barlow: github.com/jbarlow83 # © 2015 James R. Barlow: github.com/jbarlow83
from tempfile import NamedTemporaryFile from tempfile import NamedTemporaryFile
from subprocess import Popen, PIPE, STDOUT, check_call from subprocess import Popen, PIPE, STDOUT, check_call, CalledProcessError, \
check_output
from shutil import copy from shutil import copy
from functools import lru_cache
from . import get_program from . import get_program
from ..pdfa import SRGB_ICC_PROFILE from ..pdfa import SRGB_ICC_PROFILE
@lru_cache(maxsize=1)
def version():
args_gs = [
get_program('gs'),
'--version'
]
try:
version = check_output(
args_gs, close_fds=True, universal_newlines=True,
stderr=STDOUT)
except CalledProcessError as e:
print("Could not find Ghostscript executable on system PATH.",
file=sys.stderr)
raise MissingDependencyError from e
return version.strip()
def rasterize_pdf(input_file, output_file, xres, yres, raster_device, log, def rasterize_pdf(input_file, output_file, xres, yres, raster_device, log,
pageno=1): pageno=1):
with NamedTemporaryFile(delete=True) as tmp: with NamedTemporaryFile(delete=True) as tmp:
+2 -1
View File
@@ -78,7 +78,8 @@ def run(input_file, output_file, dpi, log, mode_args):
raise e from e raise e from e
else: else:
log.debug(stdout) log.debug(stdout)
Image.open(output_pnm.name).save(output_file) # unpaper sets dpi to 72
Image.open(output_pnm.name).save(output_file, dpi=(dpi, dpi))
def deskew(input_file, output_file, dpi, log): def deskew(input_file, output_file, dpi, log):
+130 -37
View File
@@ -89,13 +89,13 @@ def _is_unit_square(shorthand):
pairwise = zip(values, UNIT_SQUARE) pairwise = zip(values, UNIT_SQUARE)
return all([isclose(a, b, rel_tol=1e-3) for a, b in pairwise]) return all([isclose(a, b, rel_tol=1e-3) for a, b in pairwise])
RasterSettings = namedtuple('RasterSettings', XobjectSettings = namedtuple('XobjectSettings',
['name', 'shorthand', 'stack_depth']) ['name', 'shorthand', 'stack_depth'])
InlineSettings = namedtuple('InlineSettings', InlineSettings = namedtuple('InlineSettings',
['settings', 'shorthand', 'stack_depth']) ['settings', 'shorthand', 'stack_depth'])
ContentsInfo = namedtuple('ContentsInfo', ['raster_settings', 'inline_images']) ContentsInfo = namedtuple('ContentsInfo', ['xobject_settings', 'inline_images'])
def _normalize_stack(operations): def _normalize_stack(operations):
@@ -114,7 +114,7 @@ def _normalize_stack(operations):
yield (operands, command) yield (operands, command)
def _interpret_contents(contentstream): def _interpret_contents(contentstream, initial_shorthand=UNIT_SQUARE):
"""Interpret the PDF content stream """Interpret the PDF content stream
The stack represents the state of the PDF graphics stack. We are only The stack represents the state of the PDF graphics stack. We are only
@@ -139,8 +139,8 @@ def _interpret_contents(contentstream):
operations = contentstream.operations operations = contentstream.operations
stack = [] stack = []
ctm = _matrix_from_shorthand(UNIT_SQUARE) ctm = _matrix_from_shorthand(initial_shorthand)
image_raster_settings = [] xobject_settings = []
inline_images = [] inline_images = []
for n, op in enumerate(_normalize_stack(operations)): for n, op in enumerate(_normalize_stack(operations)):
@@ -161,10 +161,10 @@ def _interpret_contents(contentstream):
_matrix_from_shorthand(operands), ctm) _matrix_from_shorthand(operands), ctm)
elif command == b'Do': elif command == b'Do':
image_name = operands[0] image_name = operands[0]
raster = RasterSettings( settings = XobjectSettings(
name=image_name, shorthand=_shorthand_from_matrix(ctm), name=image_name, shorthand=_shorthand_from_matrix(ctm),
stack_depth=len(stack)) stack_depth=len(stack))
image_raster_settings.append(raster) xobject_settings.append(settings)
elif command == b'INLINE IMAGE': elif command == b'INLINE IMAGE':
settings = operands['settings'] settings = operands['settings']
inline = InlineSettings( inline = InlineSettings(
@@ -173,7 +173,7 @@ def _interpret_contents(contentstream):
inline_images.append(inline) inline_images.append(inline)
return ContentsInfo( return ContentsInfo(
raster_settings=image_raster_settings, xobject_settings=xobject_settings,
inline_images=inline_images) inline_images=inline_images)
@@ -241,8 +241,8 @@ def _get_dpi(ctm_shorthand, image_size):
return (dpi_w, dpi_h) return (dpi_w, dpi_h)
def _find_page_inline_images(page, pageinfo, contentsinfo): def _find_inline_images(contentsinfo):
"Find inline images on the page" "Find inline images in the contentstream"
for n, inline in enumerate(contentsinfo.inline_images): for n, inline in enumerate(contentsinfo.inline_images):
image = {} image = {}
@@ -272,20 +272,44 @@ def _find_page_inline_images(page, pageinfo, contentsinfo):
yield image yield image
def _find_page_regular_images(page, pageinfo, contentsinfo): def _image_xobjects(container):
"Find images stored in XObject resources" """Search for all XObject-based images in the container
try: Usually the container is a page, but it could also be a Form XObject
page['/Resources']['/XObject'] that contains images. Filter out the Form XObjects which are dealt with
except KeyError: elsewhere.
Generate a sequence of tuples (image, xobj container), where container,
where xobj is the name of the object and image is the object itself,
since the object does not know its own name.
"""
if '/Resources' not in container:
return return
for xobj in page['/Resources']['/XObject']: resources = container['/Resources']
# PyPDF2 returns the keys as an iterator if '/XObject' not in resources:
pdfimage = page['/Resources']['/XObject'][xobj] return
if pdfimage['/Subtype'] != '/Image': for xobj in resources['/XObject']:
continue candidate = resources['/XObject'][xobj]
if candidate['/Subtype'] == '/Image':
image = candidate
yield (image, xobj)
def _find_regular_images(container, contentsinfo):
"""Find images stored in the container's /Resources /XObject
Usually the container is a page, but it could also be a Form XObject
that contains images.
Generates images with their DPI at time of drawing.
"""
for pdfimage, xobj in _image_xobjects(container):
image = {} image = {}
image['name'] = str(xobj) image['name'] = xobj
image['width'] = pdfimage['/Width'] image['width'] = pdfimage['/Width']
image['height'] = pdfimage['/Height'] image['height'] = pdfimage['/Height']
if '/BitsPerComponent' in pdfimage: if '/BitsPerComponent' in pdfimage:
@@ -329,12 +353,12 @@ def _find_page_regular_images(page, pageinfo, contentsinfo):
image['dpi_w'] = image['dpi_h'] = 0 image['dpi_w'] = image['dpi_h'] = 0
for raster in contentsinfo.raster_settings: for xobj in contentsinfo.xobject_settings:
# Loop in case the same image is display multiple times on a page # Loop in case the same image is display multiple times on a page
if raster.name != image['name']: if xobj.name != image['name']:
continue continue
if raster.stack_depth == 0 and _is_unit_square(raster.shorthand): if xobj.stack_depth == 0 and _is_unit_square(xobj.shorthand):
# At least one PDF in the wild (and test suite) draws an image # At least one PDF in the wild (and test suite) draws an image
# when the graphics stack depth is 0, meaning that the image # when the graphics stack depth is 0, meaning that the image
# gets drawn into a square of 1x1 PDF units (or 1/72", # gets drawn into a square of 1x1 PDF units (or 1/72",
@@ -343,7 +367,7 @@ def _find_page_regular_images(page, pageinfo, contentsinfo):
continue continue
dpi_w, dpi_h = _get_dpi( dpi_w, dpi_h = _get_dpi(
raster.shorthand, (image['width'], image['height'])) xobj.shorthand, (image['width'], image['height']))
# When image is used multiple times take the highest DPI it is # When image is used multiple times take the highest DPI it is
# rendered at # rendered at
@@ -358,9 +382,85 @@ def _find_page_regular_images(page, pageinfo, contentsinfo):
yield image yield image
def _find_page_images(page, pageinfo, contentsinfo): def _find_form_xobject_images(pdf, container, contentsinfo):
yield from _find_page_inline_images(page, pageinfo, contentsinfo) """Find any images that are in Form XObjects in the container
yield from _find_page_regular_images(page, pageinfo, contentsinfo)
The container may be a page, or a parent Form XObject.
"""
if '/Resources' not in container:
return
resources = container['/Resources']
if '/XObject' not in resources:
return
for xobj in resources['/XObject']:
candidate = resources['/XObject'][xobj]
if candidate['/Subtype'] != '/Form':
continue
form_xobject = candidate
for settings in contentsinfo.xobject_settings:
if settings.name != xobj:
continue
# Find images once for each time this Form XObject is drawn.
# This could be optimized to cache the multiple drawing events
# but in practice both Form XObjects and multiple drawing of the
# same object are both very rare.
ctm_shorthand = settings.shorthand
yield from _find_images(pdf, form_xobject, ctm_shorthand)
def _find_images(pdf, container, shorthand=None):
"""Find all individual instances of images drawn in the container
Usually the container is a page, but it may also be a Form XObject.
On a typical page images are stored inline or as regular images
in an XObject.
Form XObjects may include inline images, XObject images,
and recursively, other Form XObjects; and also vector drawing commands.
Every instance of an image being drawn somewhere is flattened and
treated as a unique image, since if the same image is drawn multiple times
on one page it may be drawn at differing resolutions, and our objective
is to find the resolution at which the page can be rastered without
downsampling.
"""
if container.get('/Type') == '/Page':
# For a /Page the content stream is attached to the page's /Contents
page = container
contentstream = pypdf.pdf.ContentStream(page.getContents(), pdf)
initial_shorthand = shorthand or UNIT_SQUARE
elif container.get('/Type') == '/XObject' and \
container['/Subtype'] == '/Form':
# For a Form XObject that content stream is attached to the XObject
contentstream = pypdf.pdf.ContentStream(container, pdf)
# Set the CTM to the state it was when the "Do" operator was
# encountered that is drawing this instance of the Form XObject
ctm = _matrix_from_shorthand(shorthand or UNIT_SQUARE)
# A Form XObject may provide its own matrix to map form space into
# user space. Get this if one exists
form_matrix = _matrix_from_shorthand(
container.get('/Matrix', UNIT_SQUARE))
# Concatenate form matrix with CTM to ensure CTM is correct for
# drawing this instance of the XObject
ctm = matrix_mult(form_matrix, ctm)
initial_shorthand = _shorthand_from_matrix(ctm)
else:
return
contentsinfo = _interpret_contents(contentstream, initial_shorthand)
yield from _find_inline_images(contentsinfo)
yield from _find_regular_images(container, contentsinfo)
yield from _find_form_xobject_images(pdf, container, contentsinfo)
def _page_has_text(pdf, page): def _page_has_text(pdf, page):
@@ -405,15 +505,8 @@ def _pdf_get_pageinfo(infile, pageno: int):
except KeyError: except KeyError:
pageinfo['rotate'] = 0 pageinfo['rotate'] = 0
try: pageinfo['images'] = [im for im in
contentstream = pypdf.pdf.ContentStream(page.getContents(), pdf) _find_images(pdf, page)]
except AttributeError as e:
return pageinfo
contentsinfo = _interpret_contents(contentstream)
pageinfo['images'] = [im for im in _find_page_images(
page, pageinfo, contentsinfo)]
if pageinfo['images']: if pageinfo['images']:
xres = max(image['dpi_w'] for image in pageinfo['images']) xres = max(image['dpi_w'] for image in pageinfo['images'])
yres = max(image['dpi_h'] for image in pageinfo['images']) yres = max(image['dpi_h'] for image in pageinfo['images'])
+10 -5
View File
@@ -555,16 +555,21 @@ def select_image_layer(
re_symlink(page_pdf, output_file, log) re_symlink(page_pdf, output_file, log)
else: else:
pageinfo = get_pageinfo(image, context) pageinfo = get_pageinfo(image, context)
dpi = get_page_dpi(pageinfo, options)
dpi = float(dpi[0]), float(dpi[1]) # We rasterize a square DPI version of each page because most image
layout_fun = img2pdf.get_fixed_dpi_layout_fun(dpi) # 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 at this stage back to non-square DPI to more
# closely resemble the input, except that the hocr renderer does not
# understand non-square DPI. The tess4 renderer would be fine.
dpi = get_page_square_dpi(pageinfo, options)
layout_fun = img2pdf.get_fixed_dpi_layout_fun((dpi, dpi))
with open(image, 'rb') as imfile, \ with open(image, 'rb') as imfile, \
open(output_file, 'wb') as pdf: open(output_file, 'wb') as pdf:
rawdata = imfile.read()
log.debug('{:4d}: convert'.format(page_number(page_pdf))) log.debug('{:4d}: convert'.format(page_number(page_pdf)))
img2pdf.convert( img2pdf.convert(
rawdata, with_pdfrw=False, imfile, with_pdfrw=False,
layout_fun=layout_fun, outputstream=pdf) layout_fun=layout_fun, outputstream=pdf)
log.debug('{:4d}: convert done'.format(page_number(page_pdf))) log.debug('{:4d}: convert done'.format(page_number(page_pdf)))
+22 -3
View File
@@ -22,10 +22,21 @@ def is_linux():
return platform.system() == 'Linux' return platform.system() == 'Linux'
@pytest.helpers.register
def is_macos():
return platform.system() == 'Darwin'
@pytest.helpers.register @pytest.helpers.register
def running_in_docker(): def running_in_docker():
# Docker creates a file named /.dockerinit # Docker creates a file named /.dockerenv (newer versions) or
return os.path.exists('/.dockerinit') # /.dockerinit (older) -- this is undocumented, not an offical test
return os.path.exists('/.dockerenv') or os.path.exists('/.dockerinit')
@pytest.helpers.register
def running_in_travis():
return os.environ.get('TRAVIS') == 'true'
TESTS_ROOT = os.path.abspath(os.path.dirname(__file__)) TESTS_ROOT = os.path.abspath(os.path.dirname(__file__))
@@ -90,7 +101,7 @@ def check_ocrmypdf(input_file, output_file, *args, env=None):
p, out, err = run_ocrmypdf(input_file, output_file, *args, env=env) p, out, err = run_ocrmypdf(input_file, output_file, *args, env=env)
#print(err) # ensure py.test collects the output, use -s to view #print(err) # ensure py.test collects the output, use -s to view
assert p.returncode == 0 assert p.returncode == 0, "<stderr>\n" + err + "\n</stderr>"
assert os.path.exists(str(output_file)), "Output file not created" assert os.path.exists(str(output_file)), "Output file not created"
assert os.stat(str(output_file)).st_size > 100, "PDF too small or empty" assert os.stat(str(output_file)).st_size > 100, "PDF too small or empty"
assert out == "", \ assert out == "", \
@@ -114,3 +125,11 @@ def run_ocrmypdf(input_file, output_file, *args, env=None):
#print(err) #print(err)
return p, out, err return p, out, err
@pytest.helpers.register
def first_page_dimensions(pdf):
from ocrmypdf import pageinfo
info = pageinfo.pdf_get_all_pageinfo(str(pdf))
page0 = info[0]
return (page0['width_inches'], page0['height_inches'])
+3
View File
@@ -64,6 +64,9 @@ under the terms of the license in LICENSE.rst.
* - epson.pdf * - epson.pdf
- @lowesjam - @lowesjam
- 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)
* - formxobject.pdf
- @jbarlow83
- hand-crafted exotic PDF containing an image inside a Form XObject
* - francais.pdf * - francais.pdf
- @jbarlow83 - @jbarlow83
- a page containing French accents (diacritics) - a page containing French accents (diacritics)
Binary file not shown.
+101 -1
View File
@@ -12,6 +12,7 @@ import PyPDF2 as pypdf
from ocrmypdf.exceptions import ExitCode from ocrmypdf.exceptions import ExitCode
from ocrmypdf import leptonica from ocrmypdf import leptonica
from ocrmypdf.pdfa import file_claims_pdfa from ocrmypdf.pdfa import file_claims_pdfa
from ocrmypdf.exec import ghostscript
check_ocrmypdf = pytest.helpers.check_ocrmypdf check_ocrmypdf = pytest.helpers.check_ocrmypdf
@@ -169,6 +170,13 @@ def test_preserve_metadata(spoof_tesseract_noop, output_type,
@pytest.mark.skipif( @pytest.mark.skipif(
pytest.helpers.is_linux() and not pytest.helpers.running_in_docker(), pytest.helpers.is_linux() and not pytest.helpers.running_in_docker(),
reason="likely to fail if Linux locale is not configured correctly") reason="likely to fail if Linux locale is not configured correctly")
@pytest.mark.skipif(
pytest.helpers.is_macos() and pytest.helpers.running_in_travis(),
reason="save Travis the trouble of installing poppler")
@pytest.mark.xfail(
ghostscript.version() == '9.21',
reason="gs 9.21 has a regression that affects this"
)
@pytest.mark.parametrize("output_type", [ @pytest.mark.parametrize("output_type", [
'pdfa', 'pdf' 'pdfa', 'pdf'
]) ])
@@ -188,7 +196,7 @@ def test_override_metadata(spoof_tesseract_noop, output_type, resources,
'--output-type', output_type, '--output-type', output_type,
env=spoof_tesseract_noop) env=spoof_tesseract_noop)
assert p.returncode == ExitCode.ok assert p.returncode == ExitCode.ok, err
pdf = str(outpdf) pdf = str(outpdf)
@@ -409,10 +417,14 @@ def test_force_ocr_on_pdf_with_no_images(spoof_tesseract_crash, resources,
assert not os.path.exists(no_outpdf) assert not os.path.exists(no_outpdf)
@pytest.mark.skipif(
pytest.helpers.is_macos() and pytest.helpers.running_in_travis(),
reason="takes too long to install language packs in Travis macOS homebrew")
def test_french(spoof_tesseract_cache, resources, outpdf): def test_french(spoof_tesseract_cache, resources, outpdf):
p, out, err = run_ocrmypdf( p, out, err = run_ocrmypdf(
resources / 'francais.pdf', outpdf, '-l', 'fra', resources / 'francais.pdf', outpdf, '-l', 'fra',
env=spoof_tesseract_cache) env=spoof_tesseract_cache)
print(os.environ)
assert p.returncode == ExitCode.ok, \ assert p.returncode == ExitCode.ok, \
"This test may fail if Tesseract language packs are missing" "This test may fail if Tesseract language packs are missing"
@@ -564,6 +576,46 @@ def test_non_square_resolution(renderer, spoof_tesseract_cache,
assert in_pageinfo[0]['yres'] == out_pageinfo[0]['yres'] assert in_pageinfo[0]['yres'] == out_pageinfo[0]['yres']
@pytest.mark.skipif(sys.version_info < (3, 5), reason="needs math.isclose")
@pytest.mark.parametrize('renderer', [
'hocr',
'tesseract'
])
def test_convert_to_square_resolution(renderer, spoof_tesseract_cache,
resources, outpdf):
from math import isclose
# Confirm input image is non-square resolution
in_pageinfo = pdf_get_all_pageinfo(str(resources / 'aspect.pdf'))
assert in_pageinfo[0]['xres'] != in_pageinfo[0]['yres']
# --force-ocr requires means forced conversion to square resolution
check_ocrmypdf(
resources / 'aspect.pdf', outpdf,
'--force-ocr',
'--pdf-renderer', renderer, env=spoof_tesseract_cache)
out_pageinfo = pdf_get_all_pageinfo(str(outpdf))
in_p0, out_p0 = in_pageinfo[0], out_pageinfo[0]
# Resolution show now be equal
assert out_p0['xres'] == out_p0['yres']
# Page size should match input page size
assert isclose(in_p0['width_inches'],
out_p0['width_inches'])
assert isclose(in_p0['height_inches'],
out_p0['height_inches'])
# Because we rasterized the page to produce a new image, it should occupy
# the entire page
out_im_w = out_p0['images'][0]['width'] / out_p0['images'][0]['dpi_w']
out_im_h = out_p0['images'][0]['height'] / out_p0['images'][0]['dpi_h']
assert isclose(out_p0['width_inches'], out_im_w)
assert isclose(out_p0['height_inches'], out_im_h)
def test_image_to_pdf(spoof_tesseract_noop, resources, outpdf): def test_image_to_pdf(spoof_tesseract_noop, resources, outpdf):
check_ocrmypdf( check_ocrmypdf(
resources / 'LinnSequencer.jpg', outpdf, '--image-dpi', '200', resources / 'LinnSequencer.jpg', outpdf, '--image-dpi', '200',
@@ -614,6 +666,23 @@ def test_stdout(spoof_tesseract_noop, ocrmypdf_exec, resources, outpdf):
assert qpdf.check(output_file, log=None) assert qpdf.check(output_file, log=None)
def test_closed_streams(spoof_tesseract_noop, ocrmypdf_exec, resources, outpdf):
input_file = str(resources / 'francais.pdf')
output_file = str(outpdf)
def evil_closer():
os.close(0)
os.close(1)
p_args = ocrmypdf_exec + [input_file, output_file]
p = Popen(
p_args, close_fds=True, stdout=None, stderr=PIPE, stdin=None,
env=spoof_tesseract_noop, preexec_fn=evil_closer)
out, err = p.communicate()
print(err.decode())
assert p.returncode == ExitCode.ok
def test_masks(spoof_tesseract_noop, resources, outpdf): def test_masks(spoof_tesseract_noop, resources, outpdf):
check_ocrmypdf(resources / 'masks.pdf', outpdf, env=spoof_tesseract_noop) check_ocrmypdf(resources / 'masks.pdf', outpdf, env=spoof_tesseract_noop)
@@ -742,3 +811,34 @@ THIS FILE IS INVALID
'--tesseract-config', str(cfg_file)) '--tesseract-config', str(cfg_file))
assert "parameter not found" in err, "No error message" assert "parameter not found" in err, "No error message"
assert p.returncode == ExitCode.invalid_config assert p.returncode == ExitCode.invalid_config
def test_form_xobject(spoof_tesseract_noop, resources, outpdf):
check_ocrmypdf(resources / 'formxobject.pdf', outpdf,
'--force-ocr',
env=spoof_tesseract_noop)
@pytest.mark.skipif(sys.version_info < (3, 5), reason="needs math.isclose")
@pytest.mark.parametrize('renderer', [
'hocr',
'tesseract',
])
def test_pagesize_consistency(renderer, resources, outpdf):
from math import isclose
first_page_dimensions = pytest.helpers.first_page_dimensions
infile = resources / 'linn.pdf'
before_dims = first_page_dimensions(infile)
check_ocrmypdf(
infile,
outpdf, '--pdf-renderer', renderer,
'--clean', '--deskew', '--remove-background', '--clean-final')
after_dims = first_page_dimensions(outpdf)
assert isclose(before_dims[0], after_dims[0])
assert isclose(before_dims[1], after_dims[1])
+7
View File
@@ -101,3 +101,10 @@ def test_jpeg(resources, outdir):
assert pdfimage['enc'] == 'jpeg' assert pdfimage['enc'] == 'jpeg'
assert (pdfimage['dpi_w'] - 150) < 1e-5 assert (pdfimage['dpi_w'] - 150) < 1e-5
def test_form_xobject(resources):
filename = resources / 'formxobject.pdf'
pdfinfo = pageinfo.pdf_get_all_pageinfo(str(filename))
pdfimage = pdfinfo[0]['images'][0]
assert pdfimage['width'] == 50
+26 -5
View File
@@ -4,19 +4,40 @@
import pytest import pytest
from ocrmypdf.exceptions import ExitCode from ocrmypdf.exceptions import ExitCode
from ocrmypdf.exec import tesseract from ocrmypdf.exec import tesseract
from ocrmypdf import pageinfo
import sys
# Skip all tests in this file if not tesseract 4 # Skip all tests in this file if not tesseract 4
pytestmark = pytest.mark.skipif(not tesseract.v4(), pytestmark = pytest.mark.skipif(
reason="tesseract 4.0 required") not (tesseract.v4() and tesseract.has_textonly_pdf()),
reason="tesseract 4.0 with textonly_pdf feature required")
check_ocrmypdf = pytest.helpers.check_ocrmypdf
run_ocrmypdf = pytest.helpers.run_ocrmypdf
spoof = pytest.helpers.spoof
@pytest.mark.skipif(not tesseract.has_textonly_pdf(),
reason="requires textonly_pdf feature")
def test_textonly_pdf(resources, outdir): def test_textonly_pdf(resources, outdir):
pytest.helpers.check_ocrmypdf( check_ocrmypdf(
resources / 'linn.pdf', resources / 'linn.pdf',
outdir / 'linn_textonly.pdf', '--pdf-renderer', 'tess4') outdir / 'linn_textonly.pdf', '--pdf-renderer', 'tess4')
@pytest.mark.skipif(sys.version_info < (3, 5), reason="needs math.isclose")
def test_pagesize_consistency_tess4(resources, outpdf):
from math import isclose
infile = resources / 'linn.pdf'
before_dims = pytest.helpers.first_page_dimensions(infile)
check_ocrmypdf(
infile,
outpdf, '--pdf-renderer', 'tess4',
'--clean', '--deskew', '--remove-background', '--clean-final')
after_dims = pytest.helpers.first_page_dimensions(outpdf)
assert isclose(before_dims[0], after_dims[0])
assert isclose(before_dims[1], after_dims[1])