From df87e21c85077014fe89638cb761386fe87f96ff Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Fri, 20 Apr 2018 00:06:55 -0700 Subject: [PATCH 01/17] Add support for PDF/A-3 No ability to attach files however --- src/ocrmypdf/__main__.py | 8 ++++++-- src/ocrmypdf/pipeline.py | 3 ++- tests/test_main.py | 7 ++++--- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/ocrmypdf/__main__.py b/src/ocrmypdf/__main__.py index 2d0a0a0c..3d861904 100755 --- a/src/ocrmypdf/__main__.py +++ b/src/ocrmypdf/__main__.py @@ -156,14 +156,15 @@ parser.add_argument( '--image-dpi', metavar='DPI', type=int, help="For input image instead of PDF, use this DPI instead of file's.") parser.add_argument( - '--output-type', choices=['pdfa', 'pdf', 'pdfa-1', 'pdfa-2'], + '--output-type', choices=['pdfa', 'pdf', 'pdfa-1', 'pdfa-2', 'pdfa-3'], default='pdfa', help="Choose output type. 'pdfa' creates a PDF/A-2b compliant file for " "long term archiving (default, recommended) but may not suitable " "for users who want their file altered as little as possible. 'pdfa' " "also has problems with full Unicode text. 'pdf' attempts to " "preserve file contents as much as possible. 'pdf-a1' creates a " - "PDF/A1-b file. 'pdf-a2' is equivalent to 'pdfa'." + "PDF/A1-b file. 'pdf-a2' is equivalent to 'pdfa'. 'pdf-a3' creates a " + "PDF/A3-b file." ) # Use null string '\0' as sentinel to indicate the user supplied no argument, @@ -394,6 +395,9 @@ def check_options_output(options, log): log.info( "Ignoring --debug-rendering because it requires --pdf-renderer=hocr") + if options.output_type == 'pdfa': + options.output_type == 'pdfa-2' + lossless_reconstruction = False if options.pdf_renderer in ('hocr', 'sandwich'): if not any((options.deskew, options.clean_final, options.force_ocr, diff --git a/src/ocrmypdf/pipeline.py b/src/ocrmypdf/pipeline.py index c11c06bd..9cc7226e 100644 --- a/src/ocrmypdf/pipeline.py +++ b/src/ocrmypdf/pipeline.py @@ -970,6 +970,7 @@ def merge_pages_ghostscript( pdf_pages, _ = _merge_pages_common( input_files_groups, output_file, log, context) input_pdfinfo = context.get_pdfinfo() + ghostscript.generate_pdfa( pdf_version=input_pdfinfo.min_version, pdf_pages=pdf_pages, @@ -977,7 +978,7 @@ def merge_pages_ghostscript( compression=options.pdfa_image_compression, log=log, threads=options.jobs or 1, - pdfa_part=('1' if options.output_type == 'pdfa-1' else '2')) + pdfa_part=options.output_type[-1]) # is pdfa-1, pdfa-2, or pdfa-3 if fitz: doc = fitz.Document(output_file + '_toc.pdf') doc.setToC(input_pdfinfo.table_of_contents) diff --git a/tests/test_main.py b/tests/test_main.py index fbad9ffb..f098a230 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -951,15 +951,16 @@ def test_sidecar_nonempty(spoof_tesseract_cache, resources, outpdf): assert 'the' in ocr_text -def test_pdfa_1(spoof_tesseract_cache, resources, outpdf): +@pytest.mark.parametrize('pdfa_level', ['1', '2', '3']) +def test_pdfa_n(spoof_tesseract_cache, pdfa_level, resources, outpdf): check_ocrmypdf( resources / 'ccitt.pdf', outpdf, - '--output-type', 'pdfa-1', + '--output-type', 'pdfa-' + pdfa_level, env=spoof_tesseract_cache ) pdfa_info = file_claims_pdfa(outpdf) - assert pdfa_info['conformance'] == 'PDF/A-1B' + assert pdfa_info['conformance'] == 'PDF/A-{}B'.format(pdfa_level) def test_bad_locale(): From 11cd6201d934458200cad42206f63785a633a6cd Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Wed, 25 Apr 2018 21:57:50 -0700 Subject: [PATCH 02/17] helpers: fix missing call to complain() In practice this is probably unreachable. --- src/ocrmypdf/helpers.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/ocrmypdf/helpers.py b/src/ocrmypdf/helpers.py index b7833426..1671ea7a 100644 --- a/src/ocrmypdf/helpers.py +++ b/src/ocrmypdf/helpers.py @@ -22,6 +22,7 @@ from pathlib import Path import sys import os import multiprocessing +import warnings def re_symlink(input_file, soft_link_name, log=None): @@ -70,7 +71,7 @@ def is_iterable_notstr(thing): def page_number(input_file): "Get one-based page number implied by filename (000002.pdf -> 2)" - return int(os.path.basename(input_file)[0:6]) + return int(os.path.basename(fspath(input_file)[0:6])) def available_cpu_count(): @@ -85,7 +86,7 @@ def available_cpu_count(): except (ImportError, AttributeError): pass - complain( + warnings.warn( "Could not get CPU count. Assuming one (1) CPU." "Use -j N to set manually.") return 1 From 0934905493eea4db19d7660a147da3e7e6dfd72d Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Wed, 25 Apr 2018 21:58:18 -0700 Subject: [PATCH 03/17] Don't suppress error message from config_notfound Since it showed up in s390x bionic --- tests/test_main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_main.py b/tests/test_main.py index fbad9ffb..32483279 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -718,7 +718,7 @@ def test_tesseract_config_notfound(renderer, resources, outdir): '--pdf-renderer', renderer, '--tesseract-config', cfg_file) assert "Can't open" in err, "No error message about missing config file" - assert p.returncode == ExitCode.ok + assert p.returncode == ExitCode.ok, err @pytest.mark.parametrize('renderer', RENDERERS) From abcae0c2a43020be505b40f0ae45e1b44006e1ce Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Wed, 25 Apr 2018 22:10:51 -0700 Subject: [PATCH 04/17] Fix helpers.py again --- src/ocrmypdf/helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ocrmypdf/helpers.py b/src/ocrmypdf/helpers.py index 1671ea7a..745549a8 100644 --- a/src/ocrmypdf/helpers.py +++ b/src/ocrmypdf/helpers.py @@ -71,7 +71,7 @@ def is_iterable_notstr(thing): def page_number(input_file): "Get one-based page number implied by filename (000002.pdf -> 2)" - return int(os.path.basename(fspath(input_file)[0:6])) + return int(os.path.basename(fspath(input_file))[0:6]) def available_cpu_count(): From 4209034d2078cfdbff83ee4977d4e7a3f9706a76 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Fri, 27 Apr 2018 15:51:26 -0700 Subject: [PATCH 05/17] Add gpg key to issue template --- .github/ISSUE_TEMPLATE | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE b/.github/ISSUE_TEMPLATE index a526bfc9..4d0dc4c9 100644 --- a/.github/ISSUE_TEMPLATE +++ b/.github/ISSUE_TEMPLATE @@ -1,3 +1,12 @@ -Please include the command line and (if needed) a test file with your issue report. +Please include the command line and a test file with your issue report. If possible, please use a test file that we can include in future test cases (no personal information, no copyrighted material). + +If you wish to encrypt a test file so that only the maintainer of OCRmyPDF can view it, you may use: + +```bash + +gpg --recv-keys 4434eb74c4a35f7f --keyserver pgp.mit.edu +gpg --output test.pdf.gpg --encrypt --receipient 4434eb74c4a35f7f test.pdf + +``` From 219fe2155be776d787ac77df4acb35c4db336ea2 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Fri, 27 Apr 2018 17:16:42 -0700 Subject: [PATCH 06/17] test_pageinfo: remove duplicate import --- tests/test_pageinfo.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_pageinfo.py b/tests/test_pageinfo.py index e421ed52..9893327f 100644 --- a/tests/test_pageinfo.py +++ b/tests/test_pageinfo.py @@ -26,7 +26,6 @@ import os import shutil import pytest import img2pdf -import pytest import sys import PyPDF2 as pypdf From 2131ad46706704e7f378374fa488938db33ba8ee Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Fri, 27 Apr 2018 17:21:01 -0700 Subject: [PATCH 07/17] Fix --remove-background error on PDFs with colormapped images It's unclear how exactly a colormapped image gets to this spot given the tendency of other image processing tools to flatten such images, but someone made it happen, so now we make sure the image is okay. Closes #262 --- src/ocrmypdf/leptonica.py | 33 ++++++++++++------------- src/ocrmypdf/lib/_leptonica.py | 9 ++++--- src/ocrmypdf/lib/compile_leptonica.py | 23 +++++++++++++++++ tests/resources/README.rst | 1 + tests/resources/baiona-colormapped.png | Bin 0 -> 54170 bytes tests/test_lept.py | 30 ++++++++++++++++++++++ 6 files changed, 75 insertions(+), 21 deletions(-) create mode 100644 tests/resources/baiona-colormapped.png create mode 100644 tests/test_lept.py diff --git a/src/ocrmypdf/leptonica.py b/src/ocrmypdf/leptonica.py index 6ee9747e..26abd32f 100644 --- a/src/ocrmypdf/leptonica.py +++ b/src/ocrmypdf/leptonica.py @@ -108,13 +108,6 @@ class LeptonicaIOError(LeptonicaError): pass -class RemoveColormap(Enum): - to_binary = 0 - to_grayscale = 1 - to_full_color = 2 - based_on_src = 3 - - class Pix: """Wrapper around leptonica's PIX object. @@ -141,9 +134,10 @@ class Pix: def __repr__(self): if self._pix: - s = "" + s = "" return s.format(self._pix.w, self._pix.h, self._pix.d, - int(ffi.cast("intptr_t", self._pix))) + int(ffi.cast('intptr_t', self._pix)), + '(colormapped)' if self._pix.colormap else '') else: return "" @@ -328,13 +322,16 @@ class Pix: return None def remove_colormap(self, removal_type): - """Remove a palette + """Remove a palette (colormap); if no colormap, returns a copy of this + image + + removal_type - any of lept.REMOVE_CMAP_* - removal_type - RemovalColormap() """ - - with LeptonicaErrorTrap(): - return Pix(lept.pixRemoveColormap(self._pix, removal_type)) + return self + # with LeptonicaErrorTrap(): + # return Pix(lept.pixRemoveColormapGeneral( + # self._pix, removal_type, lept.L_COPY)) def otsu_adaptive_threshold( self, tile_size=(300, 300), kernel_size=(4, 4), scorefract=0.1): @@ -426,9 +423,11 @@ class Pix: def background_norm( self, mask=None, grayscale=None, tile_size=(10, 15), fg_threshold=60, min_count=40, bg_val=200, smooth_kernel=(2, 1)): + # Background norm doesn't work on color mapped Pix, so remove colormap + target_pix = self.remove_colormap(lept.REMOVE_CMAP_BASED_ON_SRC) with LeptonicaErrorTrap(): return Pix(lept.pixBackgroundNorm( - self._pix, + target_pix._pix, mask or ffi.NULL, grayscale or ffi.NULL, tile_size[0], @@ -450,8 +449,8 @@ class Pix: if get_leptonica_version() < 'leptonica-1.72': # Older versions of Leptonica (pre-1.72) have a buggy # implementation of pixCorrelationBinary that overflows on larger - # images. Ubuntu trusty has 1.70. Ubuntu PPA - # ppa:rebuntu16/avidemux+unofficial has "leptonlib" 1.73. + # images. Ubuntu 14.04/trusty has 1.70. Ubuntu PPA + # ppa:alex-p/tesseract-ocr has leptonlib 1.75. pix1_count = ffi.new('l_int32 *') pix2_count = ffi.new('l_int32 *') pixn_count = ffi.new('l_int32 *') diff --git a/src/ocrmypdf/lib/_leptonica.py b/src/ocrmypdf/lib/_leptonica.py index 9bdb3aed..80e67386 100644 --- a/src/ocrmypdf/lib/_leptonica.py +++ b/src/ocrmypdf/lib/_leptonica.py @@ -3,8 +3,9 @@ import _cffi_backend ffi = _cffi_backend.FFI('ocrmypdf.lib._leptonica', _version = 0x2601, - _types = b'\x00\x00\x0F\x0D\x00\x00\xBB\x03\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\xBE\x03\x00\x00\x00\x0F\x00\x00\x01\x0D\x00\x00\x01\x11\x00\x00\x00\x0F\x00\x00\x01\x0D\x00\x00\x01\x11\x00\x00\xBA\x03\x00\x00\x0F\x03\x00\x00\x00\x0F\x00\x00\x01\x0D\x00\x00\x01\x11\x00\x00\x01\x11\x00\x00\x00\x0F\x00\x00\x01\x0D\x00\x00\x01\x11\x00\x00\x01\x11\x00\x00\x01\x11\x00\x00\x00\x0F\x00\x00\x01\x0D\x00\x00\x01\x11\x00\x00\x01\x11\x00\x00\x01\x11\x00\x00\x0D\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x01\x0D\x00\x00\x01\x11\x00\x00\x01\x11\x00\x00\x01\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x01\x0D\x00\x00\x01\x11\x00\x00\x01\x11\x00\x00\x0D\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x01\x0D\x00\x00\x01\x11\x00\x00\x01\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x01\x0D\x00\x00\x01\x11\x00\x00\x01\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x0D\x01\x00\x00\x02\x03\x00\x00\x00\x0F\x00\x00\x01\x0D\x00\x00\x01\x11\x00\x00\x0D\x01\x00\x00\x0D\x01\x00\x00\x00\x0F\x00\x00\x01\x0D\x00\x00\x01\x11\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x01\x0D\x00\x00\x01\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x01\x0D\x00\x00\x01\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x01\x0D\x00\x00\x08\x11\x00\x00\x00\x0F\x00\x00\x01\x0D\x00\x00\xC7\x03\x00\x00\x1C\x01\x00\x00\x00\x0F\x00\x00\xBD\x0D\x00\x00\x00\x0F\x00\x00\x49\x0D\x00\x00\x00\x0F\x00\x00\x02\x0D\x00\x00\x01\x11\x00\x00\x01\x11\x00\x00\x1F\x03\x00\x00\x00\x0F\x00\x00\x02\x0D\x00\x00\x01\x11\x00\x00\x01\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x6E\x11\x00\x00\x6E\x11\x00\x00\x6E\x11\x00\x00\x00\x0F\x00\x00\x02\x0D\x00\x00\x01\x11\x00\x00\x6E\x11\x00\x00\x6E\x11\x00\x00\x00\x0F\x00\x00\x02\x0D\x00\x00\x01\x11\x00\x00\x49\x11\x00\x00\x49\x11\x00\x00\x00\x0F\x00\x00\x02\x0D\x00\x00\x01\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x0D\x01\x00\x00\x07\x01\x00\x00\x49\x11\x00\x00\x00\x0F\x00\x00\x02\x0D\x00\x00\x01\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x6E\x11\x00\x00\x6E\x11\x00\x00\x00\x0F\x00\x00\x02\x0D\x00\x00\x01\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x0D\x01\x00\x00\x01\x03\x00\x00\x9D\x11\x00\x00\x00\x0F\x00\x00\x02\x0D\x00\x00\x01\x11\x00\x00\xC6\x03\x00\x00\x65\x03\x00\x00\x00\x0F\x00\x00\x02\x0D\x00\x00\x08\x11\x00\x00\x01\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x02\x0D\x00\x00\xC4\x03\x00\x00\xA3\x11\x00\x00\x01\x11\x00\x00\x0D\x01\x00\x00\x00\x0F\x00\x00\xCA\x0D\x00\x00\x10\x11\x00\x00\x00\x0F\x00\x00\xCA\x0D\x00\x00\x9D\x11\x00\x00\x00\x0F\x00\x00\xCA\x0D\x00\x00\xCA\x03\x00\x00\x00\x0F\x00\x00\x00\x09\x00\x00\x01\x09\x00\x00\x02\x09\x00\x00\xBE\x03\x00\x00\x02\x01\x00\x00\x0E\x01\x00\x00\x0B\x01\x00\x00\x05\x01\x00\x00\x03\x01\x00\x00\xBC\x03\x00\x00\xC5\x03\x00\x00\x04\x01\x00\x00\xC7\x03\x00\x00\x08\x01\x00\x00\x0C\x01\x00\x00\x06\x01\x00\x00\x00\x01', - _globals = (b'\x00\x00\xB1\x23boxDestroy',0,b'\x00\x00\x67\x23getLeptonicaVersion',0,b'\x00\x00\xB7\x23lept_free',0,b'\x00\x00\x69\x23makePixelSumTab8',0,b'\x00\x00\x16\x23pixAnd',0,b'\x00\x00\x23\x23pixBackgroundNorm',0,b'\x00\x00\x1B\x23pixCleanBackgroundToWhite',0,b'\x00\x00\x0D\x23pixClipRectangle',0,b'\x00\x00\x8D\x23pixColorFraction',0,b'\x00\x00\x59\x23pixColorMagnitude',0,b'\x00\x00\x0A\x23pixConvertRGBToLuminance',0,b'\x00\x00\x6B\x23pixCorrelationBinary',0,b'\x00\x00\x80\x23pixCountPixels',0,b'\x00\x00\x63\x23pixDeserializeFromMemory',0,b'\x00\x00\x50\x23pixDeskew',0,b'\x00\x00\xB4\x23pixDestroy',0,b'\x00\x00\x0A\x23pixEndianByteSwapNew',0,b'\x00\x00\x00\x23pixFindPageForeground',0,b'\x00\x00\x7B\x23pixFindSkew',0,b'\x00\x00\x2F\x23pixGammaTRC',0,b'\x00\x00\x70\x23pixGetAverageMaskedRGB',0,b'\x00\x00\x36\x23pixGlobalNormRGB',0,b'\x00\x00\x12\x23pixInvert',0,b'\x00\x00\x54\x23pixMaskOverColorPixels',0,b'\x00\x00\x85\x23pixNumSignificantGrayColors',0,b'\x00\x00\x96\x23pixOtsuAdaptiveThreshold',0,b'\x00\x00\x3E\x23pixOtsuThreshOnBackgroundNorm',0,b'\x00\x00\x60\x23pixRead',0,b'\x00\x00\x50\x23pixRemoveColormap',0,b'\x00\x00\x12\x23pixRotate180',0,b'\x00\x00\x50\x23pixRotateOrth',0,b'\x00\x00\x4B\x23pixScale',0,b'\x00\x00\xA0\x23pixSerializeToMemory',0,b'\x00\x00\xA5\x23pixWriteImpliedFormat',0,b'\x00\x00\xAB\x23pixWriteMemPng',0), - _struct_unions = ((b'\x00\x00\x00\xBA\x00\x00\x00\x02Box',b'\x00\x00\x02\x11x',b'\x00\x00\x02\x11y',b'\x00\x00\x02\x11w',b'\x00\x00\x02\x11h',b'\x00\x00\xC7\x11refcount'),(b'\x00\x00\x00\xBB\x00\x00\x00\x02Pix',b'\x00\x00\xC7\x11w',b'\x00\x00\xC7\x11h',b'\x00\x00\xC7\x11d',b'\x00\x00\xC7\x11spp',b'\x00\x00\xC7\x11wpl',b'\x00\x00\xC7\x11refcount',b'\x00\x00\x02\x11xres',b'\x00\x00\x02\x11yres',b'\x00\x00\x02\x11informat',b'\x00\x00\x02\x11special',b'\x00\x00\xBD\x11text',b'\x00\x00\xC3\x11colormap',b'\x00\x00\xC6\x11data'),(b'\x00\x00\x00\xBC\x00\x00\x00\x02PixColormap',b'\x00\x00\xB8\x11array',b'\x00\x00\x02\x11depth',b'\x00\x00\x02\x11nalloc',b'\x00\x00\x02\x11n')), - _typenames = (b'\x00\x00\x00\xBABOX',b'\x00\x00\x00\xBBPIX',b'\x00\x00\x00\xBCPIXCMAP',b'\x00\x00\x00\x1Fl_float32',b'\x00\x00\x00\xBFl_float64',b'\x00\x00\x00\xC1l_int16',b'\x00\x00\x00\x02l_int32',b'\x00\x00\x00\xC0l_int64',b'\x00\x00\x00\xC2l_int8',b'\x00\x00\x00\xC9l_uint16',b'\x00\x00\x00\xC7l_uint32',b'\x00\x00\x00\xC8l_uint64',b'\x00\x00\x00\xC5l_uint8'), + _types = b'\x00\x00\x0F\x0D\x00\x00\xBB\x03\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\xBE\x03\x00\x00\x00\x0F\x00\x00\x01\x0D\x00\x00\x01\x11\x00\x00\x00\x0F\x00\x00\x01\x0D\x00\x00\x01\x11\x00\x00\xBA\x03\x00\x00\x0F\x03\x00\x00\x00\x0F\x00\x00\x01\x0D\x00\x00\x01\x11\x00\x00\x01\x11\x00\x00\x00\x0F\x00\x00\x01\x0D\x00\x00\x01\x11\x00\x00\x01\x11\x00\x00\x01\x11\x00\x00\x00\x0F\x00\x00\x01\x0D\x00\x00\x01\x11\x00\x00\x01\x11\x00\x00\x01\x11\x00\x00\x0D\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x01\x0D\x00\x00\x01\x11\x00\x00\x01\x11\x00\x00\x01\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x01\x0D\x00\x00\x01\x11\x00\x00\x01\x11\x00\x00\x0D\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x01\x0D\x00\x00\x01\x11\x00\x00\x01\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x01\x0D\x00\x00\x01\x11\x00\x00\x01\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x0D\x01\x00\x00\x02\x03\x00\x00\x00\x0F\x00\x00\x01\x0D\x00\x00\x01\x11\x00\x00\x0D\x01\x00\x00\x0D\x01\x00\x00\x00\x0F\x00\x00\x01\x0D\x00\x00\x01\x11\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x01\x0D\x00\x00\x01\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x01\x0D\x00\x00\x01\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x01\x0D\x00\x00\x08\x11\x00\x00\x00\x0F\x00\x00\x01\x0D\x00\x00\xC9\x03\x00\x00\x1C\x01\x00\x00\x00\x0F\x00\x00\xBD\x0D\x00\x00\x00\x0F\x00\x00\x49\x0D\x00\x00\x00\x0F\x00\x00\x02\x0D\x00\x00\x01\x11\x00\x00\x01\x11\x00\x00\x1F\x03\x00\x00\x00\x0F\x00\x00\x02\x0D\x00\x00\x01\x11\x00\x00\x01\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x6E\x11\x00\x00\x6E\x11\x00\x00\x6E\x11\x00\x00\x00\x0F\x00\x00\x02\x0D\x00\x00\x01\x11\x00\x00\x6E\x11\x00\x00\x6E\x11\x00\x00\x00\x0F\x00\x00\x02\x0D\x00\x00\x01\x11\x00\x00\x49\x11\x00\x00\x49\x11\x00\x00\x00\x0F\x00\x00\x02\x0D\x00\x00\x01\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x0D\x01\x00\x00\x07\x01\x00\x00\x49\x11\x00\x00\x00\x0F\x00\x00\x02\x0D\x00\x00\x01\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x6E\x11\x00\x00\x6E\x11\x00\x00\x00\x0F\x00\x00\x02\x0D\x00\x00\x01\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x0D\x01\x00\x00\x01\x03\x00\x00\x9D\x11\x00\x00\x00\x0F\x00\x00\x02\x0D\x00\x00\x01\x11\x00\x00\xC8\x03\x00\x00\x65\x03\x00\x00\x00\x0F\x00\x00\x02\x0D\x00\x00\x08\x11\x00\x00\x01\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x02\x0D\x00\x00\xC6\x03\x00\x00\xA3\x11\x00\x00\x01\x11\x00\x00\x0D\x01\x00\x00\x00\x0F\x00\x00\xCC\x0D\x00\x00\x10\x11\x00\x00\x00\x0F\x00\x00\xCC\x0D\x00\x00\x9D\x11\x00\x00\x00\x0F\x00\x00\xCC\x0D\x00\x00\xCC\x03\x00\x00\x00\x0F\x00\x00\x00\x09\x00\x00\x01\x09\x00\x00\x02\x09\x00\x00\xBE\x03\x00\x00\x02\x01\x00\x00\x0E\x01\x00\x00\x00\x0B\x00\x00\x01\x0B\x00\x00\x0B\x01\x00\x00\x05\x01\x00\x00\x03\x01\x00\x00\xBC\x03\x00\x00\xC7\x03\x00\x00\x04\x01\x00\x00\xC9\x03\x00\x00\x08\x01\x00\x00\x0C\x01\x00\x00\x06\x01\x00\x00\x00\x01', + _globals = (b'\xFF\xFF\xFF\x0BL_CLONE',2,b'\xFF\xFF\xFF\x0BL_COPY',1,b'\xFF\xFF\xFF\x0BL_COPY_CLONE',3,b'\xFF\xFF\xFF\x0BL_INSERT',0,b'\xFF\xFF\xFF\x0BL_NOCOPY',0,b'\xFF\xFF\xFF\x0BREMOVE_CMAP_BASED_ON_SRC',4,b'\xFF\xFF\xFF\x0BREMOVE_CMAP_TO_BINARY',0,b'\xFF\xFF\xFF\x0BREMOVE_CMAP_TO_FULL_COLOR',2,b'\xFF\xFF\xFF\x0BREMOVE_CMAP_TO_GRAYSCALE',1,b'\xFF\xFF\xFF\x0BREMOVE_CMAP_WITH_ALPHA',3,b'\x00\x00\xB1\x23boxDestroy',0,b'\x00\x00\x67\x23getLeptonicaVersion',0,b'\x00\x00\xB7\x23lept_free',0,b'\x00\x00\x69\x23makePixelSumTab8',0,b'\x00\x00\x16\x23pixAnd',0,b'\x00\x00\x23\x23pixBackgroundNorm',0,b'\x00\x00\x1B\x23pixCleanBackgroundToWhite',0,b'\x00\x00\x0D\x23pixClipRectangle',0,b'\x00\x00\x8D\x23pixColorFraction',0,b'\x00\x00\x59\x23pixColorMagnitude',0,b'\x00\x00\x0A\x23pixConvertRGBToLuminance',0,b'\x00\x00\x6B\x23pixCorrelationBinary',0,b'\x00\x00\x80\x23pixCountPixels',0,b'\x00\x00\x63\x23pixDeserializeFromMemory',0,b'\x00\x00\x50\x23pixDeskew',0,b'\x00\x00\xB4\x23pixDestroy',0,b'\x00\x00\x0A\x23pixEndianByteSwapNew',0,b'\x00\x00\x00\x23pixFindPageForeground',0,b'\x00\x00\x7B\x23pixFindSkew',0,b'\x00\x00\x2F\x23pixGammaTRC',0,b'\x00\x00\x70\x23pixGetAverageMaskedRGB',0,b'\x00\x00\x36\x23pixGlobalNormRGB',0,b'\x00\x00\x12\x23pixInvert',0,b'\x00\x00\x54\x23pixMaskOverColorPixels',0,b'\x00\x00\x85\x23pixNumSignificantGrayColors',0,b'\x00\x00\x96\x23pixOtsuAdaptiveThreshold',0,b'\x00\x00\x3E\x23pixOtsuThreshOnBackgroundNorm',0,b'\x00\x00\x60\x23pixRead',0,b'\x00\x00\x50\x23pixRemoveColormap',0,b'\x00\x00\x54\x23pixRemoveColormapGeneral',0,b'\x00\x00\x12\x23pixRotate180',0,b'\x00\x00\x50\x23pixRotateOrth',0,b'\x00\x00\x4B\x23pixScale',0,b'\x00\x00\xA0\x23pixSerializeToMemory',0,b'\x00\x00\xA5\x23pixWriteImpliedFormat',0,b'\x00\x00\xAB\x23pixWriteMemPng',0), + _struct_unions = ((b'\x00\x00\x00\xBA\x00\x00\x00\x02Box',b'\x00\x00\x02\x11x',b'\x00\x00\x02\x11y',b'\x00\x00\x02\x11w',b'\x00\x00\x02\x11h',b'\x00\x00\xC9\x11refcount'),(b'\x00\x00\x00\xBB\x00\x00\x00\x02Pix',b'\x00\x00\xC9\x11w',b'\x00\x00\xC9\x11h',b'\x00\x00\xC9\x11d',b'\x00\x00\xC9\x11spp',b'\x00\x00\xC9\x11wpl',b'\x00\x00\xC9\x11refcount',b'\x00\x00\x02\x11xres',b'\x00\x00\x02\x11yres',b'\x00\x00\x02\x11informat',b'\x00\x00\x02\x11special',b'\x00\x00\xBD\x11text',b'\x00\x00\xC5\x11colormap',b'\x00\x00\xC8\x11data'),(b'\x00\x00\x00\xBC\x00\x00\x00\x02PixColormap',b'\x00\x00\xB8\x11array',b'\x00\x00\x02\x11depth',b'\x00\x00\x02\x11nalloc',b'\x00\x00\x02\x11n')), + _enums = (b'\x00\x00\x00\xC0\x00\x00\x00\x16$1\x00REMOVE_CMAP_TO_BINARY,REMOVE_CMAP_TO_GRAYSCALE,REMOVE_CMAP_TO_FULL_COLOR,REMOVE_CMAP_WITH_ALPHA,REMOVE_CMAP_BASED_ON_SRC',b'\x00\x00\x00\xC1\x00\x00\x00\x16$2\x00L_NOCOPY,L_INSERT,L_COPY,L_CLONE,L_COPY_CLONE'), + _typenames = (b'\x00\x00\x00\xBABOX',b'\x00\x00\x00\xBBPIX',b'\x00\x00\x00\xBCPIXCMAP',b'\x00\x00\x00\x1Fl_float32',b'\x00\x00\x00\xBFl_float64',b'\x00\x00\x00\xC3l_int16',b'\x00\x00\x00\x02l_int32',b'\x00\x00\x00\xC2l_int64',b'\x00\x00\x00\xC4l_int8',b'\x00\x00\x00\xCBl_uint16',b'\x00\x00\x00\xC9l_uint32',b'\x00\x00\x00\xCAl_uint64',b'\x00\x00\x00\xC7l_uint8'), ) diff --git a/src/ocrmypdf/lib/compile_leptonica.py b/src/ocrmypdf/lib/compile_leptonica.py index 6da11266..5ffa7d87 100644 --- a/src/ocrmypdf/lib/compile_leptonica.py +++ b/src/ocrmypdf/lib/compile_leptonica.py @@ -72,6 +72,24 @@ struct Box }; typedef struct Box BOX; +enum { + REMOVE_CMAP_TO_BINARY = 0, /*!< remove colormap for conv to 1 bpp */ + REMOVE_CMAP_TO_GRAYSCALE = 1, /*!< remove colormap for conv to 8 bpp */ + REMOVE_CMAP_TO_FULL_COLOR = 2, /*!< remove colormap for conv to 32 bpp */ + REMOVE_CMAP_WITH_ALPHA = 3, /*!< remove colormap and alpha */ + REMOVE_CMAP_BASED_ON_SRC = 4 /*!< remove depending on src format */ +}; + +/*! Access and storage flags */ +enum { + L_NOCOPY = 0, /*!< do not copy the object; do not delete the ptr */ + L_INSERT = L_NOCOPY, /*!< stuff it in; do not copy or clone */ + L_COPY = 1, /*!< make/use a copy of the object */ + L_CLONE = 2, /*!< make/use clone (ref count) of the object */ + L_COPY_CLONE = 3 /*!< make a new array object (e.g., pixa) and fill */ + /*!< the array with clones (e.g., pix) */ +}; + """) ffibuilder.cdef(""" @@ -227,6 +245,11 @@ PIX * pixInvert(PIX * pixd, PIX * pixs); +PIX * +pixRemoveColormapGeneral(PIX *pixs, + l_int32 type, + l_int32 ifnocmap); + void boxDestroy(BOX **pbox); diff --git a/tests/resources/README.rst b/tests/resources/README.rst index 2b6e5683..c97c1061 100644 --- a/tests/resources/README.rst +++ b/tests/resources/README.rst @@ -147,6 +147,7 @@ Assemblies These test resources are assemblies or derivatives from other previously mentioned files, released under the same license terms as their input files. - baiona_gray.png (from baiona.png) +- baiona_colormapped.png (from baiona.png) - cardinal.pdf (four cardinal directions, baked-in rotated copies of LinnSequencer.jpg) - ccitt.pdf (LinnSequencer.jpg, converted to CCITT encoding) - encrypted_algo4.pdf (congress.jpg, encrypted with algorithm 4 - not supported by PyPDF2) diff --git a/tests/resources/baiona-colormapped.png b/tests/resources/baiona-colormapped.png new file mode 100644 index 0000000000000000000000000000000000000000..5b60b913db997b9e8d0501dd328711fed7c90c4e GIT binary patch literal 54170 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 G%4u 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 literal 0 HcmV?d00001 diff --git a/tests/test_lept.py b/tests/test_lept.py new file mode 100644 index 00000000..be68ea98 --- /dev/null +++ b/tests/test_lept.py @@ -0,0 +1,30 @@ +# © 2018 James R. Barlow: github.com/jbarlow83 +# +# This file is part of OCRmyPDF. +# +# OCRmyPDF is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# OCRmyPDF is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with OCRmyPDF. If not, see . + + +import os +import shutil +import pytest +import sys +import ocrmypdf.leptonica as lept + + +def test_colormap_backgroundnorm(resources): + # Issue #262 - unclear how to reproduce exactly, so just ensure leptonica + # can handle that case + pix = lept.Pix.read(resources / 'baiona-colormapped.png') + pix.background_norm() From 383e726d65fc17e5173efc2da576a279ff957fee Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Fri, 27 Apr 2018 19:34:57 -0700 Subject: [PATCH 08/17] Expand size growth reasons to other arguments that trigger transcoding --- src/ocrmypdf/__main__.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/ocrmypdf/__main__.py b/src/ocrmypdf/__main__.py index 2d0a0a0c..55bf6644 100755 --- a/src/ocrmypdf/__main__.py +++ b/src/ocrmypdf/__main__.py @@ -787,8 +787,20 @@ def report_output_file_size(options, _log, input_file, output_file): reasons = [] if not fitz: reasons.append("The optional dependency PyMuPDF is not installed.") - if options.force_ocr: - reasons.append("The argument --force-ocr was issued.") + image_preproc = { + 'deskew', + 'clean_final', + 'remove_background', + 'oversample', + 'force_ocr' + } + for arg in image_preproc: + attr = getattr(options, arg, None) + if not attr: + continue + reasons.append( + "The argument --{} was issued, causing transcoding.".format( + arg.replace('_', '-'))) if reasons: explanation = ( From df1129724c6b3b9c9c9c6a85a053c3a6dede5809 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Thu, 3 May 2018 01:27:13 -0700 Subject: [PATCH 09/17] Update Dockerfile for Ubuntu 18.04 --- .docker/Dockerfile | 19 +++++------- .docker/Dockerfile.tess4 | 64 ---------------------------------------- docs/installation.rst | 11 +++---- 3 files changed, 11 insertions(+), 83 deletions(-) delete mode 100644 .docker/Dockerfile.tess4 diff --git a/.docker/Dockerfile b/.docker/Dockerfile index 00ef700a..fed1b61a 100644 --- a/.docker/Dockerfile +++ b/.docker/Dockerfile @@ -1,22 +1,17 @@ # OCRmyPDF # -FROM ubuntu:17.10 +FROM ubuntu:18.04 RUN apt-get update && apt-get install -y --no-install-recommends \ - software-properties-common python-software-properties \ - python3-wheel \ - python3-reportlab \ - python3-venv \ - ghostscript \ - qpdf \ - poppler-utils \ - unpaper \ - libffi-dev \ - tesseract-ocr \ + ocrmypdf \ tesseract-ocr-eng \ tesseract-ocr-fra \ + tesseract-ocr-deu \ tesseract-ocr-spa \ - tesseract-ocr-deu + tesseract-ocr-por \ + tesseract-ocr-chi-sim \ + python3-venv \ + python3-pip ENV LANG=C.UTF-8 diff --git a/.docker/Dockerfile.tess4 b/.docker/Dockerfile.tess4 deleted file mode 100644 index 1f1b447b..00000000 --- a/.docker/Dockerfile.tess4 +++ /dev/null @@ -1,64 +0,0 @@ -# OCRmyPDF -# -FROM ubuntu:17.10 - -RUN apt-get update && apt-get install -y --no-install-recommends \ - software-properties-common python-software-properties \ - python3-wheel \ - python3-reportlab \ - python3-venv \ - ghostscript \ - qpdf \ - poppler-utils \ - unpaper \ - libffi-dev - -RUN add-apt-repository ppa:alex-p/tesseract-ocr - -RUN apt-get update \ - && apt-get autoremove -y \ - && apt-get install -y --no-install-recommends \ - tesseract-ocr \ - tesseract-ocr-eng \ - tesseract-ocr-fra \ - tesseract-ocr-deu \ - tesseract-ocr-spa \ - tesseract-ocr-por \ - tesseract-ocr-ara \ - tesseract-ocr-rus \ - tesseract-ocr-chi-sim - -ENV LANG=C.UTF-8 - -RUN python3 -m venv --system-site-packages /appenv - -# This installs the latest binary wheel instead of the code in the current -# folder. Installing from source will fail, apparently because cffi needs -# build-essentials (gcc) to do a source installation -# (i.e. "pip install ."). It's unclear to me why this is the case. -RUN . /appenv/bin/activate; \ - pip install --upgrade pip \ - && pip install ocrmypdf[fitz] - -# Now copy the application in, mainly to get the test suite. -# Do this now to make the best use of Docker cache. -COPY . /application -RUN . /appenv/bin/activate; \ - pip install -r /application/test_requirements.txt - -# Remove the junk, including the source version of application since it was -# already installed -RUN rm -rf /tmp/* /var/tmp/* /root/* /application/ocrmypdf \ - && apt-get autoremove -y \ - && apt-get autoclean -y - -RUN useradd docker \ - && mkdir /home/docker \ - && chown docker:docker /home/docker - -USER docker -WORKDIR /home/docker - -# Must use array form of ENTRYPOINT -# Non-array form does not append other arguments, because that is "intuitive" -ENTRYPOINT ["/application/.docker/docker-wrapper.sh"] diff --git a/docs/installation.rst b/docs/installation.rst index d858d926..c46b660c 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -77,25 +77,22 @@ Assuming you have a Docker engine running, you can download one of the three ava - Notes * - ocrmypdf - ``docker pull jbarlow83/ocrmypdf`` - - Latest ocrmypdf with Tesseract 3.x. Includes English, French, German, Spanish. + - Latest ocrmypdf with Tesseract 4.0.0-beta1 on Ubuntu 18.04. Includes English, French, German, Spanish, Portugeuse and Simplified Chinese. * - ocrmypdf-polyglot - ``docker pull jbarlow83/ocrmypdf-polyglot`` - As above, with all available language packs. - * - ocrmypdf-tess4 - - ``docker pull jbarlow83/ocrmypdf-tess4`` - - Latest ocrmypdf with Tesseract 4.x and English, French, German, Spanish, Portuguese, Chinese Simplified, Arabic and Russian (the top 8). For example: .. code-block:: bash - docker pull jbarlow83/ocrmypdf-tess4 + docker pull jbarlow83/ocrmypdf Then tag it to give a more convenient name, just ocrmypdf: .. code-block:: bash - docker tag jbarlow83/ocrmypdf-tess4 ocrmypdf + docker tag jbarlow83/ocrmypdf ocrmypdf .. _docker-polyglot: @@ -383,7 +380,7 @@ The following dependencies are recommended: These are in addition to the Python packaging dependencies, meaning that unfortunately, the ``pip install`` command cannot satisfy all of them. -Python 3.6 and Tesseract 4.x are recommended for best OCR results and best performance. +Python 3.6 and Tesseract 4.0.0-beta.1 are recommended for best OCR results and best performance. The library PyMuPDF is not widely available in platform distributions, and it improves OCRmyPDF in certain conditions. Consider installing OCRmyPDF from the Python binary wheels, which include a precompiled version of this library. From 98a0786c320217ca7bc2d3f43adf2738727ac9a2 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Thu, 3 May 2018 13:55:16 -0700 Subject: [PATCH 10/17] Add 18.04 update procedure --- docs/installation.rst | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/docs/installation.rst b/docs/installation.rst index c46b660c..8efaac2e 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -208,6 +208,26 @@ The command line program should now be available: ocrmypdf --help +Installing the latest version on Ubuntu 18.04 LTS +------------------------------------------------- + +Ubuntu 18.04 includes ocrmypdf 6.1.2. To install a more recent version, first +install the system version to get all the dependencies: + +.. code-block:: bash + + sudo apt-get update + sudo apt-get install \ + ocrmypdf \ + python3-pip + +Then install ocrmypdf 6.1.5 for the local user and set the user's ``PATH`` to check for the user's Python packages. + +.. code-block:: bash + + export PATH=$HOME/.local/bin:$PATH + pip3 install --user ocrmypdf[fitz] + Installing on Ubuntu 16.04 LTS ------------------------------ From 8a9f174f63ec09881de996ff6751cc830e80ff21 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Thu, 3 May 2018 16:30:20 -0700 Subject: [PATCH 11/17] Fix XMP validation issue with /CreationDate Related to previous validation issue. If the /CreationDate had no timezone, Ghostscript also creates invalid metadata. Work around this. Also fix up PDF date decoding, and transcode dates to standardize them. --- src/ocrmypdf/pdfa.py | 37 +++++++++++++++++++++---------------- tests/test_metadata.py | 13 +++++++++---- 2 files changed, 30 insertions(+), 20 deletions(-) diff --git a/src/ocrmypdf/pdfa.py b/src/ocrmypdf/pdfa.py index 405a4d4a..93f11a22 100644 --- a/src/ocrmypdf/pdfa.py +++ b/src/ocrmypdf/pdfa.py @@ -125,31 +125,31 @@ def encode_pdf_date(d: datetime) -> str: s = d.strftime(pdfmark_date_fmt) tz = d.strftime('%z') - if tz == 'Z': + if tz == 'Z' or tz == '': + # Ghostscript <= 9.23 handles missing timezones incorrectly, so if + # timezone is missing, move it into GMT. + # https://bugs.ghostscript.com/show_bug.cgi?id=699182 s += "+00'00'" - elif tz != '': + else: sign, tz_hours, tz_mins = tz[0], tz[1:3], tz[3:5] s += "{}{}'{}'".format(sign, tz_hours, tz_mins) - else: - raise ValueError("Naive timezone not supported") return s def decode_pdf_date(s: str) -> datetime: - pdfmark_date_fmts = ( - r'%Y%m%d%H%M%S%z', # +0430 etc - r'%Y%m%d%H%M%S', # no time zone - r'%Y%m%d%H%M%SZ') # trailing Z - if s.startswith('D:'): s = s[2:] + + # Literal Z00'00', is incorrect but found in the wild, + # probably made by OS X Quartz -- standardize + if s.endswith("Z00'00'"): + s = s.replace("Z00'00'", '+0000') + elif s.endswith('Z'): + s = s.replace('Z', '+0000') + s = s.replace("'", "") # Remove apos from PDF time strings - for fmt in pdfmark_date_fmts: - try: - return datetime.strptime(s, fmt) - except ValueError: - continue - return None + + return datetime.strptime(s, r'%Y%m%d%H%M%S%z') def _get_pdfmark_dates(pdfmark): @@ -170,7 +170,12 @@ def _get_pdfmark_dates(pdfmark): date_str = pdfmark[key] if date_str.startswith('D:'): date_str = date_str[2:] - yield ' {} (D:{})'.format(key, date_str) + try: + yield ' {} (D:{})'.format( + key, + encode_pdf_date(decode_pdf_date(date_str))) + except ValueError: + yield ' {} null'.format(key) def _get_pdfa_def(icc_profile, icc_identifier, pdfmark): diff --git a/tests/test_metadata.py b/tests/test_metadata.py index 4c6b4f94..adf5a361 100644 --- a/tests/test_metadata.py +++ b/tests/test_metadata.py @@ -73,11 +73,16 @@ def test_override_metadata(spoof_tesseract_noop, output_type, resources, assert p.returncode == ExitCode.ok, err - reader = pypdf.PdfFileReader(outpdf) + before = pypdf.PdfFileReader(str(input_file)) + after = pypdf.PdfFileReader(outpdf) - assert reader.documentInfo['/Title'] == german - assert reader.documentInfo['/Author'] == chinese - assert reader.documentInfo.get('/Keywords', '') == '' + assert after.documentInfo['/Title'] == german + assert after.documentInfo['/Author'] == chinese + assert after.documentInfo.get('/Keywords', '') == '' + + before_date = decode_pdf_date(before.documentInfo['/CreationDate']) + after_date = decode_pdf_date(after.documentInfo['/CreationDate']) + assert before_date == after_date pdfa_info = file_claims_pdfa(outpdf) assert pdfa_info['output'] == output_type From d607553e482f9b63ce9a89603d48d2387516cdc5 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Thu, 3 May 2018 16:47:21 -0700 Subject: [PATCH 12/17] v6.2.0 Release notes --- docs/release_notes.rst | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/docs/release_notes.rst b/docs/release_notes.rst index 5bc73c3a..618f8e71 100644 --- a/docs/release_notes.rst +++ b/docs/release_notes.rst @@ -9,10 +9,24 @@ The OCRmyPDF package itself does not contain a public API, although it is fairly find: [^`]\#([0-9]{1,3})[^0-9] replace: `#$1 `_ +v6.2.0 +------ + +- **Docker**: The Docker image ``ocrmypdf-tess4`` has been removed. The main Docker images, ``ocrmypdf`` and ``ocrmypdf-polyglot`` now use Ubuntu 18.04 as a base image, and as such Tesseract 4.0.0-beta1 is now the Tesseract version they use. There is no Docker image based on Tesseract 3.05 anymore. + +- Creation of PDF/A-3 is now supported. However, there is no ability to attach files to PDF/A-3. + +- List more reasons why the file size might grow. + +- Fix issue `#262 `_, ``--remove-background`` error on PDFs contained colormapped (paletted) images. + +- Fix another XMP metadata validation issue, in cases where the input file's creation date has no timezone and the creation date is not overridden. + + v6.1.5 ------ -- Fix issue `#253 `_, a possible division by zero when using the ``hocr`` renderer. +- Fix issue `#253 `_, a possible division by zero when using the ``hocr`` renderer. - Fix incorrectly formatted ```` field inside XMP metadata for PDF/As. veraPDF flags this as a PDF/A validation failure. The error is caused the timezone and final digit of the seconds of modified time to be omitted, so at worst the modification time stamp is rounded to the nearest 10 seconds. From b3ad3e297da1e565c1d6669fd45b1b7d5e615814 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Thu, 3 May 2018 17:04:23 -0700 Subject: [PATCH 13/17] v6.2.0 fixes --- docs/release_notes.rst | 2 +- src/ocrmypdf/__main__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/release_notes.rst b/docs/release_notes.rst index 618f8e71..bb980dd4 100644 --- a/docs/release_notes.rst +++ b/docs/release_notes.rst @@ -18,7 +18,7 @@ v6.2.0 - List more reasons why the file size might grow. -- Fix issue `#262 `_, ``--remove-background`` error on PDFs contained colormapped (paletted) images. +- Fix issue `#262 `_, ``--remove-background`` error on PDFs contained colormapped (paletted) images. - Fix another XMP metadata validation issue, in cases where the input file's creation date has no timezone and the creation date is not overridden. diff --git a/src/ocrmypdf/__main__.py b/src/ocrmypdf/__main__.py index b988ff26..16900ba3 100755 --- a/src/ocrmypdf/__main__.py +++ b/src/ocrmypdf/__main__.py @@ -396,7 +396,7 @@ def check_options_output(options, log): "Ignoring --debug-rendering because it requires --pdf-renderer=hocr") if options.output_type == 'pdfa': - options.output_type == 'pdfa-2' + options.output_type = 'pdfa-2' lossless_reconstruction = False if options.pdf_renderer in ('hocr', 'sandwich'): From 5c8a007f3ed8d15c7dc2a815c7692d544fb6b103 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Fri, 4 May 2018 13:34:34 -0700 Subject: [PATCH 14/17] Fix failure to prevent use of Ghostscript on /UserUnit files --- src/ocrmypdf/pipeline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ocrmypdf/pipeline.py b/src/ocrmypdf/pipeline.py index 9cc7226e..5f185864 100644 --- a/src/ocrmypdf/pipeline.py +++ b/src/ocrmypdf/pipeline.py @@ -222,7 +222,7 @@ def repair_and_parse_pdf( pdfinfo = PdfInfo(output_file) - if pdfinfo.has_userunit and options.output_type == 'pdfa': + if pdfinfo.has_userunit and options.output_type.startswith('pdfa'): log.error( "This input file uses a PDF feature that is not supported " "by Ghostscript, so you cannot use --output-type=pdfa for this " From 9226f8a5d1bd8dfd6e4b9d6b035de6a4703ea4fb Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Fri, 4 May 2018 15:29:43 -0700 Subject: [PATCH 15/17] Trap PDF/A-3 errors on old Ghostscript --- src/ocrmypdf/__main__.py | 5 +++++ tests/test_main.py | 3 +++ 2 files changed, 8 insertions(+) diff --git a/src/ocrmypdf/__main__.py b/src/ocrmypdf/__main__.py index 16900ba3..aa4490c4 100755 --- a/src/ocrmypdf/__main__.py +++ b/src/ocrmypdf/__main__.py @@ -398,6 +398,11 @@ def check_options_output(options, log): if options.output_type == 'pdfa': options.output_type = 'pdfa-2' + if options.output_type == 'pdfa-3' and ghostscript.version() < '9.19': + raise MissingDependencyError( + "--output-type pdfa-3 requires Ghostscript 9.19 or later" + ) + lossless_reconstruction = False if options.pdf_renderer in ('hocr', 'sandwich'): if not any((options.deskew, options.clean_final, options.force_ocr, diff --git a/tests/test_main.py b/tests/test_main.py index e08d31b1..c65f3cdd 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -953,6 +953,9 @@ def test_sidecar_nonempty(spoof_tesseract_cache, resources, outpdf): @pytest.mark.parametrize('pdfa_level', ['1', '2', '3']) def test_pdfa_n(spoof_tesseract_cache, pdfa_level, resources, outpdf): + if pdfa_level == '3' and ghostscript.version() < '9.19': + pytest.xfail(reason='Ghostscript >= 9.19 required') + check_ocrmypdf( resources / 'ccitt.pdf', outpdf, '--output-type', 'pdfa-' + pdfa_level, From 38ab03655b229c4206fa58ff18c2217c5b70d7c2 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Sun, 6 May 2018 21:36:12 -0700 Subject: [PATCH 16/17] Restore unpaper It's a suggested/recommended dep not required in Deb/Ubu. --- .docker/Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/.docker/Dockerfile b/.docker/Dockerfile index fed1b61a..3bd9bcd9 100644 --- a/.docker/Dockerfile +++ b/.docker/Dockerfile @@ -4,6 +4,7 @@ FROM ubuntu:18.04 RUN apt-get update && apt-get install -y --no-install-recommends \ ocrmypdf \ + unpaper \ tesseract-ocr-eng \ tesseract-ocr-fra \ tesseract-ocr-deu \ From c7cf041e4a33b22923d4e1cfb0e4a96c6926875d Mon Sep 17 00:00:00 2001 From: Fabian Rodriguez Date: Thu, 10 May 2018 03:10:27 -0400 Subject: [PATCH 17/17] Fixed language option example (French) (#266) Replace fre to fra. --- docs/cookbook.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/cookbook.rst b/docs/cookbook.rst index a3c4dbef..3f79152c 100644 --- a/docs/cookbook.rst +++ b/docs/cookbook.rst @@ -65,8 +65,8 @@ By default OCRmyPDF assumes the document is English. .. code-block:: bash - ocrmypdf -l fre LeParisien.pdf LeParisien.pdf - ocrmypdf -l eng+fre Bilingual-English-French.pdf Bilingual-English-French.pdf + ocrmypdf -l fra LeParisien.pdf LeParisien.pdf + ocrmypdf -l eng+fra Bilingual-English-French.pdf Bilingual-English-French.pdf Language packs must be installed for all languages specified. See :ref:`Installing additional language packs `.