Compare commits

...
44 Commits
Author SHA1 Message Date
James R. Barlow 84d120e850 v5.6.1 notes 2018-03-09 08:00:42 -08:00
James R. Barlow 8159cc6b88 Skip one test that fails for qpdf 8.0.[0,1], due to qpdf regression 2018-03-09 07:57:22 -08:00
James R. Barlow 4046766ca5 Fix Python 3.5 test suite failure on symlinks
Did not account for API difference in pathlib
2018-03-02 16:57:46 -08:00
James R. Barlow 810390df0b pipeline: Refactor duplicate with clause 2018-03-02 16:57:15 -08:00
James R. Barlow de749bc7ae Fix regression - output to stdout broken 2018-03-02 15:35:51 -08:00
James R. Barlow 9965b8800c Move Dockerfiles out of the way 2018-03-02 15:26:41 -08:00
James R. Barlow ab870fddd6 Dockerignore: glob supported now 2018-03-02 15:24:42 -08:00
James R. Barlow a79d6807cf Dockerfiles: remove deprecated MAINTAINER tag 2018-03-02 15:24:28 -08:00
James R. Barlow 8185fb7e43 Migrate Travis CI setup to Brewfile 2018-03-02 15:16:05 -08:00
Tucker Barbourandjbarlow83 4b10929b25 Fix Homebrew python package (#227)
Homebrew removed python3 and python now defaults to version 3. Here we
use `brew upgrade python` to upgrade the pre-installed version of
python to python3.
2018-03-02 14:59:23 -08:00
James R. Barlow 2d8aad1086 Improve docs 2018-03-01 00:24:38 -08:00
James R. Barlow 74ca736333 Issue #223: improve text of encrypted PDF error message 2018-02-27 15:08:22 -08:00
James R. Barlow 5e4fd8b0b9 compile_leptonica should have shebang 2018-02-24 12:49:36 -08:00
James R. Barlow 8ab8132411 lint: unused variables, wildcard imports 2018-02-24 12:48:52 -08:00
James R. Barlow 6899dd46e4 lint: remove extraneous backslash 2018-02-24 12:42:37 -08:00
James R. Barlow 8ad0697a20 lint: remove duplicate property definition 2018-02-24 12:42:03 -08:00
James R. Barlow b47e5672e6 Remove old test case that no longer works 2018-02-24 12:40:14 -08:00
James R. Barlow 45c7bd9a60 lint: Remove shebangs from non-executable files 2018-02-24 12:38:58 -08:00
James R. Barlow e7bcb95635 Fix pylint errors 2018-02-24 11:59:01 -08:00
James R. Barlow 3de83627a9 Handle output to /dev/null or directory (#219)
Previously we threw an exception if the output name was a directory (only after doing OCR) and would trigger a PermissionError on trying to flip permission bits of /dev/null due to shutil.copyfile implementation. Instead of copying file use shutil.copyfileobj which should also respect umask etc.
2018-02-19 22:15:07 -08:00
James R. Barlow f248576994 Change instructions to point away from private tap 2018-02-19 17:33:58 -08:00
James R. Barlow aac5b6de3b Update autobrew script to match final changes 2018-02-17 00:12:03 -08:00
James R. Barlow 24435f11e0 We are now in homebrew 2018-02-15 17:42:16 -08:00
James R. Barlow a9da839c39 Add vector-only PDF test case 2018-02-08 00:17:35 -08:00
James R. Barlow fa2c0296d6 v5.6.0 release notes, docs 2018-02-07 16:48:04 -08:00
James R. Barlow 1dfc32d7e6 Preserve "text as curves" vector content
Never updated the checking logic to deal with a pure vector file with no text that needs an OCR layer. This is doable, so allow it.
2018-02-07 16:05:48 -08:00
James R. Barlow 4a61beae41 autobrew: use homebrew's built-in test fixture 2018-02-05 11:10:33 -08:00
James R. Barlow bd30587bf1 Update depends_on order 2018-01-29 13:16:07 -08:00
James R. Barlow e0070e3e18 Update Dockerfile to use Ubuntu 17.10 (issue #214) 2018-01-28 15:30:41 -08:00
James R. Barlow 019513696b Ghostscript spoof scripts did not report their --version correctly 2018-01-10 17:08:14 -08:00
James R. Barlow ad7a4476db hugemono.pdf needs --max-image-mpixels to pass with Pillow 5.0 2018-01-10 16:55:18 -08:00
James R. Barlow 4812b20fb2 Fix tesseract_noop.py generating wrong size of output PDF in tests
This caused trouble before with test_deskew
2018-01-10 16:35:31 -08:00
James R. Barlow f5e07c9427 Fix Ghostscript parameter order 2018-01-10 16:33:26 -08:00
James R. Barlow 75dcb90621 Niceties: when environment variable overload is used clarify we're not checking the PATH 2018-01-10 16:33:03 -08:00
James R. Barlow dfc0434cc2 Update requirements to set Pillow to 5.0 2018-01-10 15:45:23 -08:00
James R. Barlow 882fc2257c Add --max-image-mpixels argument to support Pillow 5.0 2018-01-10 15:43:59 -08:00
James R. Barlow 41e83b52fc Document process for redoing OCR 2018-01-10 15:39:58 -08:00
James R. Barlow 47758b4d8f Reactivate autobrew 2018-01-10 15:39:36 -08:00
James R. Barlow 6bf1f970a0 Fix some parameter validation for --output-type pdfa-1 and pdfa-2 2018-01-10 11:50:08 -08:00
James R. Barlow 7d451f101f Detect old versions of Ghostscript and warn about them (#208) 2018-01-10 11:47:39 -08:00
James R. Barlow 7edbfe0e40 Update autobrew template 2018-01-09 12:36:14 -08:00
James R. Barlow 91b42cbfa8 Fix issue in sandwich renderer when skipping OCR on a rotated and deskewed page
If OCR is skipped due to --tesseract-timeout or similar, and the skip page is rotated with /Rotate, and the skip page was deskewed or had other image processing, then the skip page was created with the wrong dimensions causing the output page to be cropped.
2018-01-09 00:17:53 -08:00
James R. Barlow 6907df41b4 Disable autobrew until homebrew accepts the official release 2018-01-08 23:26:58 -08:00
James R. Barlow 2cebd90cbd Fix brew audit --strict warnings 2017-12-09 12:14:28 -08:00
46 changed files with 406 additions and 227 deletions
+2 -3
View File
@@ -1,7 +1,6 @@
# OCRmyPDF
#
FROM ubuntu:17.04
MAINTAINER James R. Barlow <jim@purplerock.ca>
FROM ubuntu:17.10
RUN apt-get update && apt-get install -y --no-install-recommends \
software-properties-common python-software-properties \
@@ -52,4 +51,4 @@ WORKDIR /home/docker
# Must use array form of ENTRYPOINT
# Non-array form does not append other arguments, because that is "intuitive"
ENTRYPOINT ["/application/docker-wrapper.sh"]
ENTRYPOINT ["/application/.docker/docker-wrapper.sh"]
@@ -14,4 +14,4 @@ USER docker
# Must use array form of ENTRYPOINT
# Non-array form does not append other arguments, because that is "intuitive"
ENTRYPOINT ["/application/docker-wrapper.sh"]
ENTRYPOINT ["/application/.docker/docker-wrapper.sh"]
@@ -1,7 +1,6 @@
# OCRmyPDF
#
FROM ubuntu:17.04
MAINTAINER James R. Barlow <jim@purplerock.ca>
FROM ubuntu:17.10
RUN apt-get update && apt-get install -y --no-install-recommends \
software-properties-common python-software-properties \
@@ -62,4 +61,4 @@ WORKDIR /home/docker
# Must use array form of ENTRYPOINT
# Non-array form does not append other arguments, because that is "intuitive"
ENTRYPOINT ["/application/docker-wrapper.sh"]
ENTRYPOINT ["/application/.docker/docker-wrapper.sh"]
+3 -12
View File
@@ -3,18 +3,9 @@
*.pyc
*.rst
*.sublime*
*/*.pyc
*/*/*.pyc
*/*/*/*.pyc
*/*/*/*/*.pyc
*/*/*/*/*/*.pyc
*/*/*/*/*/*/*.pyc
*/*/*/*/*/*/*/*.pyc
.cache/
.eggs/
.github/
.git/
.ipynb_checkpoints/
**/*.pyc
.*/
!.docker/
.ruffus_history.sqlite
bin/
build/
+3 -1
View File
@@ -34,7 +34,9 @@ before_install: |
if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then
bash .travis/linux_before_install.sh
elif [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
bash .travis/osx_before_install.sh
brew update && brew bundle --file=.travis/Brewfile
pip3 install --upgrade pip
pip3 install wheel
fi
install:
+12
View File
@@ -0,0 +1,12 @@
brew 'python'
brew 'ghostscript'
brew 'jbig2dec'
brew 'leptonica'
brew 'libffi'
brew 'libtiff'
brew 'libxml2'
brew 'openjpeg'
brew 'qpdf'
brew 'tesseract'
brew 'unpaper'
+35 -28
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# © 2016 James R. Barlow: github.com/jbarlow83
# © 2017-18 James R. Barlow: github.com/jbarlow83
from string import Template
from subprocess import run, PIPE
@@ -14,43 +14,50 @@ class Ocrmypdf < Formula
${ocrmypdf_url}
${ocrmypdf_sha256}
depends_on :python3
depends_on :x11 # Pillow needs XQuartz
depends_on "pkg-config" => :build
depends_on "libffi"
depends_on "tesseract"
depends_on "ghostscript"
depends_on "unpaper"
depends_on "qpdf"
# mactex installs its own ghostscript by default which causes problems
# mactex users should use caskroom/cask/mactex-no-ghostscript instead
conflicts_with cask: "caskroom/cask/mactex"
# For Pillow source install
depends_on "openjpeg"
depends_on "freetype"
depends_on "ghostscript"
depends_on "jpeg"
depends_on "libpng"
depends_on "libjpeg"
depends_on "webp"
depends_on "little-cms2"
depends_on "zlib"
depends_on "python"
depends_on "qpdf"
depends_on "tesseract"
depends_on "unpaper"
${resources}
def install
ENV.append ["SETUPTOOLS_SCM_PRETEND_VERSION"], "v${ocrmypdf_version}"
ENV.each do |key, value|
puts "#{key}:#{value}"
venv = virtualenv_create(libexec, "python3")
resource("Pillow").stage do
inreplace "setup.py" do |s|
sdkprefix = MacOS::CLT.installed? ? "" : MacOS.sdk_path
s.gsub! "openjpeg.h", "probably_not_a_header_called_this_eh.h"
s.gsub! "ZLIB_ROOT = None", "ZLIB_ROOT = ('#{sdkprefix}/usr/lib', '#{sdkprefix}/usr/include')"
s.gsub! "JPEG_ROOT = None", "JPEG_ROOT = ('#{Formula["jpeg"].opt_prefix}/lib', '#{Formula["jpeg"].opt_prefix}/include')"
s.gsub! "FREETYPE_ROOT = None", "FREETYPE_ROOT = ('#{Formula["freetype"].opt_prefix}/lib', '#{Formula["freetype"].opt_prefix}/include')"
end
# avoid triggering "helpful" distutils code that doesn't recognize Xcode 7 .tbd stubs
ENV.append "CFLAGS", "-I#{MacOS.sdk_path}/System/Library/Frameworks/Tk.framework/Versions/8.5/Headers" unless MacOS::CLT.installed?
venv.pip_install Pathname.pwd
end
virtualenv_install_with_resources
res = resources.map(&:name).to_set - ["Pillow"]
res.each do |r|
venv.pip_install resource(r)
end
venv.pip_install_and_link buildpath
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"
# Since we use Python 3, we require a UTF-8 locale
ENV["LC_ALL"] = "en_US.UTF-8"
system "#{bin}/ocrmypdf", "-f", "-q", "--deskew",
test_fixtures("test.pdf"), "ocr.pdf"
assert_predicate testpath/"ocr.pdf", :exist?
end
end
""")
-17
View File
@@ -1,17 +0,0 @@
#!/bin/bash
# © 2017 James R. Barlow: github.com/jbarlow83
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
+1 -2
View File
@@ -74,11 +74,10 @@ Users of Debian 9 or later or Ubuntu 16.10 or later may simply
apt-get install ocrmypdf
and macOS users may simply
and macOS users with Homebrew 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.
+23
View File
@@ -157,6 +157,29 @@ If you set ``--tesseract-timeout 0`` OCRmyPDF will apply its image processing wi
ocrmypdf --tesseract-timeout=0 --remove-background input.pdf output.pdf
Redo OCR
""""""""
To redo OCR on a file OCRed with other OCR software or a previous version of OCRmyPDF and/or Tesseract, you may use the ``--force-ocr`` argument. Normally, OCRmyPDF does not modify files that already appear to contain OCR text.
.. code-block:: bash
ocrmypdf --force-ocr input.pdf output.pdf
Note that the method above will force rasterization of all pages, potentially reducing quality or losing vector content.
To ensure quality is preserved, one could extract all of the images and rebuild the PDF for a lossless transformation. This recipe does not work when PDFs contain multiple images per page, as many do in practice. It will also lose any page rotation information.
.. code-block:: bash
pdfimages -all old-ocr.pdf prefix # extract all images
img2pdf -o temp.pdf prefix* # construct new PDF from the images
# review the new PDF to ensure it visually matches the old one
ocrmypdf --output-type pdf temp.pdf new-ocr.pdf
``--output-type pdf`` is used here to avoid using Ghostscript which will also rasterize images.
Improving OCR quality
---------------------
+1 -1
View File
@@ -9,7 +9,7 @@ OCRmyPDF documentation
OCRmyPDF adds an OCR text layer to scanned PDF files, allowing them to
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.
PDF is the best format for storing and exchanging scanned documents. Unfortunately, PDFs can be difficult to modify. OCRmyPDF makes it easy to apply image processing and OCR to existing PDFs.
.. toctree::
:maxdepth: 1
+8 -4
View File
@@ -15,18 +15,20 @@ Users of Debian 9 ("stretch") or later or Ubuntu 16.10 or later may simply
Installing on macOS
-------------------
A `Homebrew <https://brew.sh>`_ tap is available for macOS:
OCRmyPDF is now a standard `Homebrew <https://brew.sh>`_ formula. To install on macOS:
.. code-block:: bash
brew tap jbarlow83/ocrmypdf
brew install ocrmypdf
.. warning::
.. note::
Users who previously installed OCRmyPDF on macOS using ``pip install ocrmypdf`` should remove the pip version (``pip3 uninstall ocrmypdf``) before switching to the Homebrew version.
.. note::
Users who previously installed OCRmyPDF from the private tap should switch to the mainline version (``brew untap jbarlow83/ocrmypdf``) and install from there.
.. _Docker-install:
Installing the Docker image
@@ -285,6 +287,8 @@ Now we need to install ``pip`` and let it install ocrmypdf:
wget -O - -o /dev/null https://bootstrap.pypa.io/get-pip.py | python3.6
pip3.6 install ocrmypdf
The ``wget`` command will download a program and run it.
These installation instructions omit the optional dependency ``unpaper``, which is only available at version 0.4.2 in Ubuntu 14.04. The author could not find a backport of ``unpaper``, and created a .deb package to do the job of installing unpaper 6.1 (for x86 64-bit only):
.. code-block:: bash
+5 -7
View File
@@ -16,7 +16,7 @@ OCRmyPDF uses `Tesseract <https://github.com/tesseract-ocr/tesseract>`_, the bes
About PDFs
----------
PDFs are page description files that attempts to preserve a layout exactly. They can contain `vector graphic files <http://vector-conversions.com/vectorizing/raster_vs_vector.html>`_ that can contain raster objects such as scanned images. Because PDFs can contain multiple pages (unlike many image formats) and can contain fonts and text, it is a good formats for exchanging scanned documents.
PDFs are page description files that attempts to preserve a layout exactly. They contain `vector graphics <http://vector-conversions.com/vectorizing/raster_vs_vector.html>`_ that can contain raster objects such as scanned images. Because PDFs can contain multiple pages (unlike many image formats) and can contain fonts and text, it is a good formats for exchanging scanned documents.
.. image:: bitmap_vs_svg.svg
@@ -42,15 +42,13 @@ PDF/A has a few drawbacks. Some PDF viewers include an alert that the file is a
What OCRmyPDF does
------------------
OCRmyPDF analyzes each page of a PDF to determine the colorspace and resolution (DPI) needed to capture all of the information on that page without losing content. It uses `Ghostscript <http://ghostscript.com/>`_ to rasterize the page, and then performs on OCR on the rasterized image. It is not enough to simply extract the images from each page and run OCR on them individually. Of course one could use Ghostscript or another PDF rasterizer and then pass the image to Tesseract. OCRmyPDF automates this process and produces a minimally changed output file that contains the same information, colorspace and resolution.
OCRmyPDF analyzes each page of a PDF to determine the colorspace and resolution (DPI) needed to capture all of the information on that page without losing content. It uses `Ghostscript <http://ghostscript.com/>`_ to rasterize the page, and then performs on OCR on the rasterized image to create an OCR "layer". The layer is then grafted back onto the original PDF.
The Tesseract OCR engine can output 'hOCR' files, which are XML files that contain a description of the text it found on the page. OCRmyPDF will render a new PDF that contains only the hidden text layer, and merge this with the original page.
While one can use a program like Ghostscript or ImageMagick to get an image and put the image through Tesseract, that actually creates a new PDF and many details may be lost. OCRmyPDF can produce a minimally changed PDF as output.
Alternately, OCRmyPDF can use the Tesseract OCR engine to directly output PDFs for each page, then merge them.
OCRmyPDF also some image processing options like deskew which improve the appearance of files and quality of OCR. When these are used, the OCR layer is grafted onto the processed image instead.
By default, OCRmyPDF will convert the file to a PDF/A. This behavior can be disabled with the ``--output-type pdf`` argument.
Depending on the settings selected, OCRmyPDF may "graft" the OCR layer into the existing PDF, or reconstruct a visually equivalent new PDF.
By default, OCRmyPDF produces archival PDFs PDF/A, which are a stricter subset of PDF features designed for long term archives. If regular PDFs are desired, this can be disabled with ``--output-type pdf``.
Why you shouldn't do this manually
+27
View File
@@ -5,6 +5,33 @@ OCRmyPDF uses `semantic versioning <http://semver.org/>`_ for its command line i
The OCRmyPDF package itself does not contain a public API, although it is fairly stable and breaking changes are usually timed with a major release. A future release will clearly define the stable public API.
v5.6.1
------
- Fix issue #219: change how the final output file is created to avoid triggering permission errors when the output is a special file such as ``/dev/null``
- Fix test suite failures due to a qpdf 8.0.0 regression and Python 3.5's handling of symlink
- The "encrypted PDF" error message was different depending on the type of PDF encryption. Now a single clear message appears for all types of PDF encryption.
- ocrmypdf is now in Homebrew. Homebrew users are advised to the version of ocrmypdf in the official homebrew-core formulas rather than the private tap.
- Some linting
v5.6.0
------
- Fix issue #216: preserve "text as curves" PDFs without rasterizing file
- Related to the above, messages about rasterizing are more consistent
- For consistency versions minor releases will now get the trailing .0 they always should have had.
v5.5
----
- Add new argument ``--max-image-mpixels``. Pillow 5.0 now raises an exception when images may be decompression bombs. This argument can be used to override the limit Pillow sets.
- Fix output page cropped when using the sandwich renderer and OCR is skipped on a rotated and image-processed page
- A warning is now issued when old versions of Ghostscript are used in cases known to cause issues with non-Latin characters
- Fix a few parameter validation checks for ``-output-type pdfa-1`` and ``pdfa-2``
v5.4.4
------
+12 -4
View File
@@ -30,14 +30,16 @@ Depending on the options specified, OCRmyPDF may graft the OCR layer into the ex
Using OCRmyPDF online or as a service
-------------------------------------
OCRmyPDF should not be deployed as a public-facing service, like a website where a potential attacker could upload a PDF of their choice for OCR. OCRmyPDF is not designed to be secure against PDF malware. Another concern is PDFs specifically designed to be a denial of service attack: PDFs can contain recursive data structures that sometimes send parsers into infinite loops, and issue complex graphics drawing commands.
OCRmyPDF should not be deployed as a public-facing service, such as a website where a potential attacker could upload a PDF of their choice for OCR. OCRmyPDF is not designed to be secure against PDF malware. Another concern is PDFs specifically designed to be a denial of service attack: PDFs can contain recursive data structures that sometimes send parsers into infinite loops, and issue complex graphics drawing commands.
Setting aside these concerns, a side effect of OCRmyPDF is it may incidentally sanitize PDFs that contain malware. It runs ``qpdf`` to repair the PDF, which could correct malformed PDF structures that are part of an attack. When PDF/A output is selected (the default), the input PDF is partially reconstructed by Ghostscript. When ``--force-ocr`` is used, all pages are rasterized and reconverted to PDF, which could remove malware in embedded images. No guarantees.
OCRmyPDF should be relatively safe to use in a trusted intranet, with some considerations:
Limiting CPU usage
^^^^^^^^^^^^^^^^^^
OCRmyPDF will attempt to use all available CPUs and storage, so executing ``nice ocrmypdf`` or limiting the number of jobs with the ``-j`` argument may ensure the server remains available. Another option would be run OCRmyPDF jobs inside a Docker container or virtual machine, which can impose its own limits on CPU usage.
OCRmyPDF will attempt to use all available CPUs and storage, so executing ``nice ocrmypdf`` or limiting the number of jobs with the ``-j`` argument may ensure the server remains available. Another option would be run OCRmyPDF jobs inside a Docker container, a virtual machine, or a cloud instance, which can impose its own limits on CPU usage and be terminated "from orbit" if it fails to complete.
Temporary storage requirements
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -46,7 +48,7 @@ OCRmyPDF will use a large amount of temporary storage for its work, proportional
To check temporary storage usage on actual files, run ``ocrmypdf -k ...`` which will preserve and print the path to temporary storage when the job is done.
To change where temporary files are stored, change the ``TMPDIR`` environment variable for ocrmypdf's environment. (Python's ``tempfile.gettempdir()`` returns the root directory in which temporary files will be stored.)
To change where temporary files are stored, change the ``TMPDIR`` environment variable for ocrmypdf's environment. (Python's ``tempfile.gettempdir()`` returns the root directory in which temporary files will be stored.) For example, one could redirect ``TMPDIR`` to a large RAM disk to avoid wear on HDD/SSD and potentially improve performance. On Amazon Web Services, ``TMPDIR`` can be set to `empheral storage <https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/InstanceStorage.html>`_.
Timeouts
^^^^^^^^
@@ -64,7 +66,13 @@ Abbyy Cloud OCR is a viable commercial alternative with a web services API.
Password protection, digital signatures and certification
---------------------------------------------------------
OCRmyPDF cannot remove password protection from a PDF. ``qpdf``, one of its dependencies, has this capability. After OCR is applied, password protection is not permitted on PDF/A documents but the file can be converted to regular PDF.
Password protected PDFs usually have two passwords, and owner and user password. When the user password is set to empty, PDF readers will open the file automatically and marked it as "(SECURED)". While not as reliable as a digital signature, this indicates that whoever set the password approved of the file at that time. When the user password is set, the document cannot be viewed without the password.
Either way, OCRmyPDF does not remove passwords from PDFs and exits with an error on encountering them.
``qpdf``, one of OCRmyPDF's dependencies, can remove passwords. If the owner and user password are set, a password is required for ``qpdf``. If only the owner password is set, then the password can be stripped, even if one does not have the owner password.
After OCR is applied, password protection is not permitted on PDF/A documents but the file can be converted to regular PDF.
Many programs exist which are capable of inserting an image of someone's signature. On its own, this offers no security guarantees. It is trivial to remove the signature image and apply it to other files. This practice offers no real security.
+61 -29
View File
@@ -14,6 +14,7 @@ import logging
import argparse
import PyPDF2 as pypdf
import PIL
import ruffus.ruffus_exceptions as ruffus_exceptions
import ruffus.cmdline as cmdline
@@ -23,10 +24,10 @@ from .pipeline import JobContext, JobContextManager, \
cleanup_working_files, build_pipeline
from .pdfa import file_claims_pdfa
from .helpers import is_iterable_notstr, re_symlink, is_file_writable
from .exec import tesseract, qpdf
from .exec import tesseract, qpdf, ghostscript
from . import PROGRAM_NAME, VERSION
from .exceptions import *
from .exceptions import ExitCode, ExitCodeException, MissingDependencyError
from . import exceptions as ocrmypdf_exceptions
from ._unicodefun import verify_python3_env
@@ -241,6 +242,11 @@ ocrsettings.add_argument(
advanced = parser.add_argument_group(
"Advanced",
"Advanced options to control Tesseract's OCR behavior")
advanced.add_argument(
'--max-image-mpixels', action='store', type=float, metavar='MPixels',
help="Set maximum number of pixels to unpack before treating an image as a "
"decompression bomb",
default=128.0)
advanced.add_argument(
'--tesseract-config', action='append', metavar='CFG', default=[],
help="Additional Tesseract configuration files -- see documentation")
@@ -321,11 +327,12 @@ def check_options_languages(options, _log):
if '+' in options.language[0]:
options.language = options.language[0].split('+')
if not set(options.language).issubset(tesseract.languages()):
languages = set(options.language)
if not languages.issubset(tesseract.languages()):
msg = (
"The installed version of tesseract does not have language "
"data for the following requested languages: \n")
for lang in (set(options.language) - tesseract.languages()):
for lang in (languages - tesseract.languages()):
msg += lang + '\n'
raise MissingDependencyError(msg)
@@ -348,7 +355,8 @@ def check_options_output(options, log):
options.pdf_renderer = 'sandwich'
if options.pdf_renderer == 'tesseract':
if tesseract.version() < '3.05' and options.output_type == 'pdfa':
if tesseract.version() < '3.05' and \
options.output_type.startswith('pdfa'):
log.warning(
"For best results use --pdf-renderer=tesseract "
"--output-type=pdf to disable PDF/A generation via "
@@ -412,9 +420,9 @@ def check_options_ocr_behavior(options, log):
# if options.redo_ocr and (options.skip_text or options.force_ocr):
# raise argparse.ArgumentError(
# "Error: --redo-ocr and other OCR options are incompatible.")
languages = set(options.language)
if options.pdf_renderer == 'hocr' and \
not set(options.language).issubset(HOCR_OK_LANGS):
not languages.issubset(HOCR_OK_LANGS):
msg = (
"The 'hocr' PDF renderer is known to cause problems with one "
"or more of the languages in your document. ")
@@ -427,6 +435,15 @@ def check_options_ocr_behavior(options, log):
"Use --pdf-renderer tesseract --output-type pdf to avoid "
"this issue")
log.warning(msg)
elif ghostscript.version() < '9.20' and \
not languages.issubset(HOCR_OK_LANGS) \
and options.output_type != 'pdf':
msg = (
"The installed version of Ghostscript does not work correctly "
"with the OCR languages you specified. Use --output-type pdf or "
"upgrade to Ghostscript 9.20 or later to avoid this issue.")
msg += "Found Ghostscript {}".format(ghostscript.version())
log.warning(msg)
def check_options_advanced(options, log):
@@ -441,10 +458,10 @@ def check_options_advanced(options, log):
"--pdf-renderer tess4 requires Tesseract 4.x "
"commit 3d9fb3b or later")
if options.pdfa_image_compression != 'auto' and \
options.output_type != 'pdfa':
options.output_type.startswith('pdfa'):
log.warning(
"--pdfa-image-compression argument has no effect when "
"--output-type is not 'pdfa'"
"--output-type is not 'pdfa', 'pdfa-1', or 'pdfa-2'"
)
@@ -537,6 +554,7 @@ def do_ruffus_exception(ruffus_five_tuple, options, log):
exit_code = None
task_name, job_name, exc_name, exc_value, exc_stack = ruffus_five_tuple
job_name = job_name # unused
if exc_name == 'builtins.SystemExit':
match = re.search(r"\.(.+?)\)", exc_value)
exit_code_name = match.groups()[0]
@@ -559,6 +577,21 @@ def do_ruffus_exception(ruffus_five_tuple, options, log):
msg = "Error occurred while running this command:"
log.error(msg + '\n' + exc_value)
exit_code = ExitCode.child_process_error
elif (exc_name == 'PyPDF2.utils.PdfReadError' and \
'not been decrypted' in exc_value) or \
(exc_name == 'ocrmypdf.exceptions.EncryptedPdfError'):
log.error(textwrap.dedent("""\
Input PDF is encrypted. The encryption must be removed to
perform OCR.
For information about this PDF's security use
qpdf --show-encryption infilename
You can remove the encryption using
qpdf --decrypt [--password=[password]] infilename
"""))
exit_code = ExitCode.encrypted_pdf
elif exc_name == 'ocrmypdf.exceptions.PdfMergeFailedError':
log.error(textwrap.dedent("""\
Failed to merge PDF image layer with OCR layer
@@ -574,19 +607,12 @@ def do_ruffus_exception(ruffus_five_tuple, options, log):
base_exc_name = exc_name.replace('ocrmypdf.exceptions.', '')
exc_class = getattr(ocrmypdf_exceptions, base_exc_name)
exit_code = exc_class.exit_code
elif exc_name == 'PyPDF2.utils.PdfReadError' and \
'not been decrypted' in exc_value:
log.error(textwrap.dedent("""\
Input PDF uses either an encryption algorithm or a PDF security
handler that is not supported by ocrmypdf.
For information about this PDF's security use
qpdf --show-encryption [...input PDF...]
(Only algorithms "R = 1" and "R = 2" are supported.)
"""))
exit_code = ExitCode.encrypted_pdf
elif exc_name == 'PIL.Image.DecompressionBombError':
msg = cleanup_ruffus_error_message(exc_value)
msg += ("\nUse the --max-image-mpixels argument to set increase the "
"maximum number of megapixels to accept.")
log.error(msg)
exit_code = ExitCode.input_file
if exit_code is not None:
return exit_code
@@ -660,7 +686,7 @@ def log_page_orientations(pdfinfo, _log):
180: 's', 270: 'w'}
orientations = []
for n, page in enumerate(pdfinfo):
angle = pdfinfo[n].rotation or 0
angle = page.rotation or 0
if angle != 0:
orientations.append('{0}{1}'.format(
n + 1,
@@ -686,6 +712,10 @@ def run_pipeline():
check_options(options, _log)
PIL.Image.MAX_IMAGE_PIXELS = int(options.max_image_mpixels * 1000000)
if PIL.Image.MAX_IMAGE_PIXELS == 0:
PIL.Image.MAX_IMAGE_PIXELS = None
# Complain about qpdf version < 7.0.0
# Suppress the warning if in the test suite, since there are no PPAs
# for qpdf 7.0.0 for Ubuntu trusty (i.e. Travis)
@@ -730,14 +760,14 @@ def run_pipeline():
elif not is_file_writable(options.output_file):
_log.error(
"Output file location (" + options.output_file + ") " +
"is not writable.")
"is not a writable file.")
return ExitCode.file_access_error
manager = JobContextManager()
manager.register('JobContext', JobContext)
manager.register('JobContext', JobContext) # pylint: disable=no-member
manager.start()
context = manager.JobContext()
context = manager.JobContext() # pylint: disable=no-member
context.set_options(options)
context.set_work_folder(work_folder)
@@ -777,7 +807,11 @@ def run_pipeline():
if options.flowchart:
_log.info("Flowchart saved to {}".format(options.flowchart))
elif options.output_file != '-':
elif options.output_file == '-':
_log.info("Output sent to stdout")
elif os.path.samefile(options.output_file, os.devnull):
pass # Say nothing when sending to dev null
else:
if options.output_type.startswith('pdfa'):
pdfa_info = file_claims_pdfa(options.output_file)
if pdfa_info['pass']:
@@ -790,8 +824,6 @@ def run_pipeline():
if not qpdf.check(options.output_file, _log):
_log.warning('Output file: The generated PDF is INVALID')
return ExitCode.invalid_output_pdf
else:
_log.info("Output sent to stdout")
pdfinfo = context.get_pdfinfo()
if options.verbose:
+1 -1
View File
@@ -97,7 +97,7 @@ def verify_python3_env():
break
if bad_locale is not None:
extra += (
'\n\ocrmypdf discovered that you exported a UTF-8 locale\n'
'\nocrmypdf discovered that you exported a UTF-8 locale\n'
'but the locale system could not pick up from it because\n'
'it does not exist. The exported locale is "%s" but it\n'
'is not supported'
-1
View File
@@ -1,4 +1,3 @@
#!/usr/bin/env python3
# © 2016 James R. Barlow: github.com/jbarlow83
+9 -4
View File
@@ -1,4 +1,3 @@
#!/usr/bin/env python3
# © 2016 James R. Barlow: github.com/jbarlow83
"""Wrappers to manage subprocess calls"""
@@ -29,8 +28,14 @@ def get_version(program, *,
stdout=PIPE, stderr=STDOUT, check=True)
output = proc.stdout
except CalledProcessError as e:
raise MissingDependencyError(
"Could not find program '{}' on the PATH".format(program)) from e
if get_program(program) == program:
raise MissingDependencyError(
"Could not find program '{}' on the PATH".format(
program)) from e
else:
raise MissingDependencyError(
"Could not find program '{}'".format(
get_program(program))) from e
try:
version = re.match(regex, output.strip()).group(1)
@@ -40,4 +45,4 @@ def get_version(program, *,
"Message was:\n{}").format(program, output)
)
return version
return version
+4 -4
View File
@@ -1,4 +1,3 @@
#!/usr/bin/env python3
# © 2017 James R. Barlow: github.com/jbarlow83
from tempfile import NamedTemporaryFile
@@ -37,7 +36,7 @@ def rasterize_pdf(input_file, output_file, xres, yres, raster_device, log,
:param yres:
:param raster_device:
:param log:
:param pageno: page number to rasterize
:param pageno: page number to rasterize (beginning at page 1)
:param page_dpi: resolution tuple (x, y) overriding output image DPI
:return:
"""
@@ -55,11 +54,11 @@ def rasterize_pdf(input_file, output_file, xres, yres, raster_device, log,
'-sDEVICE=%s' % raster_device,
'-dFirstPage=%i' % pageno,
'-dLastPage=%i' % pageno,
'-o', tmp.name,
'-r{0}x{1}'.format(str(int_res[0]), str(int_res[1])),
'-o', tmp.name,
fspath(input_file)
]
p = run(args_gs, stdout=PIPE, stderr=STDOUT,
universal_newlines=True)
if _gs_error_reported(p.stdout):
@@ -75,6 +74,7 @@ def rasterize_pdf(input_file, output_file, xres, yres, raster_device, log,
# if the resolution happens to be fractional, then the discrepancy
# would change the size of the output page, especially if the DPI
# is quite low. Resize the image to the expected size
tmp.seek(0)
with Image.open(tmp) as im:
expected_size = round(im.size[0] / int_res[0] * res[0]), \
-3
View File
@@ -1,4 +1,3 @@
#!/usr/bin/env python3
# © 2017 James R. Barlow: github.com/jbarlow83
from subprocess import CalledProcessError, STDOUT, PIPE, run
@@ -68,8 +67,6 @@ def repair(input_file, output_file, log):
return
if _probably_encrypted(e):
log.error("{0}: this PDF is password-protected - password must "
"be removed for OCR".format(input_file))
raise EncryptedPdfError() from e
elif e.returncode == 2:
log.error("{0}: not a valid PDF, and could not repair it.".format(
+5 -2
View File
@@ -1,4 +1,3 @@
#!/usr/bin/env python3
# © 2017 James R. Barlow: github.com/jbarlow83
import sys
@@ -158,7 +157,7 @@ def tesseract_log_output(log, stdout, input_file):
try:
text = stdout.decode()
except UnicodeDecodeError as e:
except UnicodeDecodeError:
log.error(prefix + "command line output was not utf-8. " +
"This usually means Tesseract's language packs do not match "
"the installed version of Tesseract.")
@@ -279,6 +278,10 @@ def use_skip_page(text_only, skip_pdf, output_pdf, output_text):
with open(output_pdf, 'wb') as out:
pdf_out = pypdf.PdfFileWriter()
w, h = page0.mediaBox.getWidth(), page0.mediaBox.getHeight()
# If skip page has a /Rotate key, replicate the rotation
rotation = int(page0.get('/Rotate', 0))
if rotation % 180 == 90:
w, h = h, w
pdf_out.addBlankPage(w, h)
pdf_out.write(out)
-1
View File
@@ -1,4 +1,3 @@
#!/usr/bin/env python3
# © 2015 James R. Barlow: github.com/jbarlow83
# unpaper documentation:
# https://github.com/Flameeyes/unpaper/blob/master/doc/basic-concepts.md
+21 -7
View File
@@ -1,9 +1,9 @@
#!/usr/bin/env python3
# © 2016 James R. Barlow: github.com/jbarlow83
from functools import partial
from collections.abc import Iterable
from contextlib import suppress, contextmanager
from pathlib import Path
import sys
import os
@@ -63,19 +63,33 @@ def is_file_writable(test_file):
can replace it atomically. Before doing the OCR work, make sure
the location is writable.
"""
if os.path.exists(test_file):
p = Path(test_file)
if p.is_symlink():
# Python 3.5 does not accept parameters for Path.resolve() and behaves
# as if strict=True (throws an exception on failure). Python 3.6
# defaults to strict=False. This implements strict=False like behavior
# for Python 3.5.
if sys.version_info[0:2] <= (3, 5):
resolve = lambda: Path(os.path.realpath(str(p)))
else:
resolve = lambda: p.resolve(strict=False)
p = resolve()
# p.is_file() throws an exception in some cases
if p.exists() and p.is_file():
return os.access(
test_file, os.W_OK,
str(p), os.W_OK,
effective_ids=(os.access in os.supports_effective_ids))
else:
try:
fp = open(test_file, 'wb')
except OSError as e:
fp = p.open('wb')
except OSError:
return False
else:
fp.close()
with suppress(OSError):
os.unlink(test_file)
p.unlink()
return True
@@ -118,4 +132,4 @@ if sys.version_info[0:2] <= (3, 5):
else:
universal_open = open
fspath = os.fspath
fspath = os.fspath
+1 -32
View File
@@ -566,35 +566,4 @@ if __name__ == '__main__':
parser_deskew.set_defaults(func=deskew)
args = parser.parse_args()
if get_leptonica_version() != u'leptonica-1.69':
print("Unexpected leptonica version: %s" % getLeptonicaVersion())
args.func(args)
def test_skew_angle():
from PIL import Image, ImageDraw
from tempfile import NamedTemporaryFile
im = Image.new(mode='1', size=(1000, 1000), color=1)
draw = ImageDraw.Draw(im)
for n in range(20):
draw.line([(50, 25 + 50*n), (950, 25 + 50*n)], width=1)
del draw
test_angles = [0.1 * ang for ang in range(1, 10)] + \
[float(ang) for ang in range(1, 7)]
test_angles += [-ang for ang in test_angles]
test_angles = sorted(test_angles)
for rotate_angle in test_angles:
rotated_im = im.rotate(rotate_angle)
with NamedTemporaryFile(prefix='lept-skew', suffix='.png', delete=True) as tmpfile:
rotated_im.save(tmpfile)
pix = pixRead(tmpfile.name)
angle, confidence = pixFindSkew(pix)
print('{0} {1} {2}'.format(rotate_angle, angle, confidence), file=sys.stderr)
args.func(args)
+1
View File
@@ -1,3 +1,4 @@
#!/usr/bin/env python3
# © 2017 James R. Barlow: github.com/jbarlow83
from cffi import FFI
-1
View File
@@ -1,4 +1,3 @@
#!/usr/bin/env python3
# © 2015 James R. Barlow: github.com/jbarlow83
#
# Generate a PDFA_def.ps file for Ghostscript >= 9.14
-4
View File
@@ -662,10 +662,6 @@ class PageInfo:
else:
return '1.5'
@property
def images(self):
return self._pageinfo['images']
def __repr__(self):
return (
'<PageInfo '
+40 -27
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python3
# © 2016 James R. Barlow: github.com/jbarlow83
from contextlib import suppress
from shutil import copyfileobj
import sys
import os
import shutil
@@ -17,7 +17,8 @@ from .pdfinfo import PdfInfo, Encoding, Colorspace
from .pdfa import generate_pdfa_ps
from .helpers import re_symlink, is_iterable_notstr, page_number
from .exec import ghostscript, tesseract, qpdf
from .exceptions import *
from .exceptions import PdfMergeFailedError, UnsupportedImageFormatError, \
DpiError, PriorOcrFoundError, InputFileError
from . import leptonica
from . import PROGRAM_NAME, VERSION
@@ -159,7 +160,7 @@ def _pdf_guess_version(input_file, search_window=1024):
with open(input_file, 'rb') as f:
signature = f.read(1024)
m = re.search(b'%PDF-(\d\.\d)', signature)
m = re.search(br'%PDF-(\d\.\d)', signature)
if m:
return m.group(1)
return ''
@@ -244,7 +245,30 @@ def get_canvas_square_dpi(pageinfo, options):
def is_ocr_required(pageinfo, log, options):
page = pageinfo.pageno + 1
ocr_required = True
if not pageinfo.images:
if pageinfo.has_text:
msg = "{0:4d}: page already has text! {1}"
if not options.force_ocr and not options.skip_text:
log.error(msg.format(page,
"aborting (use --force-ocr to force OCR)"))
raise PriorOcrFoundError()
elif options.force_ocr:
log.info(msg.format(page,
"rasterizing text and running OCR anyway"))
ocr_required = True
elif options.skip_text:
log.info(msg.format(page,
"skipping all processing on this page"))
ocr_required = False
elif not pageinfo.images and not options.lossless_reconstruction:
# We found a page with no images and no text. That means it may
# have vector art that the user wants to OCR. If we determined
# lossless reconstruction is not possible then we have to rasterize
# the image. So if OCR is being forced, take that to mean YES, go
# ahead and rasterize. If not forced, then pretend there's no text
# on the page at all so we don't lose anything.
# This could be made smarter by explicitly searching for vector art.
if options.force_ocr and options.oversample:
# The user really wants to reprocess this file
log.info(
@@ -263,23 +287,9 @@ def is_ocr_required(pageinfo, log, options):
else:
log.info(
"{0:4d}: page has no images - "
"skipping all processing on this page".format(page))
ocr_required = False
elif pageinfo.has_text:
msg = "{0:4d}: page already has text! {1}"
if not options.force_ocr and not options.skip_text:
log.error(msg.format(page,
"aborting (use --force-ocr to force OCR)"))
raise PriorOcrFoundError()
elif options.force_ocr:
log.info(msg.format(page,
"rasterizing text and running OCR anyway"))
ocr_required = True
elif options.skip_text:
log.info(msg.format(page,
"skipping all processing on this page"))
"skipping all processing on this page to avoid losing detail. "
"Use --force-ocr if you wish to perform OCR on pages that "
"have vector content.".format(page))
ocr_required = False
if ocr_required and options.skip_big and pageinfo.images:
@@ -991,13 +1001,16 @@ def copy_final(
context):
input_file = next((ii for ii in input_files if ii.endswith('.pdf')))
if output_file == '-':
from shutil import copyfileobj
with open(input_file, 'rb') as input_stream:
with open(input_file, 'rb') as input_stream:
if output_file == '-':
copyfileobj(input_stream, sys.stdout.buffer)
sys.stdout.flush()
else:
shutil.copy(input_file, output_file)
else:
# At this point we overwrite the output_file specified by the user
# use copyfileobj because then we use open() to create the file and
# get the appropriate umask, ownership, etc.
with open(output_file, 'wb') as output_stream:
copyfileobj(input_stream, output_stream)
def build_pipeline(options, work_folder, log, context):
@@ -1226,7 +1239,7 @@ def build_pipeline(options, work_folder, log, context):
task_merge_sidecars.active_if(options.sidecar)
# Finalize
task_copy_final = main_pipeline.merge(
main_pipeline.merge(
task_func=copy_final,
input=[task_merge_pages_ghostscript, task_merge_pages_qpdf],
output=options.output_file,
+1 -1
View File
@@ -2,7 +2,7 @@
# setup.py lists a separate set of requirements that are looser to simplify
# installation
ruffus == 2.6.3
Pillow == 4.3.0
Pillow == 5.0.0
reportlab == 3.4.0
PyPDF2 == 1.26.0
img2pdf == 0.2.4
-1
View File
@@ -1,4 +1,3 @@
#!/usr/bin/env python3
# © 2017 James R. Barlow: github.com/jbarlow83
import sys
+5 -2
View File
@@ -33,7 +33,7 @@ In some cases they were converted from one image format to another without other
- Creative Commons BY-SA 3.0
* - typewriter.png, 2400dpi.pdf
- `Wikimedia: Triumph typewrtier text Linzensoep`_
* Creative Commons BY-SA 2.5
- Creative Commons BY-SA 2.5
* - baiona.png
- `Wikimedia: Baionako udalerri mugakideak`_
- Creative Commons BY-SA 4.0
@@ -100,6 +100,9 @@ under the terms of the license in LICENSE.rst.
* - trivial.pdf
- @jbarlow83
- smallest possible valid PDF-1.3 with all required fields
* - vector.pdf
- @Catscratch
- a PDF with vector art and text rendered as curves with no fonts
Assemblies
@@ -116,7 +119,7 @@ These test resources are assemblies or derivatives from other previously mention
- palette.pdf (congress.jpg, converted to a 256-color palette)
- rotated_skew.pdf (a /Rotate'd and skewed document from LinnSequencer.jpg)
- skew.pdf (from LinnSequencer.jpg, skew simulated by adjusting the transformation matrix)
- skew-encrypted.pdf (skew.pdf with encryption - access supported by PyPDF2)
- skew-encrypted.pdf (skew.pdf with encryption - access supported by PyPDF2, password is "password")
- baiona_gray.png (from baiona.png)
- poster.pdf (from LinnSequencer.jpg)
Binary file not shown.
+1 -1
View File
@@ -22,7 +22,7 @@ not permitted in PDF/A-2, overprint mode not set"""
def main():
if '--version' in sys.argv:
print('9.20')
print('SPOOFED: ' + os.path.basename(__filename__))
print('SPOOFED: ' + os.path.basename(__file__))
sys.exit(0)
gs_args = ['gs'] + sys.argv[1:]
+1 -1
View File
@@ -17,7 +17,7 @@ def real_ghostscript(argv):
def main():
if '--version' in sys.argv:
print('9.20')
print('SPOOFED: ' + os.path.basename(__filename__))
print('SPOOFED: ' + os.path.basename(__file__))
sys.exit(0)
# Unless some argument is calling for PDFA generation, forward to
+1 -1
View File
@@ -16,7 +16,7 @@ def real_ghostscript(argv):
def main():
if '--version' in sys.argv:
print('9.20')
print('SPOOFED: ' + os.path.basename(__filename__))
print('SPOOFED: ' + os.path.basename(__file__))
sys.exit(0)
# For any rendering calls (device == pdfwrite) call real ghostscript
+2 -2
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# © 2016 James R. Barlow: github.com/jbarlow83
# © 2016-18 James R. Barlow: github.com/jbarlow83
import sys
import os
@@ -16,7 +16,7 @@ def real_ghostscript(argv):
def main():
if '--version' in sys.argv:
print('9.20')
print('SPOOFED: ' + os.path.basename(__filename__))
print('SPOOFED: ' + os.path.basename(__file__))
sys.exit(0)
# For any rasterize calls (device != pdfwrite) call real ghostscript
+1 -1
View File
@@ -40,7 +40,7 @@ def main():
sys.stdout.buffer.write(BAD_UTF8)
sys.exit(1)
elif sys.argv[-1] == 'stdout':
inputf = sys.argv[-2]
# input file is at sys.argv[-2] but we don't look at it
print("""Orientation: 0
Orientation in degrees: 0
Orientation confidence: 100.00
+3 -2
View File
@@ -73,10 +73,11 @@ def main():
output = sys.argv[-3]
with Image.open(inputf) as im:
dpi = im.info['dpi']
imsize = im.size[0] * dpi[0] / 72, im.size[1] * dpi[1] / 72
pagesize = im.size[0] / dpi[0], im.size[1] / dpi[1]
ptsize = pagesize[0] * 72, pagesize[1] * 72
pdf_out = pypdf.PdfFileWriter()
pdf_out.addBlankPage(imsize[0], imsize[1])
pdf_out.addBlankPage(ptsize[0], ptsize[1])
with open(output + '.pdf', 'wb') as f:
pdf_out.write(f)
with open(output + '.txt', 'w') as f:
-1
View File
@@ -1,4 +1,3 @@
#!/usr/bin/env python3
# © 2015 James R. Barlow: github.com/jbarlow83
from ocrmypdf import hocrtransform
+110 -8
View File
@@ -1,6 +1,7 @@
# © 2015-17 James R. Barlow: github.com/jbarlow83
from subprocess import Popen, PIPE, check_output, check_call, DEVNULL
from pathlib import Path
import os
import shutil
import resource
@@ -14,6 +15,11 @@ from ocrmypdf.exec import ghostscript, tesseract, qpdf
import logging
from math import isclose
import PIL
# pytest.helpers is dynamic
# pylint: disable=no-member
# pylint: disable=w0612
check_ocrmypdf = pytest.helpers.check_ocrmypdf
run_ocrmypdf = pytest.helpers.run_ocrmypdf
@@ -69,11 +75,10 @@ def test_quick(spoof_tesseract_cache, resources, outpdf):
check_ocrmypdf(resources / 'ccitt.pdf', outpdf, env=spoof_tesseract_cache)
@pytest.mark.filterwarnings('ignore:Image size')
def test_deskew(spoof_tesseract_noop, resources, outdir):
# Run with deskew
deskewed_pdf = check_ocrmypdf(
resources / 'skew.pdf', outdir / 'skew.pdf', '-d', '-v', '1',
resources / 'skew.pdf', outdir / 'skew.pdf', '-d',
env=spoof_tesseract_noop)
# Now render as an image again and use Leptonica to find the skew angle
@@ -88,7 +93,8 @@ def test_deskew(spoof_tesseract_noop, resources, outdir):
xres=150,
yres=150,
raster_device='pngmono',
log=log)
log=log,
pageno=1)
from ocrmypdf.leptonica import Pix
pix = Pix.read(str(deskewed_png))
@@ -127,8 +133,8 @@ def test_remove_background(spoof_tesseract_noop, resources, outdir):
xres=100,
yres=100,
raster_device='png16m',
log=log)
log=log,
pageno=1)
# The output image should contain pure white and black
im = Image.open(output_png)
@@ -431,7 +437,7 @@ def test_klingon(resources, outpdf):
def test_missing_docinfo(spoof_tesseract_noop, resources, outpdf):
p, out, err = run_ocrmypdf(
resources / 'missing_docinfo.pdf', outpdf, '-l', 'eng', '-c',
resources / 'missing_docinfo.pdf', outpdf, '-l', 'eng', '--skip-text',
env=spoof_tesseract_noop)
assert p.returncode == ExitCode.ok, err
@@ -478,7 +484,7 @@ def test_encrypted(resources, no_outpdf):
p, out, err = run_ocrmypdf(
resources / 'skew-encrypted.pdf', no_outpdf)
assert p.returncode == ExitCode.encrypted_pdf
assert out.find('password')
assert out.find('encrypted')
@pytest.mark.parametrize('renderer', [
@@ -521,7 +527,9 @@ def test_tesseract_image_too_big(renderer, spoof_tesseract_big_image_error,
resources, outpdf):
check_ocrmypdf(
resources / 'hugemono.pdf', outpdf, '-r',
'--pdf-renderer', renderer, env=spoof_tesseract_big_image_error)
'--pdf-renderer', renderer,
'--max-image-mpixels', '0',
env=spoof_tesseract_big_image_error)
def test_no_unpaper(resources, no_outpdf):
@@ -877,6 +885,8 @@ def test_gs_raster_failure(spoof_no_tess_gs_raster_fail, resources, outpdf):
assert p.returncode == ExitCode.child_process_error
@pytest.mark.skipif('8.0.0' <= qpdf.version() <= '8.0.1',
reason="qpdf regression")
def test_no_contents(spoof_tesseract_noop, resources, outpdf):
check_ocrmypdf(resources / 'no_contents.pdf', outpdf, '--force-ocr',
env=spoof_tesseract_noop)
@@ -1037,3 +1047,95 @@ def test_bad_utf8(spoof_tess_bad_utf8, renderer, resources, no_outpdf):
assert p.returncode != 0
assert 'not utf-8' in err, "should whine about utf-8"
assert '\\x96' in err, 'should repeat backslash encoded output'
@pytest.mark.skipif(
not tesseract.has_textonly_pdf(),
reason="issue only affects sandwich")
def test_rotate_deskew_timeout(resources, outdir):
check_ocrmypdf(
resources / 'rotated_skew.pdf',
outdir / 'deskewed.pdf',
'--deskew',
'--tesseract-timeout', '0',
'--pdf-renderer', 'sandwich'
)
correlation = check_monochrome_correlation(
outdir,
reference_pdf=resources / 'ccitt.pdf',
reference_pageno=1,
test_pdf=outdir / 'deskewed.pdf',
test_pageno=1)
# Confirm that the page still got deskewed
assert correlation > 0.50
@pytest.mark.skipif(
PIL.PILLOW_VERSION < '5.0.0',
reason="Pillow < 5.0.0 doesn't raise the exception")
def test_decompression_bomb(resources, outpdf):
p, out, err = run_ocrmypdf(
resources / 'hugemono.pdf',
outpdf
)
assert 'decompression bomb' in err
p, out, err = run_ocrmypdf(
resources / 'hugemono.pdf',
outpdf,
'--max-image-mpixels', '2000'
)
assert p.returncode == 0
def test_text_curves(spoof_tesseract_noop, resources, outpdf):
check_ocrmypdf(
resources / 'vector.pdf', outpdf, env=spoof_tesseract_noop)
info = PdfInfo(outpdf)
assert len(info.pages[0].images) == 0, "added images to the vector PDF"
check_ocrmypdf(
resources / 'vector.pdf', outpdf, '--force-ocr',
env=spoof_tesseract_noop)
info = PdfInfo(outpdf)
assert len(info.pages[0].images) != 0, "force did not rasterize"
def test_dev_null(spoof_tesseract_noop, resources):
p, out, err = run_ocrmypdf(
resources / 'trivial.pdf',
os.devnull,
'--force-ocr',
env=spoof_tesseract_noop
)
assert p.returncode == 0, "could not send output to /dev/null"
assert len(out) == 0, "wrote to stdout"
def test_output_is_dir(spoof_tesseract_noop, resources, outdir):
p, out, err = run_ocrmypdf(
resources / 'trivial.pdf',
outdir,
'--force-ocr',
env=spoof_tesseract_noop
)
assert p.returncode == ExitCode.file_access_error
assert 'is not a writable file' in err
def test_output_is_symlink(spoof_tesseract_noop, resources, outdir):
sym = Path(outdir / 'this_is_a_symlink')
sym.symlink_to(outdir / 'out.pdf')
p, out, err = run_ocrmypdf(
resources / 'trivial.pdf',
sym,
'--force-ocr',
env=spoof_tesseract_noop
)
assert p.returncode == ExitCode.ok, err
assert (outdir / 'out.pdf').stat().st_size > 0, 'target file not created'
+1 -2
View File
@@ -1,4 +1,3 @@
#!/usr/bin/env python3
# © 2015 James R. Barlow: github.com/jbarlow83
from ocrmypdf import pdfinfo
@@ -122,4 +121,4 @@ def test_no_contents(resources):
def test_oversized_page(resources):
pdf = pdfinfo.PdfInfo(resources / 'poster.pdf')
image = pdf[0].images[0]
assert image.width * image.xres > 200, "this is supposed to be oversized"
assert image.width * image.xres > 200, "this is supposed to be oversized"
-1
View File
@@ -1,4 +1,3 @@
#!/usr/bin/env python3
# © 2017 James R. Barlow: github.com/jbarlow83
import pytest
+2 -3
View File
@@ -1,4 +1,3 @@
#!/usr/bin/env python3
# © 2017 James R. Barlow: github.com/jbarlow83
import pytest
@@ -10,7 +9,7 @@ import os
import PyPDF2 as pypdf
from contextlib import contextmanager
# pylint: disable=no-member
spoof = pytest.helpers.spoof
@@ -128,4 +127,4 @@ def test_content_preservation(ensure_tess4, resources, outpdf):
info = pdfinfo.PdfInfo(outpdf)
page = info[0]
assert len(page.images) > 1, "masks were rasterized"
assert len(page.images) > 1, "masks were rasterized"
-1
View File
@@ -1,4 +1,3 @@
#!/usr/bin/env python3
# © 2017 James R. Barlow: github.com/jbarlow83
from subprocess import Popen, PIPE, check_output, check_call, DEVNULL