Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
79382a6039 | ||
|
|
7788d94c4a | ||
|
|
33bfba8449 | ||
|
|
1d0584c644 | ||
|
|
84b9d4d021 | ||
|
|
41efd3bf0f | ||
|
|
776ada6713 | ||
|
|
f3593c915d | ||
|
|
dfe31a2f6d | ||
|
|
0c43963d69 | ||
|
|
f29fe7f23e | ||
|
|
04996caac3 |
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
# OCRmyPDF
|
# OCRmyPDF
|
||||||
#
|
#
|
||||||
|
|
||||||
FROM ubuntu:21.04 as base
|
FROM debian:bookworm-slim as base
|
||||||
|
|
||||||
ENV LANG=C.UTF-8
|
ENV LANG=C.UTF-8
|
||||||
ENV TZ=UTC
|
ENV TZ=UTC
|
||||||
|
|||||||
@@ -21,17 +21,19 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- os: ubuntu-18.04
|
- os: ubuntu-18.04
|
||||||
python: 3.7
|
python: "3.7"
|
||||||
- os: ubuntu-20.04
|
- os: ubuntu-20.04
|
||||||
python: 3.8
|
python: "3.8"
|
||||||
- os: ubuntu-20.04
|
- os: ubuntu-20.04
|
||||||
python: 3.9
|
python: "3.9"
|
||||||
|
- os: ubuntu-20.04
|
||||||
|
python: "3.10"
|
||||||
- os: ubuntu-latest
|
- os: ubuntu-latest
|
||||||
python: 3.9
|
python: "3.9"
|
||||||
- os: ubuntu-latest
|
- os: ubuntu-latest
|
||||||
python: "pypy-3.7"
|
python: "pypy-3.7"
|
||||||
- os: ubuntu-latest
|
- os: ubuntu-latest
|
||||||
python: 3.9
|
python: "3.9"
|
||||||
tesseract5: true
|
tesseract5: true
|
||||||
|
|
||||||
env:
|
env:
|
||||||
@@ -120,7 +122,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [macos-latest]
|
os: [macos-latest]
|
||||||
python: ["3.9"]
|
python: ["3.9", "3.10"]
|
||||||
|
|
||||||
env:
|
env:
|
||||||
OS: ${{ matrix.os }}
|
OS: ${{ matrix.os }}
|
||||||
@@ -175,7 +177,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [windows-latest]
|
os: [windows-latest]
|
||||||
python: ["3.9"]
|
python: ["3.9", "3.10"]
|
||||||
|
|
||||||
env:
|
env:
|
||||||
OS: ${{ matrix.os }}
|
OS: ${{ matrix.os }}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
rev: v4.0.1
|
rev: v4.1.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: check-case-conflict
|
- id: check-case-conflict
|
||||||
- id: check-merge-conflict
|
- id: check-merge-conflict
|
||||||
@@ -8,26 +8,26 @@ repos:
|
|||||||
- id: check-yaml
|
- id: check-yaml
|
||||||
- id: debug-statements
|
- id: debug-statements
|
||||||
- repo: https://github.com/pycqa/isort
|
- repo: https://github.com/pycqa/isort
|
||||||
rev: 5.9.3
|
rev: 5.10.1
|
||||||
hooks:
|
hooks:
|
||||||
- id: isort
|
- id: isort
|
||||||
args: ["--profile", "black"]
|
args: ["--profile", "black"]
|
||||||
- repo: https://github.com/psf/black
|
- repo: https://github.com/psf/black
|
||||||
rev: 21.9b0
|
rev: 22.3.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: black
|
- id: black
|
||||||
language_version: python
|
language_version: python
|
||||||
- repo: https://github.com/asottile/setup-cfg-fmt
|
- repo: https://github.com/asottile/setup-cfg-fmt
|
||||||
rev: v1.19.0
|
rev: v1.20.1
|
||||||
hooks:
|
hooks:
|
||||||
- id: setup-cfg-fmt
|
- id: setup-cfg-fmt
|
||||||
- repo: https://github.com/asottile/pyupgrade
|
- repo: https://github.com/asottile/pyupgrade
|
||||||
rev: v2.29.0
|
rev: v2.31.1
|
||||||
hooks:
|
hooks:
|
||||||
- id: pyupgrade
|
- id: pyupgrade
|
||||||
args: ["--py37-plus"]
|
args: ["--py37-plus"]
|
||||||
- repo: https://github.com/pre-commit/mirrors-mypy
|
- repo: https://github.com/pre-commit/mirrors-mypy
|
||||||
rev: v0.910-1
|
rev: v0.942
|
||||||
hooks:
|
hooks:
|
||||||
- id: mypy
|
- id: mypy
|
||||||
additional_dependencies:
|
additional_dependencies:
|
||||||
|
|||||||
+28
-4
@@ -10,7 +10,31 @@ that is, output messages may be improved at any release level, so parsing them
|
|||||||
may be unreliable. Use the API to depend on precise behavior.
|
may be unreliable. Use the API to depend on precise behavior.
|
||||||
|
|
||||||
The public API may be useful in scripts that launch OCRmyPDF processes or that
|
The public API may be useful in scripts that launch OCRmyPDF processes or that
|
||||||
wish to use some of its features for working with PDFs..
|
wish to use some of its features for working with PDFs.
|
||||||
|
|
||||||
|
The most recent release of OCRmyPDF is |OCRmyPDF PyPI|. Any newer versions
|
||||||
|
referred to in these notes may exist the main branch but have not been
|
||||||
|
tagged yet.
|
||||||
|
|
||||||
|
.. |OCRmyPDF PyPI| image:: https://img.shields.io/pypi/v/ocrmypdf.svg
|
||||||
|
|
||||||
|
v13.4.2
|
||||||
|
=======
|
||||||
|
|
||||||
|
- Worked around a
|
||||||
|
`major regression in Ghostscript 9.56.0 <https://bugs.ghostscript.com/show_bug.cgi?id=705187>`__
|
||||||
|
where **all OCR text is stripped out of the PDF**. It simply removes all text,
|
||||||
|
even generated by software other than OCRmyPDF. Fortunately, we can ask
|
||||||
|
Ghostscript 9.56.0 to use its old behavior that worked correctly for our purposes.
|
||||||
|
Users must avoid the combination (Ghostscript 9.56.0, ocrmypdf <13.4.2) since
|
||||||
|
older versions of OCRmyPDF have no way of detecting that this particular
|
||||||
|
version of Ghostscript removes all OCR text.
|
||||||
|
- Marked pdfminer 20220319 as supported.
|
||||||
|
- Fixed some deprecation warnings from recent versions of Pillow and pytest.
|
||||||
|
- Test suite now covers Python 3.10 (Python 3.10 worked fine before, but was not
|
||||||
|
being tested).
|
||||||
|
- Docker image now uses debian:bookworm-slim as the base image to fix the Docker
|
||||||
|
image build.
|
||||||
|
|
||||||
v13.4.1
|
v13.4.1
|
||||||
=======
|
=======
|
||||||
@@ -24,9 +48,9 @@ v13.4.0
|
|||||||
|
|
||||||
- Fixed test failures when using pikepdf 5.0.0.
|
- Fixed test failures when using pikepdf 5.0.0.
|
||||||
- Various improvements to the optimizer. In particular, we now recognize PDF images
|
- Various improvements to the optimizer. In particular, we now recognize PDF images
|
||||||
that are encoded with both flate and DCT (JPEG), and also produce PDF with images
|
that are encoded with both deflate (PNG) and DCT (JPEG), and also produce PDF
|
||||||
compressed with flate and DCT, since this often yields file size improvements
|
with images compressed with deflate and DCT, since this often yields file size
|
||||||
compared to plain DCT.
|
improvements compared to plain DCT.
|
||||||
|
|
||||||
v13.3.0
|
v13.3.0
|
||||||
=======
|
=======
|
||||||
|
|||||||
+1
-1
@@ -59,7 +59,7 @@ def do_ocrmypdf(file):
|
|||||||
return Response("--sidecar not supported", 501, mimetype='text/plain')
|
return Response("--sidecar not supported", 501, mimetype='text/plain')
|
||||||
|
|
||||||
ocrmypdf_args = ["ocrmypdf", *cmd_args, up_file, down_file]
|
ocrmypdf_args = ["ocrmypdf", *cmd_args, up_file, down_file]
|
||||||
proc = run(ocrmypdf_args, stdout=PIPE, stderr=PIPE, encoding="utf-8")
|
proc = run(ocrmypdf_args, capture_output=True, encoding="utf-8")
|
||||||
if proc.returncode != 0:
|
if proc.returncode != 0:
|
||||||
stderr = proc.stderr
|
stderr = proc.stderr
|
||||||
return Response(stderr, 400, mimetype='text/plain')
|
return Response(stderr, 400, mimetype='text/plain')
|
||||||
|
|||||||
@@ -49,8 +49,8 @@ install_requires =
|
|||||||
coloredlogs>=14.0 # strictly optional
|
coloredlogs>=14.0 # strictly optional
|
||||||
img2pdf>=0.3.0,<0.5 # pure Python
|
img2pdf>=0.3.0,<0.5 # pure Python
|
||||||
packaging>=20
|
packaging>=20
|
||||||
pdfminer.six!=20200720,>=20191110,<=20211012
|
pdfminer.six!=20200720,>=20191110,<=20220319
|
||||||
pikepdf>=4.0.0,!=5.0.0
|
pikepdf!=5.0.0,>=4.0.0
|
||||||
pluggy>=0.13.0,<2
|
pluggy>=0.13.0,<2
|
||||||
reportlab>=3.5.66
|
reportlab>=3.5.66
|
||||||
tqdm>=4
|
tqdm>=4
|
||||||
|
|||||||
@@ -24,6 +24,13 @@ from ocrmypdf.exceptions import MissingDependencyError, SubprocessOutputError
|
|||||||
from ocrmypdf.helpers import Resolution
|
from ocrmypdf.helpers import Resolution
|
||||||
from ocrmypdf.subprocess import get_version, run, run_polling_stderr
|
from ocrmypdf.subprocess import get_version, run, run_polling_stderr
|
||||||
|
|
||||||
|
# Remove this workaround when we require Pillow >= 10
|
||||||
|
try:
|
||||||
|
Transpose = Image.Transpose # type: ignore
|
||||||
|
except AttributeError:
|
||||||
|
# Pillow 9 shim
|
||||||
|
Transpose = Image # type: ignore
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
missing_gs_error = """
|
missing_gs_error = """
|
||||||
@@ -132,11 +139,11 @@ def rasterize_pdf(
|
|||||||
# rotation is a clockwise angle and Image.ROTATE_* is
|
# rotation is a clockwise angle and Image.ROTATE_* is
|
||||||
# counterclockwise so this cancels out the rotation
|
# counterclockwise so this cancels out the rotation
|
||||||
if rotation == 90:
|
if rotation == 90:
|
||||||
im = im.transpose(Image.ROTATE_90)
|
im = im.transpose(Transpose.ROTATE_90)
|
||||||
elif rotation == 180:
|
elif rotation == 180:
|
||||||
im = im.transpose(Image.ROTATE_180)
|
im = im.transpose(Transpose.ROTATE_180)
|
||||||
elif rotation == 270:
|
elif rotation == 270:
|
||||||
im = im.transpose(Image.ROTATE_270)
|
im = im.transpose(Transpose.ROTATE_270)
|
||||||
if rotation % 180 == 90:
|
if rotation % 180 == 90:
|
||||||
page_dpi = page_dpi.flip_axis()
|
page_dpi = page_dpi.flip_axis()
|
||||||
im.save(fspath(output_file), dpi=page_dpi)
|
im.save(fspath(output_file), dpi=page_dpi)
|
||||||
@@ -210,14 +217,18 @@ def generate_pdfa(
|
|||||||
# Older versions of Ghostscript expect a leading slash in
|
# Older versions of Ghostscript expect a leading slash in
|
||||||
# sColorConversionStrategy, newer ones should not have it. See Ghostscript
|
# sColorConversionStrategy, newer ones should not have it. See Ghostscript
|
||||||
# git commit fe1c025d.
|
# git commit fe1c025d.
|
||||||
strategy = ('/' + strategy) if version() < '9.19' else strategy
|
gs_version = version()
|
||||||
|
strategy = ('/' + strategy) if gs_version < '9.19' else strategy
|
||||||
|
|
||||||
if version() == '9.23':
|
if gs_version == '9.23':
|
||||||
# 9.23: added JPEG passthrough as a new feature, but with a bug that
|
# 9.23: added JPEG passthrough as a new feature, but with a bug that
|
||||||
# incorrectly formats some images. Fixed as of 9.24. So we disable this
|
# incorrectly formats some images. Fixed as of 9.24. So we disable this
|
||||||
# feature for 9.23.
|
# feature for 9.23.
|
||||||
# https://bugs.ghostscript.com/show_bug.cgi?id=699216
|
# https://bugs.ghostscript.com/show_bug.cgi?id=699216
|
||||||
compression_args.append('-dPassThroughJPEGImages=false')
|
compression_args.append('-dPassThroughJPEGImages=false')
|
||||||
|
elif gs_version == '9.56.0':
|
||||||
|
# 9.56.0 breaks our OCR...?
|
||||||
|
compression_args.append('-dNEWPDF=false')
|
||||||
|
|
||||||
# nb no need to specify ProcessColorModel when ColorConversionStrategy
|
# nb no need to specify ProcessColorModel when ColorConversionStrategy
|
||||||
# is set; see:
|
# is set; see:
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ from decimal import Decimal
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from subprocess import PIPE, STDOUT
|
from subprocess import PIPE, STDOUT
|
||||||
from tempfile import TemporaryDirectory
|
from tempfile import TemporaryDirectory
|
||||||
from typing import List, Optional, Tuple, Union
|
from typing import Iterator, List, Optional, Tuple, Union
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
@@ -76,7 +76,7 @@ def _convert_image(im: Image.Image) -> Tuple[Image.Image, bool, str]:
|
|||||||
|
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def _setup_unpaper_io(input_file: Path) -> Tuple[Path, Path, Path]:
|
def _setup_unpaper_io(input_file: Path) -> Iterator[Tuple[Path, Path, Path]]:
|
||||||
with Image.open(input_file) as im:
|
with Image.open(input_file) as im:
|
||||||
if im.width * im.height >= UNPAPER_IMAGE_PIXEL_LIMIT:
|
if im.width * im.height >= UNPAPER_IMAGE_PIXEL_LIMIT:
|
||||||
raise UnpaperImageTooLargeError(w=im.width, h=im.height)
|
raise UnpaperImageTooLargeError(w=im.width, h=im.height)
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ from typing import Dict, Iterable, Optional
|
|||||||
import img2pdf
|
import img2pdf
|
||||||
import pikepdf
|
import pikepdf
|
||||||
from pikepdf.models.metadata import encode_pdf_date
|
from pikepdf.models.metadata import encode_pdf_date
|
||||||
from PIL import Image, ImageDraw
|
from PIL import Image, ImageColor, ImageDraw
|
||||||
|
|
||||||
from ocrmypdf._concurrent import Executor
|
from ocrmypdf._concurrent import Executor
|
||||||
from ocrmypdf._exec import unpaper
|
from ocrmypdf._exec import unpaper
|
||||||
@@ -38,6 +38,13 @@ from ocrmypdf.optimize import optimize
|
|||||||
from ocrmypdf.pdfa import generate_pdfa_ps
|
from ocrmypdf.pdfa import generate_pdfa_ps
|
||||||
from ocrmypdf.pdfinfo import Colorspace, Encoding, PdfInfo
|
from ocrmypdf.pdfinfo import Colorspace, Encoding, PdfInfo
|
||||||
|
|
||||||
|
# Remove this workaround when we require Pillow >= 10
|
||||||
|
try:
|
||||||
|
BICUBIC = Image.Resampling.BICUBIC # type: ignore
|
||||||
|
except AttributeError:
|
||||||
|
# Pillow 9 shim
|
||||||
|
BICUBIC = Image.BICUBIC # type: ignore
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
VECTOR_PAGE_DPI = 400
|
VECTOR_PAGE_DPI = 400
|
||||||
@@ -484,7 +491,9 @@ def preprocess_deskew(input_file: Path, page_context: PageContext):
|
|||||||
# According to Pillow docs, .rotate() will automatically use Image.NEAREST
|
# According to Pillow docs, .rotate() will automatically use Image.NEAREST
|
||||||
# resampling if image is mode '1' or 'P'
|
# resampling if image is mode '1' or 'P'
|
||||||
deskewed = im.rotate(
|
deskewed = im.rotate(
|
||||||
deskew_angle_degrees, resample=Image.BICUBIC, fillcolor='white'
|
deskew_angle_degrees,
|
||||||
|
resample=BICUBIC,
|
||||||
|
fillcolor=ImageColor.getcolor('white', mode=im.mode),
|
||||||
)
|
)
|
||||||
deskewed.save(output_file, dpi=dpi)
|
deskewed.save(output_file, dpi=dpi)
|
||||||
|
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ log = logging.getLogger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
def check_platform():
|
def check_platform():
|
||||||
if os.name == 'nt' and sys.maxsize <= 2 ** 32: # pragma: no cover
|
if os.name == 'nt' and sys.maxsize <= 2**32: # pragma: no cover
|
||||||
# 32-bit interpreter on Windows
|
# 32-bit interpreter on Windows
|
||||||
log.error(
|
log.error(
|
||||||
"You are running OCRmyPDF in a 32-bit (x86) Python interpreter."
|
"You are running OCRmyPDF in a 32-bit (x86) Python interpreter."
|
||||||
|
|||||||
@@ -647,8 +647,8 @@ def _pdf_pageinfo_concurrent(
|
|||||||
max_workers,
|
max_workers,
|
||||||
check_pages,
|
check_pages,
|
||||||
detailed_analysis=False,
|
detailed_analysis=False,
|
||||||
) -> List[Optional['PageInfo']]:
|
) -> Sequence[Optional['PageInfo']]:
|
||||||
pages = [None] * len(pdf.pages)
|
pages: Sequence[Optional['PageInfo']] = [None] * len(pdf.pages)
|
||||||
|
|
||||||
def update_pageinfo(result, pbar):
|
def update_pageinfo(result, pbar):
|
||||||
page = result
|
page = result
|
||||||
@@ -925,11 +925,11 @@ class PdfInfo:
|
|||||||
@property
|
@property
|
||||||
def min_version(self) -> str:
|
def min_version(self) -> str:
|
||||||
# The minimum PDF is the maximum version that any particular page needs
|
# The minimum PDF is the maximum version that any particular page needs
|
||||||
return max(page.min_version for page in self.pages)
|
return max(page.min_version for page in self.pages if page)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def has_userunit(self) -> bool:
|
def has_userunit(self) -> bool:
|
||||||
return any(page.userunit != 1.0 for page in self.pages)
|
return any(page.userunit != 1.0 for page in self.pages if page)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def has_acroform(self) -> bool:
|
def has_acroform(self) -> bool:
|
||||||
|
|||||||
@@ -4,6 +4,9 @@
|
|||||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
# type: ignore
|
||||||
|
# Non-Windows mypy now breaks when trying to typecheck winreg
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
@@ -17,6 +20,7 @@ try:
|
|||||||
except ModuleNotFoundError as e:
|
except ModuleNotFoundError as e:
|
||||||
raise ModuleNotFoundError("This module is for Windows only") from e
|
raise ModuleNotFoundError("This module is for Windows only") from e
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
T = TypeVar('T')
|
T = TypeVar('T')
|
||||||
|
|||||||
+1
-2
@@ -134,8 +134,7 @@ def run_ocrmypdf(
|
|||||||
|
|
||||||
p = run(
|
p = run(
|
||||||
p_args,
|
p_args,
|
||||||
stdout=PIPE,
|
capture_output=True,
|
||||||
stderr=PIPE,
|
|
||||||
text=text,
|
text=text,
|
||||||
check=False,
|
check=False,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ from functools import partial
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from subprocess import PIPE, CalledProcessError, CompletedProcess
|
from subprocess import PIPE, CalledProcessError, CompletedProcess
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
import threading
|
||||||
|
|
||||||
from ocrmypdf import hookimpl
|
from ocrmypdf import hookimpl
|
||||||
from ocrmypdf.builtin_plugins.tesseract_ocr import TesseractOcrEngine
|
from ocrmypdf.builtin_plugins.tesseract_ocr import TesseractOcrEngine
|
||||||
@@ -176,26 +177,28 @@ def cached_run(options, run_args, **run_kwargs):
|
|||||||
|
|
||||||
|
|
||||||
class CacheOcrEngine(TesseractOcrEngine):
|
class CacheOcrEngine(TesseractOcrEngine):
|
||||||
|
lock = threading.Lock()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_orientation(input_file, options):
|
def get_orientation(input_file, options):
|
||||||
with patch('ocrmypdf._exec.tesseract.run', new=partial(cached_run, options)):
|
with CacheOcrEngine.lock, patch('ocrmypdf._exec.tesseract.run', new=partial(cached_run, options)):
|
||||||
return TesseractOcrEngine.get_orientation(input_file, options)
|
return TesseractOcrEngine.get_orientation(input_file, options)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_deskew(input_file, options) -> float:
|
def get_deskew(input_file, options) -> float:
|
||||||
with patch('ocrmypdf._exec.tesseract.run', new=partial(cached_run, options)):
|
with CacheOcrEngine.lock, patch('ocrmypdf._exec.tesseract.run', new=partial(cached_run, options)):
|
||||||
return TesseractOcrEngine.get_deskew(input_file, options)
|
return TesseractOcrEngine.get_deskew(input_file, options)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def generate_hocr(input_file, output_hocr, output_text, options):
|
def generate_hocr(input_file, output_hocr, output_text, options):
|
||||||
with patch('ocrmypdf._exec.tesseract.run', new=partial(cached_run, options)):
|
with CacheOcrEngine.lock, patch('ocrmypdf._exec.tesseract.run', new=partial(cached_run, options)):
|
||||||
TesseractOcrEngine.generate_hocr(
|
TesseractOcrEngine.generate_hocr(
|
||||||
input_file, output_hocr, output_text, options
|
input_file, output_hocr, output_text, options
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def generate_pdf(input_file, output_pdf, output_text, options):
|
def generate_pdf(input_file, output_pdf, output_text, options):
|
||||||
with patch('ocrmypdf._exec.tesseract.run', new=partial(cached_run, options)):
|
with CacheOcrEngine.lock, patch('ocrmypdf._exec.tesseract.run', new=partial(cached_run, options)):
|
||||||
TesseractOcrEngine.generate_pdf(
|
TesseractOcrEngine.generate_pdf(
|
||||||
input_file, output_pdf, output_text, options
|
input_file, output_pdf, output_text, options
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -24,8 +24,7 @@ def test_fish():
|
|||||||
['fish', '-n', 'misc/completion/ocrmypdf.fish'],
|
['fish', '-n', 'misc/completion/ocrmypdf.fish'],
|
||||||
check=True,
|
check=True,
|
||||||
encoding='utf-8',
|
encoding='utf-8',
|
||||||
stdout=PIPE,
|
capture_output=True,
|
||||||
stderr=PIPE,
|
|
||||||
)
|
)
|
||||||
assert proc.stderr == '', proc.stderr
|
assert proc.stderr == '', proc.stderr
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
@@ -41,8 +40,7 @@ def test_bash():
|
|||||||
['bash', '-n', 'misc/completion/ocrmypdf.bash'],
|
['bash', '-n', 'misc/completion/ocrmypdf.bash'],
|
||||||
check=True,
|
check=True,
|
||||||
encoding='utf-8',
|
encoding='utf-8',
|
||||||
stdout=PIPE,
|
capture_output=True,
|
||||||
stderr=PIPE,
|
|
||||||
)
|
)
|
||||||
assert proc.stderr == '', proc.stderr
|
assert proc.stderr == '', proc.stderr
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
|
|||||||
+2
-4
@@ -630,8 +630,7 @@ def test_compression_preserved(ocrmypdf_exec, resources, image, outpdf):
|
|||||||
]
|
]
|
||||||
p = run(
|
p = run(
|
||||||
p_args,
|
p_args,
|
||||||
stdout=PIPE,
|
capture_output=True,
|
||||||
stderr=PIPE,
|
|
||||||
stdin=input_stream,
|
stdin=input_stream,
|
||||||
text=True,
|
text=True,
|
||||||
check=False,
|
check=False,
|
||||||
@@ -691,8 +690,7 @@ def test_compression_changed(ocrmypdf_exec, resources, image, compression, outpd
|
|||||||
]
|
]
|
||||||
p = run(
|
p = run(
|
||||||
p_args,
|
p_args,
|
||||||
stdout=PIPE,
|
capture_output=True,
|
||||||
stderr=PIPE,
|
|
||||||
stdin=input_stream,
|
stdin=input_stream,
|
||||||
text=True,
|
text=True,
|
||||||
check=False,
|
check=False,
|
||||||
|
|||||||
@@ -188,7 +188,7 @@ def test_xml_metadata_preserved(test_file, output_type, resources, outpdf):
|
|||||||
try:
|
try:
|
||||||
from libxmp.utils import file_to_dict # pylint: disable=import-outside-toplevel
|
from libxmp.utils import file_to_dict # pylint: disable=import-outside-toplevel
|
||||||
except Exception: # pylint: disable=broad-except
|
except Exception: # pylint: disable=broad-except
|
||||||
pytest.skip("libxmp not available or libexempi3 not installed")
|
pytest.skip(reason="libxmp not available or libexempi3 not installed")
|
||||||
|
|
||||||
before = file_to_dict(str(input_file))
|
before = file_to_dict(str(input_file))
|
||||||
|
|
||||||
|
|||||||
@@ -173,17 +173,15 @@ def test_stack_abuse():
|
|||||||
p = pikepdf.Pdf.new()
|
p = pikepdf.Pdf.new()
|
||||||
|
|
||||||
stream = pikepdf.Stream(p, b'q ' * 35)
|
stream = pikepdf.Stream(p, b'q ' * 35)
|
||||||
with pytest.warns(None) as record:
|
with pytest.warns(UserWarning, match="overflowed"):
|
||||||
pdfinfo.info._interpret_contents(stream)
|
pdfinfo.info._interpret_contents(stream)
|
||||||
assert 'overflowed' in str(record[0].message)
|
|
||||||
|
|
||||||
stream = pikepdf.Stream(p, b'q Q Q Q Q')
|
stream = pikepdf.Stream(p, b'q Q Q Q Q')
|
||||||
with pytest.warns(None) as record:
|
with pytest.warns(UserWarning, match="underflowed") as record:
|
||||||
pdfinfo.info._interpret_contents(stream)
|
pdfinfo.info._interpret_contents(stream)
|
||||||
assert 'underflowed' in str(record[0].message)
|
|
||||||
|
|
||||||
stream = pikepdf.Stream(p, b'q ' * 135)
|
stream = pikepdf.Stream(p, b'q ' * 135)
|
||||||
with pytest.warns(None):
|
with pytest.warns():
|
||||||
with pytest.raises(RuntimeError):
|
with pytest.raises(RuntimeError):
|
||||||
pdfinfo.info._interpret_contents(stream)
|
pdfinfo.info._interpret_contents(stream)
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,12 @@ from .conftest import check_ocrmypdf, run_ocrmypdf
|
|||||||
|
|
||||||
# pylintx: disable=unused-variable
|
# pylintx: disable=unused-variable
|
||||||
|
|
||||||
|
# Remove this workaround when we require Pillow >= 10
|
||||||
|
try:
|
||||||
|
Transpose = Image.Transpose # type: ignore
|
||||||
|
except AttributeError:
|
||||||
|
# Pillow 9 shim
|
||||||
|
Transpose = Image # type: ignore
|
||||||
|
|
||||||
RENDERERS = ['hocr', 'sandwich']
|
RENDERERS = ['hocr', 'sandwich']
|
||||||
|
|
||||||
@@ -222,7 +228,7 @@ def test_rotate_page_level(image_angle, page_angle, resources, outdir):
|
|||||||
with Image.open(fspath(resources / 'typewriter.png')) as im:
|
with Image.open(fspath(resources / 'typewriter.png')) as im:
|
||||||
if image_angle != 0:
|
if image_angle != 0:
|
||||||
ccw_angle = -image_angle % 360
|
ccw_angle = -image_angle % 360
|
||||||
im = im.transpose(getattr(Image, f'ROTATE_{ccw_angle}'))
|
im = im.transpose(getattr(Transpose, f'ROTATE_{ccw_angle}'))
|
||||||
im.save(memimg, format='PNG')
|
im.save(memimg, format='PNG')
|
||||||
memimg.seek(0)
|
memimg.seek(0)
|
||||||
mempdf = BytesIO()
|
mempdf = BytesIO()
|
||||||
|
|||||||
+3
-3
@@ -30,12 +30,12 @@ def test_stdin(ocrmypdf_exec, resources, outpdf):
|
|||||||
'--plugin',
|
'--plugin',
|
||||||
'tests/plugins/tesseract_noop.py',
|
'tests/plugins/tesseract_noop.py',
|
||||||
]
|
]
|
||||||
run(p_args, stdout=PIPE, stderr=PIPE, stdin=input_stream, check=True)
|
run(p_args, capture_output=True, stdin=input_stream, check=True)
|
||||||
|
|
||||||
|
|
||||||
def test_stdout(ocrmypdf_exec, resources, outpdf):
|
def test_stdout(ocrmypdf_exec, resources, outpdf):
|
||||||
if 'COV_CORE_DATAFILE' in os.environ:
|
if 'COV_CORE_DATAFILE' in os.environ:
|
||||||
pytest.skip(msg="Coverage uses stdout")
|
pytest.skip(reason="Coverage uses stdout")
|
||||||
|
|
||||||
input_file = str(resources / 'francais.pdf')
|
input_file = str(resources / 'francais.pdf')
|
||||||
output_file = str(outpdf)
|
output_file = str(outpdf)
|
||||||
@@ -72,7 +72,7 @@ def test_bad_locale(monkeypatch):
|
|||||||
)
|
)
|
||||||
def test_dev_null(resources):
|
def test_dev_null(resources):
|
||||||
if 'COV_CORE_DATAFILE' in os.environ:
|
if 'COV_CORE_DATAFILE' in os.environ:
|
||||||
pytest.skip(msg="Coverage uses stdout")
|
pytest.skip(reason="Coverage uses stdout")
|
||||||
|
|
||||||
p = run_ocrmypdf(
|
p = run_ocrmypdf(
|
||||||
resources / 'trivial.pdf',
|
resources / 'trivial.pdf',
|
||||||
|
|||||||
Reference in New Issue
Block a user