Compare commits

...
36 Commits
Author SHA1 Message Date
James R. Barlow a1033cdc64 osx_brew: show output before letting “brew audit” check it 2017-04-18 23:38:07 -07:00
James R. Barlow 204336e1a5 Move release notes into the rest of documentation 2017-04-18 18:26:31 -07:00
James R. Barlow 8954e6c3b9 v4.5.4 Update release notes 2017-04-18 18:09:15 -07:00
James R. Barlow fee22b6b0b Fix #151, cannot write mode P as JPEG
all(<empty generator>) is True.
2017-04-18 18:08:45 -07:00
James R. Barlow 2b82c31b85 Update documentation 2017-04-18 18:07:19 -07:00
James R. Barlow 9a4813089c autobrew: remove homebrew dependency “zlib”, causes audit failure 2017-04-18 15:28:30 -07:00
James R. Barlow 554fcc8b9d Add test case for #152 2017-04-18 15:20:25 -07:00
Tomandjbarlow83 345256ee99 Fix --skip-big when there are no images in pdf (#152)
* fixed skip-big when there are no images in pdf

* added only_text pdf

* updated only_text pdf

* Delete only_text.pdf
2017-04-18 15:19:24 -07:00
James R. Barlow 58d1042147 v4.5.3 release notes update 2017-03-29 23:50:31 -07:00
James R. Barlow 7b7e3a3e03 Enable lossless reconstruction for —pdf-renderer tess4 where appropriate 2017-03-29 23:44:12 -07:00
James R. Barlow 1e7fbd4202 Fix issues with —pdf-renderer tess4 page skipping
If tess4 renderer needed to skip OCR on a page it would end up
duplicating the page contents onto the new page, rather than creating
a blank OCR layer and placing it on the output page. This created
duplicated content in output files.
2017-03-29 23:43:26 -07:00
James R. Barlow 4a9e9e9db2 v4.5.3 release notes 2017-03-29 13:19:34 -07:00
James R. Barlow 88ef2718f1 Reject high Unicode metadata at command line
Ghostscript 9.21 does not seem to accept Unicode above U+FFFF. Previous
versions did, but it now exits with a rangecheck error (-15).

Reject on the command line for now. Complete fix would also need to
check input PDF’s metadata.
2017-03-28 11:08:38 -07:00
James R. Barlow e71e8ca3ad Workaround for GS VMerror -25 bug
Avoid inserting docinfo keys that would be translated to null strings,
to avoid running afoul of
https://bugs.ghostscript.com/show_bug.cgi?id=697684
2017-03-28 11:05:43 -07:00
James R. Barlow 45e9257d6e Don’t use filename “pdfa_def.ps” for GS file
At recommendation of Artifex people, don’t use the filename pdfa_def.ps
because if given without a full path Ghostscript will prioritize
searching its own resources over the current directory, and will pick up
 the example file pdfa_def.ps instead.  We used the full path anyway,
but this is a prudent defensive change to avoid odd Ghostscript behavior.
2017-03-28 11:02:46 -07:00
James R. Barlow 2954e72652 Some examples of Ghostscript and Tesseract warnings/errors were not tagged properly 2017-03-28 10:59:53 -07:00
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
22 changed files with 708 additions and 163 deletions
+5
View File
@@ -4,6 +4,8 @@
venv*/
pyvenv.cfg
tasks.py
.bash_history
.ruffus_history.sqlite
# Package building
*.egg-info/
@@ -27,8 +29,11 @@ htmlcov/
# Testing
log/
/*.pdf
/*.qdf
*.ipynb
.ipynb_checkpoints/
tests/cache/
tests/output/
tests/resources/private/
tmp/
pdfbox-app*.jar
+26 -21
View File
@@ -1,4 +1,3 @@
sudo: required
dist: trusty
language: python
cache:
@@ -6,39 +5,45 @@ cache:
directories:
- packages
- tests/cache
- $HOME/Library/Caches/Homebrew
python:
- '3.4'
- '3.5'
- '3.6'
os:
- linux
matrix:
include:
- os: linux
sudo: required
language: python
python: 3.4
- 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:
- rm -f $HOME/.cache/pip/log/debug.log
before_install:
- 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
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then bash .travis/linux_before_install.sh ; fi
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then bash .travis/osx_before_install.sh ; fi
install:
- pip install .
- pip install -r requirements.txt -r test_requirements.txt
- pip3 install .
- pip3 install -r requirements.txt -r test_requirements.txt
script:
- mv ocrmypdf dont_import_this_ocrmypdf
- pytest
- 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
# steps to set up testpypi deploy for untagged builds if desired
@@ -51,5 +56,5 @@ deploy:
on:
branch: master
tags: true
condition: $TRAVIS_PYTHON_VERSION = "3.6"
condition: $TRAVIS_PYTHON_VERSION == "3.6" && $TRAVIS_OS_NAME == "linux"
skip_upload_docs: true
+74
View File
@@ -0,0 +1,74 @@
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 "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
cat ocrmypdf.rb
brew audit ocrmypdf.rb
+12 -2
View File
@@ -35,7 +35,7 @@ Main features
- Supports more than `100 languages <https://github.com/tesseract-ocr/tessdata>`_ recognized by Tesseract
- Battle-tested on thousands of PDFs, a test suite and continuous integration
For details: please consult the `release notes <RELEASE_NOTES.rst>`_.
For details: please consult the `documentation <https://ocrmypdf.readthedocs.io/en/latest/>`_.
Motivation
----------
@@ -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.
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.
+7 -4
View File
@@ -11,14 +11,18 @@ be searched.
PDFs are the best format for scanned documents. Unfortunately, PDFs can be difficult to work with. OCRmyPDF makes it easy to apply image processing and OCR to existing PDFs.
Contents:
.. toctree::
:maxdepth: 2
:maxdepth: 1
introduction
release_notes
installation
languages
.. toctree::
:caption: Usage
:maxdepth: 2
cookbook
advanced
batch
@@ -26,7 +30,6 @@ Contents:
errors
Indices and tables
==================
+15 -7
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
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:
Installing the Docker image
@@ -103,8 +115,8 @@ Or in the wonderful `fish shell <https://fishshell.com/>`_:
funcsave ocrmypdf
Installing on macOS
-------------------
Manual installation on macOS
----------------------------
These instructions probably work on all macOS supported by Homebrew.
@@ -175,10 +187,6 @@ No package is currently available for Ubuntu 16.04, but you can install the depe
tesseract-ocr \
qpdf \
python3-pip \
python3-pil \
python3-pytest \
python3-reportlab \
python3-ruffus \
python3-cffi
If you wish install OCRmyPDF to the system Python, then install as follows (note this installs new packages
@@ -193,7 +201,7 @@ follow these steps.
.. code-block:: bash
python3 -m venv --system-site-packages venv-ocrmypdf
python3 -m venv venv-ocrmypdf
source venv-ocrmypdf/bin/activate
pip3 install ocrmypdf
+26 -1
View File
@@ -53,6 +53,20 @@ By default, OCRmyPDF will convert the file to a PDF/A. This behavior can be dis
Depending on the settings selected, OCRmyPDF may "graft" the OCR layer into the existing PDF, or reconstruct a visually equivalent new PDF.
Why you shouldn't do this manually
----------------------------------
There are two routes to manually applying OCR to an existing PDF, both of which destroy information in the original PDF.
1. Rasterize each page as an image, OCR the images, and combine the output into a PDF. This preserves the appearance of each page, but resamples all images (possibly losing quality, increasing file size, introducing compression artifacts, etc.)
2. Extract each image, OCR, and combine the output into a PDF. This loses the context in which images are used in the PDF, meaning that cropping, rotation and scaling of pages may be lost. Some PDFs use multiple images per page with stencil masks, which would quite difficult to reassemble correctly. This also loses and text or vector art on any pages in a PDF with both scanned and pure digital content.
In the case of a PDF that is nothing other than a container of images (no rotation, scaling, cropping, one image per page), the second approach is can be lossless.
OCRmyPDF uses several strategies depending on input options and the input PDF itself, but generally speaking it rasterizes a page for OCR and then grafts the OCR back onto the original. As such it can handle complex PDFs and still preserve their contents as much as possible.
Limitations
-----------
@@ -73,4 +87,15 @@ Ghostscript also imposes some limitations:
* PDFs containing JBIG2-encoded content will be converted to CCITT Group4 encoding, which has lower compression ratios, if Ghostscript PDF/A is enabled.
OCRmyPDF is currently not designed to be used as a Python API; it is designed to be run as a command line tool. ``import ocrmypf`` currently attempts to process the command line on ``sys.argv`` at import time so it has side effects that will interfere with its use as a package. The API it presents should not be considered stable.
OCRmyPDF is currently not designed to be used as a Python API; it is designed to be run as a command line tool. ``import ocrmypf`` currently attempts to process the command line on ``sys.argv`` at import time so it has side effects that will interfere with its use as a package. The API it presents should not be considered stable.
Similar programs
----------------
To the author's knowledge, OCRmyPDF is the most feature-rich and thoroughly tested command line OCR PDF conversion tool. If it doesn't meet your needs, consider of these similar open source programs:
* pdf2pdfocr
* pdfsandwich
* pypdfocr
* pdfbeads
View File

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

+82 -52
View File
@@ -1,11 +1,41 @@
RELEASE NOTES
Release notes
=============
OCRmyPDF uses `semantic versioning <http://semver.org/>`_.
v4.5.4
------
- Fix ``--skip-big`` raising an exception if a page contains no images (#152) (thanks to @TomRaz)
- Fix an issue where pages with no images might trigger "cannot write mode P as JPEG" (#151)
v4.5.3
------
- Added a workaround for Ghostscript 9.21 and probably earlier versions would fail with the error message "VMerror -25", due to a Ghostscript bug in XMP metadata handling
- High Unicode characters (U+10000 and up) are no longer accepted for setting metadata on the command line, as Ghostscript may not handle them correctly.
- Fixed an issue where the ``tess4`` renderer would duplicate content onto output pages if tesseract failed or timed out
- Fixed ``tess4`` renderer not recognized when lossless reconstruction is possible
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
@@ -13,7 +43,7 @@ v4.5
v4.4.2
======
------
- The Docker images (ocrmypdf, ocrmypdf-polyglot, ocrmypdf-tess4) are now based on Ubuntu 16.10 instead of Debian stretch
@@ -24,7 +54,7 @@ v4.4.2
v4.4.1
======
------
- To prevent a `TIFF output error <https://github.com/python-pillow/Pillow/issues/2206>`_ caused by img2pdf >= 0.2.1 and Pillow <= 3.4.2, dependencies have been tightened
- The Tesseract 4.00 simultaneous process limit was increased from 1 to 2, since it was observed that 1 lowers performance
@@ -33,7 +63,7 @@ v4.4.1
- Tweaks to setup.py to deal with issues in the v4.4 release
v4.4
====
----
- Tesseract 4.00 is now supported on an experimental basis.
@@ -51,32 +81,32 @@ v4.4
v4.3.5
======
------
- Update documentation to confirm Python 3.6.0 compatibility. No code changes were needed, so many earlier versions are likely supported.
v4.3.4
======
------
- Fixed "decimal.InvalidOperation: quantize result has too many digits" for high DPI images
v4.3.3
======
------
- Fixed PDF/A creation with Ghostscript 9.20 properly
- Fixed an exception on inline stencil masks with a missing optional parameter
v4.3.2
======
------
- Fixed a PDF/A creation issue with Ghostscript 9.20 (note: this fix did not actually work)
v4.3.1
======
------
- Fixed an issue where pages produced by the "hocr" renderer after a Tesseract timeout would be rotated incorrectly if the input page was rotated with a /Rotate marker
- Fixed a file handle leak in LeptonicaErrorTrap that would cause a "too many open files" error for files around hundred pages of pages long when ``--deskew`` or ``--remove-background`` or other Leptonica based image processing features were in use, depending on the system value of ``ulimit -n``
@@ -87,7 +117,7 @@ v4.3.1
v4.3
====
----
- New feature ``--remove-background`` to detect and erase the background of color and grayscale images
- Better documentation
@@ -98,20 +128,20 @@ v4.3
+ Some output validation is disabled in this mode
v4.2.5
======
------
- Fixed an issue (#100) with PDFs that omit the optional /BitsPerComponent parameter on images
- Removed non-free file milk.pdf
v4.2.4
======
------
- Fixed an error (#90) caused by PDFs that use stencil masks properly
- Fixed handling of PDFs that try to draw images or stencil masks without properly setting up the graphics state (such images are now ignored for the purposes of calculating DPI)
v4.2.3
======
------
- Fixed an issue with PDFs that store page rotation (/Rotate) in an indirect object
- Integrated a few fixes to simplify downstream packaging (Debian)
@@ -125,20 +155,20 @@ v4.2.3
v4.2.2
======
------
- Improvements to documentation
v4.2.1
======
------
- Fixed an issue where PDF pages that contained stencil masks would report an incorrect DPI and cause Ghostscript to abort
- Implemented stdin streaming
v4.2
====
----
- ocrmypdf will now try to convert single image files to PDFs if they are provided as input (#15)
@@ -171,13 +201,13 @@ v4.2
- Ghostscript now runs in "safer" mode where possible
v4.1.4
======
------
- Bug fix: monochrome images with an ICC profile attached were incorrectly converted to full color images if lossless reconstruction was not possible due to other settings; consequence was increased file size for these images
v4.1.3
======
------
- More helpful error message for PDFs with version 4 security handler
- Update usage instructions for Windows/Docker users
@@ -186,14 +216,14 @@ v4.1.3
v4.1.2
======
------
- Replace IEC sRGB ICC profile with Debian's sRGB (from icc-profiles-free) which is more compatible with the MIT license
- More helpful error message for an error related to certain types of malformed PDFs
v4.1
====
----
- ``--rotate-pages`` now only rotates pages when reasonably confidence in the orientation. This behavior can be adjusted with the new argument ``--rotate-pages-threshold``
- Fixed problems in error checking if ``unpaper`` is uninstalled or missing at run-time
@@ -201,20 +231,20 @@ v4.1
v4.0.7
======
------
- Minor correction to Ghostscript output settings
v4.0.6
======
------
- Update install instructions
- Provide a sRGB profile instead of using Ghostscript's
v4.0.5
======
------
- Remove some verbose debug messages from v4.0.4
- Fixed temporary that wasn't being deleted
@@ -222,22 +252,22 @@ v4.0.5
- Inline images are now checked during DPI calculation instead of rejecting the image
v4.0.4
======
------
Released with verbose debug message turned on. Do not use. Skip to v4.0.5.
v4.0.3
======
------
New features
------------
^^^^^^^^^^^^
- Page orientations detected are now reported in a summary comment
Fixes
-----
^^^^^
- Show stack trace if unexpected errors occur
- Treat "too few characters" error message from Tesseract as a reason to skip that page rather than
@@ -246,10 +276,10 @@ Fixes
v4.0.2
======
------
Fixes
-----
^^^^^
- Fixed compatibility with Tesseract 3.04.01 release, particularly its different way of outputting
orientation information
@@ -258,19 +288,19 @@ Fixes
v4.0.1
======
------
Fixes
-----
^^^^^
- Fixed a KeyError if tesseract fails to find page orientation information
v4.0
====
----
New features
------------
^^^^^^^^^^^^
- Automatic page rotation (``-r``) is now available. It uses ignores any prior rotation information
on PDFs and sets rotation based on the dominant orientation of detectable text. This feature is
@@ -280,7 +310,7 @@ New features
Fixes
-----
^^^^^
- Fixed an issue where lossless reconstruction could cause some pages to be appear incorrectly
if the page was rotated by the user in Acrobat after being scanned (specifically if it a /Rotate tag)
@@ -289,7 +319,7 @@ Fixes
Changes
-------
^^^^^^^
- Logging output is now much easier to read
- ``--deskew`` is now performed by Leptonica instead of unpaper (#25)
@@ -302,20 +332,20 @@ Changes
v3.2.1
======
------
Changes
-------
^^^^^^^
- Fixed issue #47 "convert() got and unexpected keyword argument 'dpi'" by upgrading to img2pdf 0.2
- Tweaked the Dockerfiles
v3.2
====
----
New features
------------
^^^^^^^^^^^^
- Lossless reconstruction: when possible, OCRmyPDF will inject text layers without
otherwise manipulating the content and layout of a PDF page. For example, a PDF containing a mix
@@ -327,25 +357,25 @@ New features
for the polyglots among us. It is much larger.
Changes
-------
^^^^^^^
- JPEG transcoding quality is now 95 instead of the default 75. Bigger file sizes for less degradation.
v3.1.1
======
------
Changes
-------
^^^^^^^
- Fixed bug that caused incorrect page size and DPI calculations on documents with mixed page sizes
v3.1
====
----
Changes
-------
^^^^^^^
- Default output format is now PDF/A-2b instead of PDF/A-1b
- Python 3.5 and macOS El Capitan are now supported platforms - no changes were
@@ -360,10 +390,10 @@ Changes
- Set up Travis CI automatic integration testing
v3.0
====
----
New features
------------
^^^^^^^^^^^^
- Easier installation with a Docker container or Python's ``pip`` package manager
- Eliminated many external dependencies, so it's easier to setup
@@ -386,7 +416,7 @@ New features
- Multiple images on the same PDF page are now supported
Changes
-------
^^^^^^^
- New, robust rewrite in Python 3.4+ with ruffus_ pipelines
- Now uses Ghostscript 9.14's improved color conversion model to preserve PDF colors
@@ -424,7 +454,7 @@ Changes
.. _JHOVE: http://jhove.sourceforge.net/
Release candidates
------------------
^^^^^^^^^^^^^^^^^^
- rc9:
@@ -492,12 +522,12 @@ where ``settings.txt`` contains *one argument per line*, for example:
Fixes
-----
^^^^^
- Handling of filenames containing spaces: fixed
Notes and known issues
----------------------
^^^^^^^^^^^^^^^^^^^^^^
- Some dependencies may work with lower versions than tested, so try
overriding dependencies if they are "in the way" to see if they work.
@@ -510,7 +540,7 @@ Notes and known issues
v2.2-stable (2014-09-29)
========================
------------------------
OCRmyPDF versions 1 and 2 were implemented as shell scripts. OCRmyPDF 3.0+ is a fork that gradually replaced all shell scripts with Python while maintaining the existing command line arguments. No one is maintaining old versions.
+1 -1
View File
@@ -1,4 +1,4 @@
PDF Security Issues
PDF security issues
===================
OCRmyPDF should only be used on PDFs you trust. It is not designed to protect you against malware.
+81 -9
View File
@@ -46,6 +46,10 @@ def complain(message):
print(*textwrap.wrap(message), file=sys.stderr)
# Hack to help debugger context find /usr/local/bin
if 'IDE_PROJECT_ROOTS' in os.environ:
os.environ['PATH'] = '/usr/local/bin:' + os.environ['PATH']
if tesseract.version() < MINIMUM_TESS_VERSION:
complain(
"Please install tesseract {0} or newer "
@@ -53,7 +57,6 @@ if tesseract.version() < MINIMUM_TESS_VERSION:
MINIMUM_TESS_VERSION, tesseract.version()))
sys.exit(ExitCode.missing_dependency)
# -------------
# Parser
@@ -275,12 +278,15 @@ def check_options_output(options, log):
if options.pdf_renderer in ('tesseract', 'tess4'):
if tesseract.version() < '3.05':
log.warning(
"tesseract < 3.05 may corrupt PDF output. "
"--pdf-renderer=tesseract is not recommend.")
"The setting --pdf-renderer=tesseract is not recommend for "
" use with tesseract versions less than 3.05, because it "
" produces OCR text that is incompatible with Ghostscript and "
" some other software.")
elif tesseract.version() == '4.00.00alpha':
log.warning(
"tesseract 4.00.00alpha may corrupt PDF output. "
"--pdf-renderer={tesseract,tess4} is not recommend.")
"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':
log.info(
@@ -288,7 +294,7 @@ def check_options_output(options, log):
"--pdf-renderer=tesseract.")
lossless_reconstruction = False
if options.pdf_renderer == 'hocr':
if options.pdf_renderer in ('hocr', 'tess4'):
if not any((options.deskew, options.clean_final, options.force_ocr,
options.remove_background)):
lossless_reconstruction = True
@@ -341,13 +347,31 @@ def check_options_advanced(options, log):
"commit 3d9fb3b or later")
def check_options_metadata(options, log):
import unicodedata
metadata = [options.title, options.author, options.keywords,
options.subject]
for s in (m for m in metadata if m):
for c in s:
if unicodedata.category(c) == 'Co' or ord(c) >= 0x10000:
raise ValueError(
"One of the metadata strings contains "
"an unsupported Unicode character: '{}' (U+{})".format(
c, hex(ord(c))[2:].upper()
))
def check_options(options, log):
try:
check_options_languages(options, log)
check_options_metadata(options, log)
check_options_output(options, log)
check_options_preprocessing(options, log)
check_options_ocr_behavior(options, log)
check_options_advanced(options, log)
except ValueError as e:
log.error(e)
sys.exit(ExitCode.bad_args)
except argparse.ArgumentError as e:
log.error(e)
sys.exit(ExitCode.bad_args)
@@ -478,10 +502,58 @@ def traverse_ruffus_exception(e_args, 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():
options = parser.parse_args()
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(
logging_factory, __name__, [None, options.verbose])
_log.debug('ocrmypdf ' + VERSION)
@@ -522,9 +594,9 @@ def run_pipeline():
file."""))
return ExitCode.bad_args
elif not is_file_writable(options.output_file):
_log.error(textwrap.dedent("""\
Cutput file location is not writable."""))
return ExitCode.file_access_error
_log.error(textwrap.dedent("""\
Cutput file location is not writable."""))
return ExitCode.file_access_error
manager = JobContextManager()
manager.register('JobContext', JobContext)
+22 -2
View File
@@ -2,12 +2,32 @@
# © 2015 James R. Barlow: github.com/jbarlow83
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 functools import lru_cache
from . import get_program
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,
pageno=1):
with NamedTemporaryFile(delete=True) as tmp:
@@ -61,7 +81,7 @@ def generate_pdfa(pdf_pages, output_file, log, threads=1):
universal_newlines=True)
stdout, _ = p.communicate()
if 'error' in stdout:
if 'error' in stdout or 'ERROR' in stdout:
log.error(stdout)
elif 'overprint mode not set' in stdout:
# Unless someone is going to print PDF/A documents on a
+23 -2
View File
@@ -11,6 +11,7 @@ from ..helpers import page_number
from . import get_program
from collections import namedtuple
from textwrap import dedent
import PyPDF2 as pypdf
from subprocess import Popen, PIPE, CalledProcessError, \
TimeoutExpired, check_output, STDOUT, DEVNULL
@@ -186,6 +187,8 @@ def tesseract_log_output(log, stdout, input_file):
log.warning(prefix + "unsure about page orientation")
elif 'error' in line.lower() or 'exception' in line.lower():
log.error(prefix + line.strip())
elif 'warning' in line.lower():
log.warning(prefix + line.strip())
elif 'read_params_file' in line.lower():
log.error(prefix + line.strip())
else:
@@ -270,6 +273,24 @@ def generate_hocr(input_file, output_hocr, language: list, engine_mode,
f_out.write(line)
def use_skip_page(text_only, skip_pdf, output_pdf):
if not text_only:
os.symlink(skip_pdf, output_pdf)
return
# For text only we must create a blank page with dimensions identical
# to the skip page because this is equivalent to a page with no text
pdf_in = pypdf.PdfFileReader(skip_pdf)
page0 = pdf_in.pages[0]
with open(output_pdf, 'wb') as out:
pdf_out = pypdf.PdfFileWriter()
w, h = page0.mediaBox.getWidth(), page0.mediaBox.getHeight()
pdf_out.addBlankPage(w, h)
pdf_out.write(out)
def generate_pdf(input_image, skip_pdf, output_pdf, language: list,
engine_mode, text_only: bool,
tessconfig: list, timeout: float, pagesegmode: int, log):
@@ -307,14 +328,14 @@ def generate_pdf(input_image, skip_pdf, output_pdf, language: list,
universal_newlines=True, timeout=timeout)
except TimeoutExpired:
page_timedout(log, input_image)
shutil.copy(skip_pdf, output_pdf)
use_skip_page(text_only, skip_pdf, output_pdf)
except CalledProcessError as e:
tesseract_log_output(log, e.output, input_image)
if 'read_params_file: parameter not found' in e.output:
raise TesseractConfigError() from e
if 'Image too large' in e.output:
shutil.copy(skip_pdf, output_pdf)
use_skip_page(text_only, skip_pdf, output_pdf)
return
raise e from e
else:
+2 -1
View File
@@ -78,7 +78,8 @@ def run(input_file, output_file, dpi, log, mode_args):
raise e from e
else:
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):
+22 -12
View File
@@ -26,11 +26,7 @@ pdfa_def_template = u"""%!
/ICCProfile ($icc_profile)
def
[ /Title <$title>
/Author <$author>
/Subject <$subject>
/Keywords <$keywords>
/Creator <$creator>
[$docinfo
/DOCINFO pdfmark
% Define an ICC profile :
@@ -89,20 +85,30 @@ def encode_text_string(s: str) -> str:
def _get_pdfa_def(icc_profile, icc_identifier, pdfmark):
pdfmark_utf16 = {k: encode_text_string(v) for k, v in pdfmark.items()}
# Ghostscript <= 9.21 has a bug where null entries in DOCINFO might produce
# ERROR: VMerror (-25) on closing pdfwrite device.
# https://bugs.ghostscript.com/show_bug.cgi?id=697684
# Work around this by only adding keys that have a nontrivial value
docinfo_keys = ('/Title', '/Author', '/Subject', '/Creator', '/Keywords')
docinfo_line_template = ' {key} <{value}>'
def docinfo_gen():
for key in docinfo_keys:
if key in pdfmark and pdfmark[key].strip() != '':
line = docinfo_line_template.format(
key=key, value=encode_text_string(pdfmark[key]))
yield line
docinfo = '\n'.join(docinfo_gen())
t = Template(pdfa_def_template)
result = t.substitute(icc_profile=icc_profile,
icc_identifier=icc_identifier,
title=pdfmark_utf16.get('/Title', ''),
author=pdfmark_utf16.get('/Author', ''),
subject=pdfmark_utf16.get('/Subject', ''),
creator=pdfmark_utf16.get('/Creator', ''),
keywords=pdfmark_utf16.get('/Keywords', ''))
docinfo=docinfo)
return result
def generate_pdfa_def(target_filename, pdfmark, icc='sRGB'):
def generate_pdfa_ps(target_filename, pdfmark, icc='sRGB'):
if icc == 'sRGB':
icc_profile = SRGB_ICC_PROFILE
else:
@@ -116,6 +122,10 @@ def generate_pdfa_def(target_filename, pdfmark, icc='sRGB'):
f.write(ps)
# The old name is generate_pdfa_def -- now deprecated
generate_pdfa_def = generate_pdfa_ps
def file_claims_pdfa(filename):
"""Determines if the file claims to be PDF/A compliant
+29 -21
View File
@@ -23,7 +23,7 @@ from ruffus import formatter, regex, Pipeline, suffix
from .hocrtransform import HocrTransform
from .pageinfo import pdf_get_all_pageinfo
from .pdfa import generate_pdfa_def, file_claims_pdfa
from .pdfa import generate_pdfa_ps, file_claims_pdfa
from .helpers import re_symlink, is_iterable_notstr, page_number
from .exec import ghostscript, tesseract, qpdf
from .exceptions import *
@@ -247,7 +247,7 @@ def is_ocr_required(pageinfo, log, options):
"skipping all processing on this page"))
ocr_required = False
if ocr_required and options.skip_big:
if ocr_required and options.skip_big and pageinfo['images']:
pixel_count = pageinfo['width_pixels'] * pageinfo['height_pixels']
if pixel_count > (options.skip_big * 1000000):
ocr_required = False
@@ -393,15 +393,16 @@ def rasterize_with_ghostscript(
pageinfo = get_pageinfo(input_file, context)
device = 'png16m' # 24-bit
if all(image['comp'] == 1 for image in pageinfo['images']):
if all(image['bpc'] == 1 for image in pageinfo['images']):
device = 'pngmono'
elif all(image['bpc'] > 1 and image['color'] == 'index'
for image in pageinfo['images']):
device = 'png256'
elif all(image['bpc'] > 1 and image['color'] == 'gray'
for image in pageinfo['images']):
device = 'pnggray'
if pageinfo['images']:
if all(image['comp'] == 1 for image in pageinfo['images']):
if all(image['bpc'] == 1 for image in pageinfo['images']):
device = 'pngmono'
elif all(image['bpc'] > 1 and image['color'] == 'index'
for image in pageinfo['images']):
device = 'png256'
elif all(image['bpc'] > 1 and image['color'] == 'gray'
for image in pageinfo['images']):
device = 'pnggray'
log.debug("Rasterize {0} with {1}".format(
os.path.basename(input_file), device))
@@ -517,7 +518,8 @@ def select_visible_page_image(
image = next(ii for ii in infiles if ii.endswith(image_suffix))
pageinfo = get_pageinfo(image, context)
if all(orig_image['enc'] == 'jpeg' for orig_image in pageinfo['images']):
if pageinfo['images'] and \
all(im['enc'] == 'jpeg' for im in pageinfo['images']):
# If all images were JPEGs originally, produce a JPEG as output
im = Image.open(image)
@@ -555,16 +557,21 @@ def select_image_layer(
re_symlink(page_pdf, output_file, log)
else:
pageinfo = get_pageinfo(image, context)
dpi = get_page_dpi(pageinfo, options)
dpi = float(dpi[0]), float(dpi[1])
layout_fun = img2pdf.get_fixed_dpi_layout_fun(dpi)
# 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 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, \
open(output_file, 'wb') as pdf:
rawdata = imfile.read()
log.debug('{:4d}: convert'.format(page_number(page_pdf)))
img2pdf.convert(
rawdata, with_pdfrw=False,
imfile, with_pdfrw=False,
layout_fun=layout_fun, outputstream=pdf)
log.debug('{:4d}: convert done'.format(page_number(page_pdf)))
@@ -762,13 +769,14 @@ def generate_postscript_stub(
options = context.get_options()
pdf = pypdf.PdfFileReader(input_file)
pdfmark = get_pdfmark(pdf, options)
generate_pdfa_def(output_file, pdfmark)
generate_pdfa_ps(output_file, pdfmark)
def skip_page(
input_file,
output_file,
log):
log,
context):
# The purpose of this step is its filter to forward only the skipped
# files (.skip.oriented.pdf) while disregarding the processed ones
# (.ocr.oriented.pdf). Alternative would be for merge_pages to filter
@@ -1031,7 +1039,7 @@ def build_pipeline(options, work_folder, log, context):
task_func=generate_postscript_stub,
input=task_repair_pdf,
filter=formatter(r'\.repaired\.pdf'),
output=os.path.join(work_folder, 'pdfa_def.ps'),
output=os.path.join(work_folder, 'pdfa.ps'),
extras=[log, context])
task_generate_postscript_stub.active_if(options.output_type == 'pdfa')
@@ -1043,7 +1051,7 @@ def build_pipeline(options, work_folder, log, context):
filter=suffix('.skip.oriented.pdf'),
output='.done.pdf',
output_dir=work_folder,
extras=[log])
extras=[log, context])
# Merge pages
task_merge_pages_ghostscript = main_pipeline.merge(
+19 -1
View File
@@ -22,6 +22,11 @@ def is_linux():
return platform.system() == 'Linux'
@pytest.helpers.register
def is_macos():
return platform.system() == 'Darwin'
@pytest.helpers.register
def running_in_docker():
# Docker creates a file named /.dockerenv (newer versions) or
@@ -29,6 +34,11 @@ def running_in_docker():
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__))
SPOOF_PATH = os.path.join(TESTS_ROOT, 'spoof')
PROJECT_ROOT = os.path.dirname(TESTS_ROOT)
@@ -91,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)
#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.stat(str(output_file)).st_size > 100, "PDF too small or empty"
assert out == "", \
@@ -115,3 +125,11 @@ def run_ocrmypdf(input_file, output_file, *args, env=None):
#print(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'])
+116 -20
View File
@@ -12,6 +12,7 @@ import PyPDF2 as pypdf
from ocrmypdf.exceptions import ExitCode
from ocrmypdf import leptonica
from ocrmypdf.pdfa import file_claims_pdfa
from ocrmypdf.exec import ghostscript
check_ocrmypdf = pytest.helpers.check_ocrmypdf
@@ -166,48 +167,50 @@ def test_preserve_metadata(spoof_tesseract_noop, output_type,
assert pdfa_info['output'] == output_type
@pytest.mark.skipif(
pytest.helpers.is_linux() and not pytest.helpers.running_in_docker(),
reason="likely to fail if Linux locale is not configured correctly")
@pytest.mark.parametrize("output_type", [
'pdfa', 'pdf'
])
def test_override_metadata(spoof_tesseract_noop, output_type, resources,
outpdf):
input_file = resources / 'c02-22.pdf'
german = 'Du siehst den Wald vor lauter Bäumen nicht.'
chinese = '孔子'
high_unicode = 'U+1030C is: 𐌌'
p, out, err = run_ocrmypdf(
input_file, outpdf,
'--title', german,
'--author', chinese,
'--subject', high_unicode,
'--output-type', output_type,
env=spoof_tesseract_noop)
assert p.returncode == ExitCode.ok
assert p.returncode == ExitCode.ok, err
pdf = str(outpdf)
reader = pypdf.PdfFileReader(outpdf)
out_pdfinfo = check_output(['pdfinfo', pdf], universal_newlines=True)
lines_pdfinfo = out_pdfinfo.splitlines()
pdfinfo = {}
for line in lines_pdfinfo:
k, v = line.strip().split(':', maxsplit=1)
pdfinfo[k.strip()] = v.strip()
assert reader.documentInfo['/Title'] == german
assert reader.documentInfo['/Author'] == chinese
assert reader.documentInfo.get('/Keywords', '') == ''
assert pdfinfo['Title'] == german
assert pdfinfo['Author'] == chinese
assert pdfinfo['Subject'] == high_unicode
assert pdfinfo.get('Keywords', '') == ''
pdfa_info = file_claims_pdfa(pdf)
pdfa_info = file_claims_pdfa(outpdf)
assert pdfa_info['output'] == output_type
def test_high_unicode(spoof_tesseract_noop, resources, no_outpdf):
# Ghostscript doesn't support high Unicode, so neither do we, to be
# safe
input_file = resources / 'c02-22.pdf'
high_unicode = 'U+1030C is: 𐌌'
p, out, err = run_ocrmypdf(
input_file, no_outpdf,
'--subject', high_unicode,
'--output-type', 'pdfa',
env=spoof_tesseract_noop)
assert p.returncode == ExitCode.bad_args, err
@pytest.mark.parametrize('renderer', [
'hocr',
'tesseract',
@@ -409,10 +412,14 @@ def test_force_ocr_on_pdf_with_no_images(spoof_tesseract_crash, resources,
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):
p, out, err = run_ocrmypdf(
resources / 'francais.pdf', outpdf, '-l', 'fra',
env=spoof_tesseract_cache)
print(os.environ)
assert p.returncode == ExitCode.ok, \
"This test may fail if Tesseract language packs are missing"
@@ -564,6 +571,46 @@ def test_non_square_resolution(renderer, spoof_tesseract_cache,
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):
check_ocrmypdf(
resources / 'LinnSequencer.jpg', outpdf, '--image-dpi', '200',
@@ -614,6 +661,23 @@ def test_stdout(spoof_tesseract_noop, ocrmypdf_exec, resources, outpdf):
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):
check_ocrmypdf(resources / 'masks.pdf', outpdf, env=spoof_tesseract_noop)
@@ -748,3 +812,35 @@ 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])
def test_skip_big_with_no_images(spoof_tesseract_noop, resources, outpdf):
check_ocrmypdf(resources / 'blank.pdf', outpdf,
'--skip-big', '5',
'--force-ocr',
env=spoof_tesseract_noop)
+98 -7
View File
@@ -4,19 +4,110 @@
import pytest
from ocrmypdf.exceptions import ExitCode
from ocrmypdf.exec import tesseract
from ocrmypdf import pageinfo
import sys
import os
import PyPDF2 as pypdf
spoof = pytest.helpers.spoof
def tess4_possible_location():
"""The location of tesseract 4 may be OCRMYPDF_TESS4, OCRMYPDF_TESSERACT,
or the installed version on PATH."""
return os.environ.get('OCRMYPDF_TESS4') or \
os.environ.get('OCRMYPDF_TESSERACT') or \
'tesseract'
@pytest.fixture
def ensure_tess4():
return spoof(tesseract=tess4_possible_location())
def tess4_available():
"""Check if a tesseract 4 binary is available, even if it's not the
official "tesseract" on PATH
"""
old_environ = os.environ.copy()
try:
os.environ['OCRMYPDF_TESSERACT'] = tess4_possible_location()
return tesseract.v4() and tesseract.has_textonly_pdf()
finally:
os.environ = old_environ
# Skip all tests in this file if not tesseract 4
pytestmark = pytest.mark.skipif(not tesseract.v4(),
reason="tesseract 4.0 required")
pytestmark = pytest.mark.skipif(
not tess4_available(),
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):
pytest.helpers.check_ocrmypdf(
def test_textonly_pdf(ensure_tess4, resources, outdir):
check_ocrmypdf(
resources / 'linn.pdf',
outdir / 'linn_textonly.pdf', '--pdf-renderer', 'tess4')
outdir / 'linn_textonly.pdf', '--pdf-renderer', 'tess4',
env=ensure_tess4)
@pytest.mark.skipif(sys.version_info < (3, 5), reason="needs math.isclose")
def test_pagesize_consistency_tess4(ensure_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',
env=ensure_tess4)
after_dims = pytest.helpers.first_page_dimensions(outpdf)
assert isclose(before_dims[0], after_dims[0])
assert isclose(before_dims[1], after_dims[1])
@pytest.mark.parametrize('basename', ['graph_ocred.pdf', 'cardinal.pdf'])
def test_skip_pages_does_not_replicate(
ensure_tess4, resources, basename, outdir):
infile = resources / basename
outpdf = outdir / basename
check_ocrmypdf(
infile,
outpdf, '--pdf-renderer', 'tess4', '--force-ocr',
'--tesseract-timeout', '0',
env=ensure_tess4
)
info_in = pageinfo.pdf_get_all_pageinfo(str(infile))
info = pageinfo.pdf_get_all_pageinfo(str(outpdf))
for page in info:
assert len(page['images']) == 1, "skipped page was replicated"
for n in range(len(info_in)):
assert info[n]['width_inches'] == info_in[n]['width_inches']
def test_content_preservation(ensure_tess4, resources, outpdf):
infile = resources / 'masks.pdf'
check_ocrmypdf(
infile,
outpdf, '--pdf-renderer', 'tess4', '--tesseract-timeout', '0',
env=ensure_tess4
)
info = pageinfo.pdf_get_all_pageinfo(str(outpdf))
page = info[0]
assert len(page['images']) > 1, "masked were rasterized"