Compare commits

...
9 Commits
Author SHA1 Message Date
James R. Barlow 1c2adc3d89 v12.3.0 release notes 2021-07-14 02:38:23 -07:00
James R. Barlow 37923ffe52 Work around Pillow 8.3.1 DPI changes
Pillow decided against round-tripping DPI values.
https://github.com/python-pillow/Pillow/pull/5476

Fixes #802
2021-07-14 02:34:28 -07:00
James R. Barlow 773e28478c graft: don't use deprecated pikepdf APIs 2021-07-14 01:44:26 -07:00
James R. Barlow e6db071273 Merge remote-tracking branch 'origin/master' 2021-07-14 00:48:30 -07:00
mara004andGitHub de74b80335 docs: Fix two missing words (#805)
* Fix two missing words
2021-07-14 00:48:00 -07:00
James R. Barlow 5cba68b93d tests: Don't require symlink permissions on Windows
Some of tests required symlink permissions, which CI workers have but typical Windows
user accounts do not. Mostly these are just correctness tests.
2021-07-14 00:11:47 -07:00
mara004andGitHub d2d39de92f Update api.rst (#797)
fix excess 'no'
2021-07-05 14:12:51 -07:00
James R. Barlow 0b834411fe validation: mention ISO 639-2 to give people a clue about how to find the appropriate code 2021-06-28 15:19:59 -07:00
James R. Barlow c935ba070b Fix name of sphinx-rtd-theme 2021-06-23 00:43:30 -07:00
9 changed files with 60 additions and 15 deletions
+1 -1
View File
@@ -44,7 +44,7 @@ execution. To do this, it will:
The Python process that calls ``ocrmypdf.ocr()`` must be sufficiently
privileged to perform these actions.
There is no currently no option to manage how jobs are scheduled other
There currently is no option to manage how jobs are scheduled other
than the argument ``jobs=`` which will limit the number of worker
processes.
+5 -5
View File
@@ -19,7 +19,7 @@ PDF is a rich, complex file format. The official PDF 1.7 specification,
ISO 32000:2008, is hundreds of pages long and references several annexes
each of which are similar in length. PDFs can contain video, audio, XML,
JavaScript and other programming, and forms. In some cases, they can
open internet connections to pre-selected URLs. All of these possible
open internet connections to pre-selected URLs. All of these are possible
attack vectors.
In short, PDFs `may contain
@@ -31,7 +31,7 @@ describes a high-paranoia method which allows potentially hostile PDFs
to be viewed and rasterized safely in a disposable virtual machine. A
trusted PDF created in this manner is converted to images and loses all
information making it searchable and losing all compression. OCRmyPDF
could be used restore searchability.
could be used to restore searchability.
How OCRmyPDF processes PDFs
===========================
@@ -66,8 +66,8 @@ service. OCRmyPDF relies on Ghostscript, and therefore, if deployed
online one should be prepared to comply with Ghostscript's Affero GPL
license, and any other licenses.
Setting aside these concerns, a side effect of OCRmyPDF is it may
incidentally sanitize PDFs that contain certain types of malware. It
Setting aside these concerns, a side effect of OCRmyPDF is that it may
incidentally sanitize PDFs containing certain types of malware. It
repairs the PDF with pikepdf/libqpdf, 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.
@@ -83,7 +83,7 @@ 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, a virtual machine,
would be to 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.
+13
View File
@@ -13,6 +13,19 @@ 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.
v12.3.0
=======
- Fixed a regression introduced in Pillow 8.3.0. Pillow no longer rounds DPI
for image resolutions. We now account for this. (:issue:`802`)
- We no longer use some API calls that are deprecated in the latest versions of
pikepdf.
- Improved error message when a language is requested that doesn't look like a
typical ISO 639-2 code.
- Fixed some tests that attempted to symlink on Windows, breaking tests on a
Windows desktop but not usually on CI.
- Documentation fixes (thanks to @mara004)
v12.2.0
=======
+1 -1
View File
@@ -79,7 +79,7 @@ test =
# or brew install exempi
docs =
sphinx
sphinx_rtd_theme
sphinx-rtd-theme
sphinx-issues
extended_test =
PyMuPDF == 1.13.4
+10 -2
View File
@@ -47,7 +47,8 @@ def strip_invisible_text(pdf, page):
render_mode = 0
text_objects = []
page.page_contents_coalesce()
rich_page = pikepdf.Page(page)
rich_page.contents_coalesce()
for operands, operator in pikepdf.parse_content_stream(page, ''):
if not in_text_obj:
if operator == pikepdf.Operator('BT'):
@@ -307,7 +308,14 @@ class OcrGrafter:
if strip_old_text:
strip_invisible_text(self.pdf_base, base_page)
base_page.page_contents_add(new_text_layer, prepend=True)
if hasattr(pikepdf.Page, 'contents_add'):
# pikepdf >= 2.14 adds this method and deprecates the one below
pikepdf.Page(base_page).contents_add(new_text_layer, prepend=True)
else:
# pikepdf < 2.14
base_page.page_contents_add(
new_text_layer, prepend=True
) # pragma: no cover
_update_resources(
obj=base_page, font=font, font_key=font_key, procset=procset
+4 -3
View File
@@ -65,13 +65,14 @@ def check_options_languages(options, ocr_engine_languages):
log.debug("No language specified; assuming --language %s", DEFAULT_LANGUAGE)
if not ocr_engine_languages:
return
if not options.languages.issubset(ocr_engine_languages):
missing_languages = options.languages - ocr_engine_languages
if missing_languages:
msg = (
f"OCR engine does not have language data for the following "
"requested languages: \n"
)
for lang in options.languages - ocr_engine_languages:
msg += lang + '\n'
msg += '\n'.join(lang for lang in missing_languages)
msg += '\nNote: most languages are identified by a 3-digit ISO 639-2 Code'
raise MissingDependencyError(msg)
+21 -2
View File
@@ -25,19 +25,31 @@ log = logging.getLogger(__name__)
class Resolution(namedtuple('Resolution', ('x', 'y'))):
"""The number of pixels per inch in each 2D direction."""
"""The number of pixels per inch in each 2D direction.
Resolution objects are considered "equal" for == purposes if they are
equal to a reasonable tolerance.
"""
__slots__ = ()
# rel_tol after converting from dpi to pixels per meter and saving
# as integer with rounding, as many file formats
CONVERSION_ERROR = 0.002
def round(self, ndigits: int):
return Resolution(round(self.x, ndigits), round(self.y, ndigits))
def to_int(self):
return Resolution(int(round(self.x)), int(round(self.y)))
@classmethod
def _isclose(cls, a, b):
return isclose(a, b, rel_tol=cls.CONVERSION_ERROR)
@property
def is_square(self) -> bool:
return isclose(self.x, self.y, rel_tol=1e-3)
return self._isclose(self.x, self.y)
@property
def is_finite(self) -> bool:
@@ -61,6 +73,13 @@ class Resolution(namedtuple('Resolution', ('x', 'y'))):
def __repr__(self): # pragma: no cover
return f"Resolution({self.x}x{self.y} dpi)"
def __eq__(self, other):
if isinstance(other, tuple) and len(other) == 2:
other = Resolution(*other)
if not isinstance(other, Resolution):
return NotImplemented
return self._isclose(self.x, other.x) and self._isclose(self.y, other.y)
class NeverRaise(Exception):
"""An exception that is never raised"""
+1 -1
View File
@@ -72,7 +72,7 @@ def test_rasterize_rotated(francais, outdir, caplog):
with Image.open(outdir / 'out.png') as im:
assert im.size == (target_size[1], target_size[0])
assert im.info['dpi'] == (forced_dpi[1], forced_dpi[0])
assert im.info['dpi'] == forced_dpi.flip_axis()
def test_gs_render_failure(resources, outpdf):
+4
View File
@@ -16,6 +16,8 @@ from ocrmypdf import helpers
from .conftest import running_in_docker
needs_symlink = pytest.mark.skipif(os.name == 'nt', reason='needs posix symlink')
class TestSafeSymlink:
def test_safe_symlink_link_self(self, tmp_path, caplog):
@@ -27,6 +29,7 @@ class TestSafeSymlink:
with pytest.raises(FileExistsError):
helpers.safe_symlink(tmp_path / 'input', tmp_path / 'regular_file')
@needs_symlink
def test_safe_symlink_relink(self, tmp_path):
(tmp_path / 'regular_file_a').touch()
(tmp_path / 'regular_file_b').write_bytes(b'ABC')
@@ -77,6 +80,7 @@ class TestFileIsWritable:
def test_plain(self, non_existent):
assert helpers.is_file_writable(non_existent)
@needs_symlink
def test_symlink_loop(self, tmp_path):
loop = tmp_path / 'loop'
loop.symlink_to(loop)