From fa48205bb8fa74484f542ea1f9ba6ef02f5c2135 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Thu, 18 Oct 2018 21:46:08 -0700 Subject: [PATCH 01/68] Add feature to remove vector graphics objects --- src/ocrmypdf/__main__.py | 5 +++++ src/ocrmypdf/_pipeline.py | 3 ++- src/ocrmypdf/exec/ghostscript.py | 5 ++++- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/ocrmypdf/__main__.py b/src/ocrmypdf/__main__.py index 3e69effd..175118d4 100755 --- a/src/ocrmypdf/__main__.py +++ b/src/ocrmypdf/__main__.py @@ -244,6 +244,11 @@ preprocessing.add_argument( '--oversample', metavar='DPI', type=numeric(int, 0, 5000), default=0, help="Oversample images to at least the specified DPI, to improve OCR " "results slightly") +preprocessing.add_argument( + '--remove-vectors', action='store_true', + help="EXPERIMENTAL. Remove any vector graphics objects from the PDF, " + "including text rendered as curves. Useful when these objects " + "interfere with OCR.") ocrsettings = parser.add_argument_group( "OCR options", diff --git a/src/ocrmypdf/_pipeline.py b/src/ocrmypdf/_pipeline.py index e114d6b6..5bb057d6 100644 --- a/src/ocrmypdf/_pipeline.py +++ b/src/ocrmypdf/_pipeline.py @@ -493,7 +493,8 @@ def rasterize_with_ghostscript( ghostscript.rasterize_pdf( input_file, output_file, xres=canvas_dpi, yres=canvas_dpi, raster_device=device, log=log, page_dpi=(page_dpi, page_dpi), - pageno=page_number(input_file), rotation=correction) + pageno=page_number(input_file), rotation=correction, + filter_vector=options.remove_vectors) def preprocess_remove_background( diff --git a/src/ocrmypdf/exec/ghostscript.py b/src/ocrmypdf/exec/ghostscript.py index 60a0f3c6..d1abfe96 100644 --- a/src/ocrmypdf/exec/ghostscript.py +++ b/src/ocrmypdf/exec/ghostscript.py @@ -101,7 +101,7 @@ def extract_text(input_file, pageno=1): def rasterize_pdf(input_file, output_file, xres, yres, raster_device, log, - pageno=1, page_dpi=None, rotation=None): + pageno=1, page_dpi=None, rotation=None, filter_vector=False): """Rasterize one page of a PDF at resolution (xres, yres) in canvas units. The image is sized to match the integer pixels dimensions implied by @@ -116,6 +116,8 @@ def rasterize_pdf(input_file, output_file, xres, yres, raster_device, log, :param log: :param pageno: page number to rasterize (beginning at page 1) :param page_dpi: resolution tuple (x, y) overriding output image DPI + :param rotation: 0, 90, 180, 270: clockwise angle to rotate page + :param filter_vector: if True, remove vector graphics objects :return: """ res = xres, yres @@ -134,6 +136,7 @@ def rasterize_pdf(input_file, output_file, xres, yres, raster_device, log, '-dFirstPage=%i' % pageno, '-dLastPage=%i' % pageno, '-r{0}x{1}'.format(str(int_res[0]), str(int_res[1])), + ] + (['-dFILTERVECTOR'] if filter_vector else []) + [ '-o', tmp.name, '-dAutoRotatePages=/None', # Probably has no effect on raster '-f', From 16af753206e509fb4276f6d6235829448307814f Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Fri, 19 Oct 2018 00:02:19 -0700 Subject: [PATCH 02/68] Add functional "redo OCR" feature Needs argument validation and some other changes. Needs testing with mixed-content PDFs. Only really works for pure invisible text at the moment. --- src/ocrmypdf/__main__.py | 6 +++- src/ocrmypdf/_pipeline.py | 13 ++++--- src/ocrmypdf/_weave.py | 40 +++++++++++++++++++-- src/ocrmypdf/pdfinfo.py | 73 +++++++++++++++++++++++++++++---------- tests/test_pdfinfo.py | 10 ++++++ 5 files changed, 116 insertions(+), 26 deletions(-) diff --git a/src/ocrmypdf/__main__.py b/src/ocrmypdf/__main__.py index 3e69effd..94235d1e 100755 --- a/src/ocrmypdf/__main__.py +++ b/src/ocrmypdf/__main__.py @@ -250,13 +250,17 @@ ocrsettings = parser.add_argument_group( "Control how OCR is applied") ocrsettings.add_argument( '-f', '--force-ocr', action='store_true', - help="Rasterize any fonts or vector objects on each page, apply OCR, and " + help="Rasterize any text or vector objects on each page, apply OCR, and " "save the rastered output (this rewrites the PDF)") ocrsettings.add_argument( '-s', '--skip-text', action='store_true', help="Skip OCR on any pages that already contain text, but include the " "page in final output; useful for PDFs that contain a mix of " "images, text pages, and/or previously OCRed pages") +ocrsettings.add_argument( + '--redo-ocr', action='store_true', + help="Remove any invisible text, and apply OCR") + ocrsettings.add_argument( '--skip-big', type=numeric(float, 0, 5000), metavar='MPixels', help="Skip OCR on pages larger than the specified amount of megapixels, " diff --git a/src/ocrmypdf/_pipeline.py b/src/ocrmypdf/_pipeline.py index e114d6b6..b72a8603 100644 --- a/src/ocrmypdf/_pipeline.py +++ b/src/ocrmypdf/_pipeline.py @@ -243,7 +243,7 @@ def is_ocr_required(pageinfo, log, options): if pageinfo.has_text: msg = "{0:4d}: page already has text! – {1}" - if not options.force_ocr and not options.skip_text: + if not options.force_ocr and not (options.skip_text or options.redo_ocr): log.error(msg.format(page, "aborting (use --force-ocr to force OCR)")) raise PriorOcrFoundError() @@ -251,6 +251,10 @@ def is_ocr_required(pageinfo, log, options): log.info(msg.format(page, "rasterizing text and running OCR anyway")) ocr_required = True + elif options.redo_ocr and pageinfo.only_ocr_text: + log.info(msg.format(page, + "redoing OCR")) + ocr_required = True elif options.skip_text: log.info(msg.format(page, "skipping all processing on this page")) @@ -559,12 +563,13 @@ def select_ocr_image( user.""" image = infiles[0] - if context.get_options().force_ocr: + options = context.get_options() + pageinfo = get_pageinfo(image, context) + + if options.force_ocr or (options.redo_ocr and pageinfo.only_ocr_text): re_symlink(image, output_file, log) return - pageinfo = get_pageinfo(image, context) - with Image.open(image) as im: from PIL import ImageColor from PIL import ImageDraw diff --git a/src/ocrmypdf/_weave.py b/src/ocrmypdf/_weave.py index 7d08ffd6..7d97a6af 100644 --- a/src/ocrmypdf/_weave.py +++ b/src/ocrmypdf/_weave.py @@ -43,8 +43,39 @@ def _update_page_resources(*, page, font, font_key, procset): resources['/ProcSet'] = procset +def _strip_old_text(pdf, page): + stream = [] + in_text_obj = False + + page.page_contents_coalesce() + for operands, operator in pikepdf.parse_content_stream(page, ''): + if not in_text_obj: + if operator == pikepdf.Operator('BT'): + in_text_obj = True + else: + stream.append((operands, operator)) + else: + if operator == pikepdf.Operator('ET'): + in_text_obj = False + + def convert(op): + try: + return op.unparse() + except AttributeError: + return str(op).encode('ascii') + + lines = [] + for operands, operator in stream: + line = b' '.join(convert(op) for op in operands) + b' ' + operator.unparse() + lines.append(line) + + content_stream = b'\n'.join(lines) + page.Contents = pikepdf.Stream(pdf, content_stream) + + def _weave_layers_graft( - *, pdf_base, page_num, text, font, font_key, procset, rotation, log): + *, pdf_base, page_num, text, font, font_key, procset, rotation, + strip_old_text, log): """Insert the text layer from text page 0 on to pdf_base at page_num""" log.debug("Grafting") @@ -109,6 +140,9 @@ def _weave_layers_graft( new_text_layer = pikepdf.Stream(pdf_base, pdf_text_contents) + if strip_old_text: + _strip_old_text(pdf_base, base_page) + base_page.page_contents_add(new_text_layer, prepend=True) _update_page_resources( @@ -336,10 +370,12 @@ def weave_layers( if text and font: # Graft the text layer onto this page, whether new or old + strip_old = (context.get_options().redo_ocr + and pdfinfo[page_num - 1].only_ocr_text) _weave_layers_graft( pdf_base=pdf_base, page_num=page_num, text=text, font=font, font_key=font_key, rotation=text_misaligned, procset=procset, - log=log + strip_old_text=strip_old, log=log ) # Correct the rotation if applicable diff --git a/src/ocrmypdf/pdfinfo.py b/src/ocrmypdf/pdfinfo.py index a2748c23..fd54a729 100644 --- a/src/ocrmypdf/pdfinfo.py +++ b/src/ocrmypdf/pdfinfo.py @@ -107,6 +107,20 @@ ContentsInfo = namedtuple('ContentsInfo', ['xobject_settings', 'inline_images', 'found_text', 'found_vector']) +class VectorInfo: + def __init__(self): + pass + + +class TextInfo: + def __init__(self, invisible, visible): + self.invisible = invisible + self.visible = visible + + def __bool__(self): + return self.invisible or self.visible + + def _normalize_stack(graphobjs): """Convert runs of qQ's in the stack into single graphobjs""" for operands, operator in graphobjs: @@ -143,9 +157,14 @@ def _interpret_contents(contentstream, initial_shorthand=UNIT_SQUARE): xobject_settings = [] inline_images = [] found_text, found_vector = False, False - text_operators = set("""Tj " ' TJ""".split()) - vector_operators = set('S s f F f* B B* b b*'.split()) - operator_whitelist = """q Q Do cm TJ Tj " ' BI ID EI S s f F f* B B* b b*""" + found_invisible_text, found_visible_text = False, False + text_mode_ops = set("""BT ET Tr""".split()) + text_showing_ops = set("""Tj " ' TJ""".split()) + vector_ops = set('S s f F f* B B* b b*'.split()) + image_ops = set('BI ID EI q Q Do cm'.split()) + text_render_mode = 0 + operator_whitelist = ' '.join( + text_mode_ops | text_showing_ops | vector_ops | image_ops) for n, graphobj in enumerate(_normalize_stack( pikepdf.parse_content_stream(contentstream, operator_whitelist))): @@ -176,15 +195,25 @@ def _interpret_contents(contentstream, initial_shorthand=UNIT_SQUARE): iimage=iimage, shorthand=ctm.shorthand, stack_depth=len(stack)) inline_images.append(inline) - elif operator in text_operators: + elif operator in text_mode_ops: + if operator == 'BT': + text_render_mode = 0 + elif operator == 'Tr': + text_render_mode = operands[0] + elif operator in text_showing_ops: found_text = True - elif operator in vector_operators: + if text_render_mode == 3: + found_invisible_text = True + else: + found_visible_text = True + elif operator in vector_ops: found_vector = True return ContentsInfo( xobject_settings=xobject_settings, inline_images=inline_images, - found_text=found_text, + found_text=TextInfo(invisible=found_invisible_text, + visible=found_visible_text), found_vector=found_vector) @@ -252,11 +281,6 @@ def _get_dpi(ctm_shorthand, image_size): return dpi_w, dpi_h -class VectorInfo: - def __init__(self): - pass - - class ImageInfo: DPI_PREC = Decimal('1.000') @@ -444,11 +468,11 @@ def _find_form_xobject_images(pdf, container, contentsinfo): # but in practice both Form XObjects and multiple drawing of the # same object are both very rare. ctm_shorthand = settings.shorthand - yield from _find_images( + yield from _process_content_streams( pdf=pdf, container=form_xobject, shorthand=ctm_shorthand) -def _find_images(*, pdf, container, shorthand=None): +def _process_content_streams(*, pdf, container, shorthand=None): """Find all individual instances of images drawn in the container Usually the container is a page, but it may also be a Form XObject. @@ -491,6 +515,8 @@ def _find_images(*, pdf, container, shorthand=None): if contentsinfo.found_vector: yield VectorInfo() + if contentsinfo.found_text: + yield contentsinfo.found_text yield from _find_inline_images(contentsinfo) yield from _find_regular_images(container, contentsinfo) yield from _find_form_xobject_images(pdf, container, contentsinfo) @@ -591,15 +617,20 @@ def _pdf_get_pageinfo(pdf, pageno: int, infile, xmltext): pageinfo['rotate'] = 0 userunit_shorthand = (userunit, 0, 0, userunit, 0, 0) - pageinfo['images'] = [im for im in - _find_images(pdf=pdf, container=page, - shorthand=userunit_shorthand)] + contentsinfo = [ci for ci in + _process_content_streams(pdf=pdf, container=page, + shorthand=userunit_shorthand)] - if any(isinstance(im, VectorInfo) for im in pageinfo['images']): + pageinfo['has_vector'] = False + if any(isinstance(ci, VectorInfo) for ci in contentsinfo): pageinfo['has_vector'] = True - pageinfo['images'] = [im for im in pageinfo['images'] - if not isinstance(im, VectorInfo)] + textinfos = [ti for ti in contentsinfo if isinstance(ti, TextInfo)] + all_invisible = all(ti.invisible for ti in textinfos) and len(textinfos) > 0 + pageinfo['only_ocr_text'] = all_invisible + + pageinfo['images'] = [im for im in contentsinfo + if isinstance(im, ImageInfo)] if pageinfo['images']: xres = Decimal(max(image.xres for image in pageinfo['images'])) yres = Decimal(max(image.yres for image in pageinfo['images'])) @@ -666,6 +697,10 @@ class PageInfo: def has_vector(self): return self._pageinfo['has_vector'] + @property + def only_ocr_text(self): + return self._pageinfo['only_ocr_text'] + @property def width_inches(self): return self._pageinfo['width_inches'] diff --git a/tests/test_pdfinfo.py b/tests/test_pdfinfo.py index c3ee0b6d..574f8832 100644 --- a/tests/test_pdfinfo.py +++ b/tests/test_pdfinfo.py @@ -175,3 +175,13 @@ def test_vector(resources): filename = resources / 'vector.pdf' pdf = pdfinfo.PdfInfo(filename) assert pdf[0].has_vector + assert not pdf[0].only_ocr_text + assert not pdf[0].has_text + + +def test_ocr_detection(resources): + filename = resources / 'graph_ocred.pdf' + pdf = pdfinfo.PdfInfo(filename) + assert not pdf[0].has_vector + assert pdf[0].only_ocr_text + assert pdf[0].has_text From d11c428407f42d7cbcee77efbe48132ba7b5d472 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Sat, 20 Oct 2018 01:14:33 -0700 Subject: [PATCH 03/68] Redo OCR: disallow in cases that will damage the output PDF --- src/ocrmypdf/_pipeline.py | 17 +++++++++++++---- src/ocrmypdf/_weave.py | 14 ++++++++++++-- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/src/ocrmypdf/_pipeline.py b/src/ocrmypdf/_pipeline.py index 2ff54ca3..3eb20d28 100644 --- a/src/ocrmypdf/_pipeline.py +++ b/src/ocrmypdf/_pipeline.py @@ -251,9 +251,18 @@ def is_ocr_required(pageinfo, log, options): log.info(msg.format(page, "rasterizing text and running OCR anyway")) ocr_required = True - elif options.redo_ocr and pageinfo.only_ocr_text: - log.info(msg.format(page, - "redoing OCR")) + elif options.redo_ocr: + if pageinfo.only_ocr_text: + log.info(msg.format(page, + "redoing OCR")) + else: + log.error( + ("%4d: page has both printable and hidden text, so " + "--redo-ocr is not currently possible for this file. " + "Try --force-ocr."), + page + ) + raise PriorOcrFoundError() ocr_required = True elif options.skip_text: log.info(msg.format(page, @@ -590,7 +599,7 @@ def select_ocr_image( Decimal(bbox[1]) / Decimal(72) * yres, Decimal(bbox[2]) / Decimal(72) * xres, Decimal(bbox[3]) / Decimal(72) * yres] - pixcoords = [int(c) for c in pixcoords] + pixcoords = [int(round(c)) for c in pixcoords] log.debug('blanking %r', pixcoords) draw.rectangle(pixcoords, fill=white) #draw.rectangle(pixcoords, outline=pink) diff --git a/src/ocrmypdf/_weave.py b/src/ocrmypdf/_weave.py index 7d97a6af..5256c47a 100644 --- a/src/ocrmypdf/_weave.py +++ b/src/ocrmypdf/_weave.py @@ -43,20 +43,29 @@ def _update_page_resources(*, page, font, font_key, procset): resources['/ProcSet'] = procset -def _strip_old_text(pdf, page): +def _strip_old_invisible_text(pdf, page, log): stream = [] in_text_obj = False + render_mode = 0 + text_objects = [] page.page_contents_coalesce() for operands, operator in pikepdf.parse_content_stream(page, ''): if not in_text_obj: if operator == pikepdf.Operator('BT'): in_text_obj = True + render_mode = 0 else: stream.append((operands, operator)) else: + if operator == pikepdf.Operator('Tr'): + render_mode = operands[0] + text_objects.append((operands, operator)) if operator == pikepdf.Operator('ET'): in_text_obj = False + if render_mode != 3: + stream.extend(text_objects) + text_objects.clear() def convert(op): try: @@ -141,7 +150,7 @@ def _weave_layers_graft( new_text_layer = pikepdf.Stream(pdf_base, pdf_text_contents) if strip_old_text: - _strip_old_text(pdf_base, base_page) + _strip_old_invisible_text(pdf_base, base_page, log) base_page.page_contents_add(new_text_layer, prepend=True) @@ -370,6 +379,7 @@ def weave_layers( if text and font: # Graft the text layer onto this page, whether new or old +# strip_old = context.get_options().redo_ocr strip_old = (context.get_options().redo_ocr and pdfinfo[page_num - 1].only_ocr_text) _weave_layers_graft( From 0d396e1ac07cb141cadb3f9a69b69b47a2253417 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Mon, 22 Oct 2018 22:13:59 -0700 Subject: [PATCH 04/68] option check: Remove always-True condition Both renderers are now lossless reconstruction-capable. (Have been since 7.0) --- src/ocrmypdf/__main__.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/ocrmypdf/__main__.py b/src/ocrmypdf/__main__.py index 21f8f0c2..c21e2f06 100755 --- a/src/ocrmypdf/__main__.py +++ b/src/ocrmypdf/__main__.py @@ -470,10 +470,9 @@ def check_options_output(options, log): ) lossless_reconstruction = False - if options.pdf_renderer in ('hocr', 'sandwich'): - if not any((options.deskew, options.clean_final, options.force_ocr, - options.remove_background)): - lossless_reconstruction = True + if not any((options.deskew, options.clean_final, options.force_ocr, + options.remove_background)): + lossless_reconstruction = True options.lossless_reconstruction = lossless_reconstruction From a063cff720d00b5362e3e0891797bb0526958c7f Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Wed, 24 Oct 2018 21:53:24 -0700 Subject: [PATCH 05/68] Rename/expose strip_invisible_text --- src/ocrmypdf/_weave.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ocrmypdf/_weave.py b/src/ocrmypdf/_weave.py index 5256c47a..4480137f 100644 --- a/src/ocrmypdf/_weave.py +++ b/src/ocrmypdf/_weave.py @@ -43,7 +43,7 @@ def _update_page_resources(*, page, font, font_key, procset): resources['/ProcSet'] = procset -def _strip_old_invisible_text(pdf, page, log): +def strip_invisible_text(pdf, page, log): stream = [] in_text_obj = False render_mode = 0 @@ -150,7 +150,7 @@ def _weave_layers_graft( new_text_layer = pikepdf.Stream(pdf_base, pdf_text_contents) if strip_old_text: - _strip_old_invisible_text(pdf_base, base_page, log) + strip_invisible_text(pdf_base, base_page, log) base_page.page_contents_add(new_text_layer, prepend=True) From 2435cd23cea5858c1d63871c53a41e95dad6af53 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Thu, 25 Oct 2018 00:36:45 -0700 Subject: [PATCH 06/68] Move pdfinfo into a package --- src/ocrmypdf/{pdfinfo.py => pdfinfo/__init__.py} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename src/ocrmypdf/{pdfinfo.py => pdfinfo/__init__.py} (99%) diff --git a/src/ocrmypdf/pdfinfo.py b/src/ocrmypdf/pdfinfo/__init__.py similarity index 99% rename from src/ocrmypdf/pdfinfo.py rename to src/ocrmypdf/pdfinfo/__init__.py index fd54a729..3d733855 100644 --- a/src/ocrmypdf/pdfinfo.py +++ b/src/ocrmypdf/pdfinfo/__init__.py @@ -25,8 +25,8 @@ from unittest.mock import Mock import re import xml.etree.ElementTree as ET -from .exec import ghostscript -from .helpers import fspath +from ..exec import ghostscript +from ..helpers import fspath from pikepdf import PdfMatrix import pikepdf From ff41fbf67347a9d7261f3a65825d655f7f1ee832 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Thu, 25 Oct 2018 12:42:35 -0700 Subject: [PATCH 07/68] Add pdfminer based layout analysis --- src/ocrmypdf/pdfinfo/__init__.py | 6 +- src/ocrmypdf/pdfinfo/layout.py | 143 +++++++++++++++++++++++++++++++ 2 files changed, 146 insertions(+), 3 deletions(-) create mode 100644 src/ocrmypdf/pdfinfo/layout.py diff --git a/src/ocrmypdf/pdfinfo/__init__.py b/src/ocrmypdf/pdfinfo/__init__.py index 3d733855..bf89465d 100644 --- a/src/ocrmypdf/pdfinfo/__init__.py +++ b/src/ocrmypdf/pdfinfo/__init__.py @@ -25,11 +25,12 @@ from unittest.mock import Mock import re import xml.etree.ElementTree as ET +from pikepdf import PdfMatrix +import pikepdf + from ..exec import ghostscript from ..helpers import fspath -from pikepdf import PdfMatrix -import pikepdf Colorspace = Enum('Colorspace', 'gray rgb cmyk lab icc index sep devn pattern jpeg2000') @@ -418,7 +419,6 @@ def _find_regular_images(container, contentsinfo): that contains images. Generates images with their DPI at time of drawing. - """ for pdfimage, xobj in _image_xobjects(container): diff --git a/src/ocrmypdf/pdfinfo/layout.py b/src/ocrmypdf/pdfinfo/layout.py new file mode 100644 index 00000000..cda03dd7 --- /dev/null +++ b/src/ocrmypdf/pdfinfo/layout.py @@ -0,0 +1,143 @@ +# © 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 pdfminer.pdfinterp +import pdfminer.pdfdevice + +from pdfminer.pdfpage import PDFPage + +from pdfminer.utils import matrix2str, bbox2str, fsplit +from pdfminer.pdffont import PDFUnicodeNotDefined +from pdfminer.layout import ( + LTChar, LTContainer, LTLayoutContainer, LTPage, LTTextLine, LAParams +) + +from pdfminer.converter import PDFLayoutAnalyzer + +class LTStateAwareChar(LTChar): + """A subclass of LTChar that tracks text render mode at time of drawing""" + + def __init__(self, matrix, font, fontsize, scaling, rise, text, textwidth, textdisp, textstate, *args): + super().__init__(matrix, font, fontsize, scaling, rise, text, textwidth, textdisp, *args) + self.rendermode = textstate.render + + def is_compatible(self, obj): + """We are only compatible with same rendering mode""" + if not hasattr(obj, 'rendermode'): + return False + return self.rendermode == obj.rendermode + + def __repr__(self): + return ('<%s %s matrix=%s rendermode=%r font=%r adv=%s text=%r>' % + (self.__class__.__name__, bbox2str(self.bbox), + matrix2str(self.matrix), self.rendermode, self.fontname, self.adv, + self.get_text())) + + +class LTStateAwarePage(LTPage): + """A page container that exploits character type information""" + + def __init__(self, pageid, bbox, rotate=0): + LTPage.__init__(self, pageid, bbox, rotate) + + def analyze(self, laparams): + """Analysis taking rendering mode into account + + Looks at visible and invisible characters separately. + Depends on some superclass implementation details... + """ + + objs = self._objs[:] + + # Split into invisible text objects and all others + (invisible_textobjs, other_objs) = fsplit( + lambda obj: getattr(obj, 'rendermode', 0) == 3, self) + + # Analyze all invisible text objects and group them into text lines and + # text boxes + self._objs = invisible_textobjs + LTPage.analyze(self, laparams) + invisible_analyzed = self._objs[:] + + # Analyze all other objects + self._objs = other_objs + LTPage.analyze(self, laparams) + other_analyzed = self._objs[:] + + self._objs = invisible_analyzed + other_analyzed + self.visible = other_analyzed + self.invisible = invisible_analyzed + + +class TextPositionTracker(PDFLayoutAnalyzer): + """A page layout analyzer that pays attention to text visibility""" + + def __init__(self, rsrcmgr, pageno=1, laparams=None): + super().__init__(rsrcmgr, pageno, laparams) + self.textstate = None + self.result = None + + def begin_page(self, page, ctm): + super().begin_page(page, ctm) + self.cur_item = LTStateAwarePage(self.pageno, page.mediabox) + + def end_page(self, page): + assert not self._stack, str(len(self._stack)) + assert isinstance(self.cur_item, LTPage), str(type(self.cur_item)) + if self.laparams is not None: + self.cur_item.analyze(self.laparams) + self.pageno += 1 + self.receive_layout(self.cur_item) + + def render_string(self, textstate, seq, *args): + self.textstate = textstate.copy() + super().render_string(self.textstate, seq, *args) + + def render_char(self, matrix, font, fontsize, scaling, rise, cid, *args): + try: + text = font.to_unichr(cid) + assert isinstance(text, str), str(type(text)) + except PDFUnicodeNotDefined: + text = self.handle_undefined_char(font, cid) + textwidth = font.char_width(cid) + textdisp = font.char_disp(cid) + item = LTStateAwareChar( + matrix, font, fontsize, scaling, rise, text, textwidth, textdisp, self.textstate, *args) + self.cur_item.add(item) + return item.adv + + def handle_undefined_char(self, font, cid): + log.info('undefined: %r, %r', font, cid) + return '(cid:%d)' % cid + + def receive_layout(self, ltpage): + self.result = (ltpage.visible, ltpage.invisible) + + def get_result(self): + return self.result + + +def get_textblocks(infile, pageno): + rman = pdfminer.pdfinterp.PDFResourceManager(caching=True) + dev = TextPositionTracker(rman, laparams=LAParams()) + interp = pdfminer.pdfinterp.PDFPageInterpreter(rman, dev) + + page = PDFPage.get_pages(infile, pagenos=[pageno], maxpages=0) + + interp.process_page(next(page)) + + return dev.get_result() From 7ba0ff5c363d76cb401011dd32dccd6576f3f6cd Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Thu, 25 Oct 2018 16:51:01 -0700 Subject: [PATCH 08/68] Fix strip invisible text bug: missing BT operator --- src/ocrmypdf/_weave.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ocrmypdf/_weave.py b/src/ocrmypdf/_weave.py index 4480137f..1574314d 100644 --- a/src/ocrmypdf/_weave.py +++ b/src/ocrmypdf/_weave.py @@ -55,6 +55,7 @@ def strip_invisible_text(pdf, page, log): if operator == pikepdf.Operator('BT'): in_text_obj = True render_mode = 0 + text_objects.append((operands, operator)) else: stream.append((operands, operator)) else: From 339afb02aa17ba244deeca1fd18cbe73f5e906f8 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Thu, 25 Oct 2018 16:53:47 -0700 Subject: [PATCH 09/68] --redo-ocr now works in the presence of printable text --- src/ocrmypdf/_pipeline.py | 4 ++-- src/ocrmypdf/_weave.py | 4 +--- src/ocrmypdf/pdfinfo/__init__.py | 39 ++++++++++++++++++++++++++++---- 3 files changed, 37 insertions(+), 10 deletions(-) diff --git a/src/ocrmypdf/_pipeline.py b/src/ocrmypdf/_pipeline.py index 3eb20d28..519e6ace 100644 --- a/src/ocrmypdf/_pipeline.py +++ b/src/ocrmypdf/_pipeline.py @@ -576,7 +576,7 @@ def select_ocr_image( options = context.get_options() pageinfo = get_pageinfo(image, context) - if options.force_ocr or (options.redo_ocr and pageinfo.only_ocr_text): + if options.force_ocr: re_symlink(image, output_file, log) return @@ -590,7 +590,7 @@ def select_ocr_image( xres, yres = im.info['dpi'] log.debug('resolution %r %r', xres, yres) - for textarea in pageinfo.get_textareas(): + for textarea in pageinfo.get_textareas(visible=True): # Calculate resolution based on the image size and page dimensions # without regard whatever resolution is in pageinfo (may differ or # be None) diff --git a/src/ocrmypdf/_weave.py b/src/ocrmypdf/_weave.py index 1574314d..c719796e 100644 --- a/src/ocrmypdf/_weave.py +++ b/src/ocrmypdf/_weave.py @@ -380,9 +380,7 @@ def weave_layers( if text and font: # Graft the text layer onto this page, whether new or old -# strip_old = context.get_options().redo_ocr - strip_old = (context.get_options().redo_ocr - and pdfinfo[page_num - 1].only_ocr_text) + strip_old = context.get_options().redo_ocr _weave_layers_graft( pdf_base=pdf_base, page_num=page_num, text=text, font=font, font_key=font_key, rotation=text_misaligned, procset=procset, diff --git a/src/ocrmypdf/pdfinfo/__init__.py b/src/ocrmypdf/pdfinfo/__init__.py index bf89465d..0ea265f7 100644 --- a/src/ocrmypdf/pdfinfo/__init__.py +++ b/src/ocrmypdf/pdfinfo/__init__.py @@ -594,15 +594,30 @@ def _pdf_get_pageinfo(pdf, pageno: int, infile, xmltext): page = pdf.pages[pageno] - pageinfo['textinfo'] = _page_get_textblocks( - fspath(infile), pageno, xmltext=xmltext) + # pageinfo['textinfo'] = _page_get_textblocks( + # fspath(infile), pageno, xmltext=xmltext) + + from .layout import get_textblocks + with Path(infile).open('rb') as f: + pageinfo['objects'] = get_textblocks(f, pageno) mediabox = [Decimal(d) for d in page.MediaBox.as_list()] width_pt = mediabox[2] - mediabox[0] height_pt = mediabox[3] - mediabox[1] + def bboxes(hierarchical_textinfo): + from pdfminer.layout import LTTextLine, LTTextBox + for obj in hierarchical_textinfo: + if isinstance(hierarchical_textinfo, (LTTextLine, LTTextBox)): + yield hierarchical_textinfo.bbox + else: + try: + yield from bboxes(obj) + except TypeError: + continue + pageinfo['has_text'] = _page_has_text( - pageinfo['textinfo'], width_pt, height_pt) + bboxes(pageinfo['objects']), width_pt, height_pt) userunit = page.get('/UserUnit', Decimal(1.0)) if not isinstance(userunit, Decimal): @@ -732,8 +747,22 @@ class PageInfo: def images(self): return self._pageinfo['images'] - def get_textareas(self): - yield from self._pageinfo['textinfo'] + def get_textareas(self, visible=True, invisible=True): + def bboxes(objs): + from pdfminer.layout import LTTextBox + for obj in objs: + if isinstance(obj, LTTextBox): + yield obj.bbox + else: + try: + yield from bboxes(obj) + except TypeError: + continue + + if visible: + yield from bboxes(self._pageinfo['objects'][0]) + if invisible: + yield from bboxes(self._pageinfo['objects'][1]) @property def xres(self): From 58cc70725e0c64b02c2ec3293a7979c46071816c Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Fri, 26 Oct 2018 01:07:02 -0700 Subject: [PATCH 10/68] Reorganize around getting bboxes for visible/invisible text --- src/ocrmypdf/pdfinfo/__init__.py | 24 +----------------------- src/ocrmypdf/pdfinfo/layout.py | 16 ++++++++++++++-- tests/test_pdfinfo.py | 2 +- 3 files changed, 16 insertions(+), 26 deletions(-) diff --git a/src/ocrmypdf/pdfinfo/__init__.py b/src/ocrmypdf/pdfinfo/__init__.py index 0ea265f7..b83442ed 100644 --- a/src/ocrmypdf/pdfinfo/__init__.py +++ b/src/ocrmypdf/pdfinfo/__init__.py @@ -28,6 +28,7 @@ import xml.etree.ElementTree as ET from pikepdf import PdfMatrix import pikepdf +from .layout import get_textblocks, bboxes from ..exec import ghostscript from ..helpers import fspath @@ -597,7 +598,6 @@ def _pdf_get_pageinfo(pdf, pageno: int, infile, xmltext): # pageinfo['textinfo'] = _page_get_textblocks( # fspath(infile), pageno, xmltext=xmltext) - from .layout import get_textblocks with Path(infile).open('rb') as f: pageinfo['objects'] = get_textblocks(f, pageno) @@ -605,17 +605,6 @@ def _pdf_get_pageinfo(pdf, pageno: int, infile, xmltext): width_pt = mediabox[2] - mediabox[0] height_pt = mediabox[3] - mediabox[1] - def bboxes(hierarchical_textinfo): - from pdfminer.layout import LTTextLine, LTTextBox - for obj in hierarchical_textinfo: - if isinstance(hierarchical_textinfo, (LTTextLine, LTTextBox)): - yield hierarchical_textinfo.bbox - else: - try: - yield from bboxes(obj) - except TypeError: - continue - pageinfo['has_text'] = _page_has_text( bboxes(pageinfo['objects']), width_pt, height_pt) @@ -748,17 +737,6 @@ class PageInfo: return self._pageinfo['images'] def get_textareas(self, visible=True, invisible=True): - def bboxes(objs): - from pdfminer.layout import LTTextBox - for obj in objs: - if isinstance(obj, LTTextBox): - yield obj.bbox - else: - try: - yield from bboxes(obj) - except TypeError: - continue - if visible: yield from bboxes(self._pageinfo['objects'][0]) if invisible: diff --git a/src/ocrmypdf/pdfinfo/layout.py b/src/ocrmypdf/pdfinfo/layout.py index cda03dd7..63f7cde4 100644 --- a/src/ocrmypdf/pdfinfo/layout.py +++ b/src/ocrmypdf/pdfinfo/layout.py @@ -23,7 +23,8 @@ from pdfminer.pdfpage import PDFPage from pdfminer.utils import matrix2str, bbox2str, fsplit from pdfminer.pdffont import PDFUnicodeNotDefined from pdfminer.layout import ( - LTChar, LTContainer, LTLayoutContainer, LTPage, LTTextLine, LAParams + LTChar, LTContainer, LTLayoutContainer, LTPage, LTTextLine, LAParams, + LTTextBox ) from pdfminer.converter import PDFLayoutAnalyzer @@ -121,7 +122,7 @@ class TextPositionTracker(PDFLayoutAnalyzer): return item.adv def handle_undefined_char(self, font, cid): - log.info('undefined: %r, %r', font, cid) + #log.info('undefined: %r, %r', font, cid) return '(cid:%d)' % cid def receive_layout(self, ltpage): @@ -141,3 +142,14 @@ def get_textblocks(infile, pageno): interp.process_page(next(page)) return dev.get_result() + + +def bboxes(hierarchical_textinfo): + for obj in hierarchical_textinfo: + if isinstance(hierarchical_textinfo, (LTTextBox)): + yield hierarchical_textinfo.bbox + else: + try: + yield from bboxes(obj) + except TypeError: + continue diff --git a/tests/test_pdfinfo.py b/tests/test_pdfinfo.py index 574f8832..6057737e 100644 --- a/tests/test_pdfinfo.py +++ b/tests/test_pdfinfo.py @@ -145,7 +145,7 @@ def test_pickle(resources): # For multiprocessing we must be able to pickle our information - if # this fails then we are probably storing some unpickleabe pikepdf or # other external data around - filename = resources / 'formxobject.pdf' + filename = resources / 'graph_ocred.pdf' pdf = pdfinfo.PdfInfo(filename) pickle.dumps(pdf) From b12c2cfedf0a2673f17390e769b81e060edfbf55 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Sat, 27 Oct 2018 01:24:48 -0700 Subject: [PATCH 11/68] Fix handling of Type3 fonts with no ToUnicode mapping --- src/ocrmypdf/pdfinfo/layout.py | 69 +++++++++++++++++++++++++++++----- 1 file changed, 59 insertions(+), 10 deletions(-) diff --git a/src/ocrmypdf/pdfinfo/layout.py b/src/ocrmypdf/pdfinfo/layout.py index 63f7cde4..4040e032 100644 --- a/src/ocrmypdf/pdfinfo/layout.py +++ b/src/ocrmypdf/pdfinfo/layout.py @@ -15,32 +15,79 @@ # You should have received a copy of the GNU General Public License # along with OCRmyPDF. If not, see . +import pdfminer.encodingdb import pdfminer.pdfinterp import pdfminer.pdfdevice -from pdfminer.pdfpage import PDFPage - -from pdfminer.utils import matrix2str, bbox2str, fsplit -from pdfminer.pdffont import PDFUnicodeNotDefined +from pdfminer.converter import PDFLayoutAnalyzer +from pdfminer.glyphlist import glyphname2unicode from pdfminer.layout import ( LTChar, LTContainer, LTLayoutContainer, LTPage, LTTextLine, LAParams, LTTextBox ) +from pdfminer.pdffont import PDFUnicodeNotDefined, PDFType3Font +from pdfminer.pdfpage import PDFPage +from pdfminer.utils import matrix2str, bbox2str, fsplit + + +def PDFType3Font_to_unichr(self, cid): + """Patch Type3 fonts to fix misinterpretation of gids as Unicode mapping + + In a Type3 font the /Encoding /Differences [ ] array describes the mapping + of character codes to glyph numbers like /g178 where 178 is an index into + Type3 font's /CharProcs data structure. + + See PDF RM 1.7: 9.6.6.3 Encodings for Type 3 Fonts + + There is no correspondence between glyph numbers and Unicode, however, + except by coincidence. So, if there is no ToUnicode table, then Unicode + mapping is impossible. + + """ + try: + if self.unicode_map: + return self.unicode_map.get_unichr(cid) + except KeyError: + pass + raise PDFUnicodeNotDefined(None, cid) + +PDFType3Font.to_unichr = PDFType3Font_to_unichr -from pdfminer.converter import PDFLayoutAnalyzer class LTStateAwareChar(LTChar): """A subclass of LTChar that tracks text render mode at time of drawing""" + __slots__ = ( + 'rendermode', '_text', 'matrix', 'fontname', 'adv', 'upright', 'size', + 'width', 'height', 'bbox', 'x0', 'x1', 'y0', 'y1' + ) + def __init__(self, matrix, font, fontsize, scaling, rise, text, textwidth, textdisp, textstate, *args): super().__init__(matrix, font, fontsize, scaling, rise, text, textwidth, textdisp, *args) self.rendermode = textstate.render def is_compatible(self, obj): - """We are only compatible with same rendering mode""" - if not hasattr(obj, 'rendermode'): + """Check if characters can be combined into a textline + + We consider characters compatible if: + - the Unicode mapping is known, and both have the same render mode + - the Unicode mapping is unknown but both are part of the same font + """ + both_unicode_mapped = (isinstance(self._text, str) and + isinstance(obj._text, str)) + try: + if both_unicode_mapped: + return self.rendermode == obj.rendermode + font0, _ = self._text + font1, _ = obj._text + return font0 == font1 and self.rendermode == obj.rendermode + except (ValueError, AttributeError): return False - return self.rendermode == obj.rendermode + + def get_text(self): + if isinstance(self._text, tuple): + return '�' + return self._text def __repr__(self): return ('<%s %s matrix=%s rendermode=%r font=%r adv=%s text=%r>' % @@ -59,7 +106,9 @@ class LTStateAwarePage(LTPage): """Analysis taking rendering mode into account Looks at visible and invisible characters separately. - Depends on some superclass implementation details... + Depends on some superclass implementation details, largely because only + LTPage has the "group into textboxes" code, so we have to manipulate + our _objs to create multiple collections. """ objs = self._objs[:] @@ -123,7 +172,7 @@ class TextPositionTracker(PDFLayoutAnalyzer): def handle_undefined_char(self, font, cid): #log.info('undefined: %r, %r', font, cid) - return '(cid:%d)' % cid + return (font, cid) def receive_layout(self, ltpage): self.result = (ltpage.visible, ltpage.invisible) From 0e4d978d200b372318539c2b3f855fb9632a7915 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Sat, 27 Oct 2018 23:22:28 -0700 Subject: [PATCH 12/68] pdfinfo: all -> not any --- src/ocrmypdf/pdfinfo/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ocrmypdf/pdfinfo/__init__.py b/src/ocrmypdf/pdfinfo/__init__.py index b83442ed..6be7fd63 100644 --- a/src/ocrmypdf/pdfinfo/__init__.py +++ b/src/ocrmypdf/pdfinfo/__init__.py @@ -630,7 +630,7 @@ def _pdf_get_pageinfo(pdf, pageno: int, infile, xmltext): pageinfo['has_vector'] = True textinfos = [ti for ti in contentsinfo if isinstance(ti, TextInfo)] - all_invisible = all(ti.invisible for ti in textinfos) and len(textinfos) > 0 + all_invisible = not any(ti.visible for ti in textinfos) pageinfo['only_ocr_text'] = all_invisible pageinfo['images'] = [im for im in contentsinfo From e6d64be89062e99ca68a7368cd43298dad62e4b6 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Sat, 27 Oct 2018 23:22:44 -0700 Subject: [PATCH 13/68] pdfinfo: formatting --- src/ocrmypdf/pdfinfo/layout.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ocrmypdf/pdfinfo/layout.py b/src/ocrmypdf/pdfinfo/layout.py index 4040e032..daa65f48 100644 --- a/src/ocrmypdf/pdfinfo/layout.py +++ b/src/ocrmypdf/pdfinfo/layout.py @@ -166,7 +166,8 @@ class TextPositionTracker(PDFLayoutAnalyzer): textwidth = font.char_width(cid) textdisp = font.char_disp(cid) item = LTStateAwareChar( - matrix, font, fontsize, scaling, rise, text, textwidth, textdisp, self.textstate, *args) + matrix, font, fontsize, scaling, rise, text, + textwidth, textdisp, self.textstate, *args) self.cur_item.add(item) return item.adv From fda890ab47e130cf24c1a5cafa2285513a3a20a2 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Sun, 28 Oct 2018 14:05:50 -0700 Subject: [PATCH 14/68] pdfinfo: further layout improvements Rather than grouping visible/invisible in a custom analysis step, use pdfminer's analysis and iterate. Make iteration predicate and return more generic. --- src/ocrmypdf/pdfinfo/__init__.py | 31 ++++++++------ src/ocrmypdf/pdfinfo/layout.py | 72 +++++++++++++------------------- 2 files changed, 46 insertions(+), 57 deletions(-) diff --git a/src/ocrmypdf/pdfinfo/__init__.py b/src/ocrmypdf/pdfinfo/__init__.py index 6be7fd63..04d00105 100644 --- a/src/ocrmypdf/pdfinfo/__init__.py +++ b/src/ocrmypdf/pdfinfo/__init__.py @@ -28,7 +28,7 @@ import xml.etree.ElementTree as ET from pikepdf import PdfMatrix import pikepdf -from .layout import get_textblocks, bboxes +from .layout import get_textblocks, filter_textboxes, textbox_predicate from ..exec import ghostscript from ..helpers import fspath @@ -567,18 +567,19 @@ def _page_has_text(text_blocks, page_width, page_height): margin_ratio = 0.125 interior_bbox = ( - margin_ratio * pw, margin_ratio * ph, - (1 - margin_ratio) * pw, (1 - margin_ratio) * ph + margin_ratio * pw, # left + (1 - margin_ratio) * ph, # top + (1 - margin_ratio) * pw, # right + margin_ratio * ph # bottom (first quadrant: bottom < top) ) def rects_intersect(a, b): """ Where (a,b) are 4-tuple rects (left-0, top-1, right-2, bottom-3) https://stackoverflow.com/questions/306316/determine-if-two-rectangles-overlap-each-other - Negative signs to account for our coordinates being in the fourth quadrant - and the formula assuming the first + Formula assumes all boxes are in first quadrant """ - return a[0] < b[2] and a[2] > b[0] and -a[1] > -b[3] and -a[3] < -b[1] + return a[0] < b[2] and a[2] > b[0] and a[1] > b[3] and a[3] < b[1] has_text = False for bbox in text_blocks: @@ -605,8 +606,12 @@ def _pdf_get_pageinfo(pdf, pageno: int, infile, xmltext): width_pt = mediabox[2] - mediabox[0] height_pt = mediabox[3] - mediabox[1] + bboxes = (textbox.bbox for textbox in filter_textboxes( + pageinfo['objects'], lambda obj: True) + ) pageinfo['has_text'] = _page_has_text( - bboxes(pageinfo['objects']), width_pt, height_pt) + bboxes, width_pt, height_pt + ) userunit = page.get('/UserUnit', Decimal(1.0)) if not isinstance(userunit, Decimal): @@ -629,7 +634,7 @@ def _pdf_get_pageinfo(pdf, pageno: int, infile, xmltext): if any(isinstance(ci, VectorInfo) for ci in contentsinfo): pageinfo['has_vector'] = True - textinfos = [ti for ti in contentsinfo if isinstance(ti, TextInfo)] + textinfos = (ti for ti in contentsinfo if isinstance(ti, TextInfo)) all_invisible = not any(ti.visible for ti in textinfos) pageinfo['only_ocr_text'] = all_invisible @@ -736,11 +741,11 @@ class PageInfo: def images(self): return self._pageinfo['images'] - def get_textareas(self, visible=True, invisible=True): - if visible: - yield from bboxes(self._pageinfo['objects'][0]) - if invisible: - yield from bboxes(self._pageinfo['objects'][1]) + def get_textareas(self, visible=None, corrupt=None): + return (obj.bbox for obj in filter_textboxes( + self._pageinfo['objects'], + textbox_predicate(visible=visible, corrupt=corrupt) + )) @property def xres(self): diff --git a/src/ocrmypdf/pdfinfo/layout.py b/src/ocrmypdf/pdfinfo/layout.py index daa65f48..b580a1c8 100644 --- a/src/ocrmypdf/pdfinfo/layout.py +++ b/src/ocrmypdf/pdfinfo/layout.py @@ -96,43 +96,6 @@ class LTStateAwareChar(LTChar): self.get_text())) -class LTStateAwarePage(LTPage): - """A page container that exploits character type information""" - - def __init__(self, pageid, bbox, rotate=0): - LTPage.__init__(self, pageid, bbox, rotate) - - def analyze(self, laparams): - """Analysis taking rendering mode into account - - Looks at visible and invisible characters separately. - Depends on some superclass implementation details, largely because only - LTPage has the "group into textboxes" code, so we have to manipulate - our _objs to create multiple collections. - """ - - objs = self._objs[:] - - # Split into invisible text objects and all others - (invisible_textobjs, other_objs) = fsplit( - lambda obj: getattr(obj, 'rendermode', 0) == 3, self) - - # Analyze all invisible text objects and group them into text lines and - # text boxes - self._objs = invisible_textobjs - LTPage.analyze(self, laparams) - invisible_analyzed = self._objs[:] - - # Analyze all other objects - self._objs = other_objs - LTPage.analyze(self, laparams) - other_analyzed = self._objs[:] - - self._objs = invisible_analyzed + other_analyzed - self.visible = other_analyzed - self.invisible = invisible_analyzed - - class TextPositionTracker(PDFLayoutAnalyzer): """A page layout analyzer that pays attention to text visibility""" @@ -143,7 +106,7 @@ class TextPositionTracker(PDFLayoutAnalyzer): def begin_page(self, page, ctm): super().begin_page(page, ctm) - self.cur_item = LTStateAwarePage(self.pageno, page.mediabox) + self.cur_item = LTPage(self.pageno, page.mediabox) def end_page(self, page): assert not self._stack, str(len(self._stack)) @@ -176,7 +139,7 @@ class TextPositionTracker(PDFLayoutAnalyzer): return (font, cid) def receive_layout(self, ltpage): - self.result = (ltpage.visible, ltpage.invisible) + self.result = ltpage def get_result(self): return self.result @@ -194,12 +157,33 @@ def get_textblocks(infile, pageno): return dev.get_result() -def bboxes(hierarchical_textinfo): - for obj in hierarchical_textinfo: - if isinstance(hierarchical_textinfo, (LTTextBox)): - yield hierarchical_textinfo.bbox +def textbox_predicate(*, visible, corrupt): + def real_predicate(textbox, want_visible=visible, want_corrupt=corrupt): + textline = textbox._objs[0] + first_char = textline._objs[0] + + result = True + + is_visible = (first_char.rendermode != 3) + if want_visible is not None: + if is_visible != want_visible: + result = False + is_corrupt = (first_char.get_text() == '\ufffd') + if want_corrupt is not None: + if is_corrupt != want_corrupt: + result = False + + return result + return real_predicate + + +def filter_textboxes(obj, predicate): + for child in obj: + if isinstance(child, (LTTextBox)): + if predicate(child): + yield child else: try: - yield from bboxes(obj) + yield from filter_textboxes(child, predicate) except TypeError: continue From 5ac2d31d0d5869fde1d0deacc740ec52f87d1d13 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Sun, 28 Oct 2018 14:06:25 -0700 Subject: [PATCH 15/68] Redo OCR can now handle visible and invisible text, so adjust accordingly Still can't filter out corrupt text --- src/ocrmypdf/_pipeline.py | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/src/ocrmypdf/_pipeline.py b/src/ocrmypdf/_pipeline.py index 519e6ace..eef05497 100644 --- a/src/ocrmypdf/_pipeline.py +++ b/src/ocrmypdf/_pipeline.py @@ -252,17 +252,8 @@ def is_ocr_required(pageinfo, log, options): "rasterizing text and running OCR anyway")) ocr_required = True elif options.redo_ocr: - if pageinfo.only_ocr_text: - log.info(msg.format(page, - "redoing OCR")) - else: - log.error( - ("%4d: page has both printable and hidden text, so " - "--redo-ocr is not currently possible for this file. " - "Try --force-ocr."), - page - ) - raise PriorOcrFoundError() + log.info(msg.format(page, + "redoing OCR")) ocr_required = True elif options.skip_text: log.info(msg.format(page, @@ -590,15 +581,21 @@ def select_ocr_image( xres, yres = im.info['dpi'] log.debug('resolution %r %r', xres, yres) - for textarea in pageinfo.get_textareas(visible=True): + + mask = None # Exclude both visible and invisible text from OCR + if options.redo_ocr: + mask = True # Mask visible text, but not invisible text + + for textarea in pageinfo.get_textareas(visible=mask, corrupt=None): # Calculate resolution based on the image size and page dimensions # without regard whatever resolution is in pageinfo (may differ or # be None) bbox = textarea - pixcoords = [Decimal(bbox[0]) / Decimal(72) * xres, - Decimal(bbox[1]) / Decimal(72) * yres, - Decimal(bbox[2]) / Decimal(72) * xres, - Decimal(bbox[3]) / Decimal(72) * yres] + xscale, yscale = float(xres) / 72.0, float(yres) / 72.0 + pixcoords = [bbox[0] * xscale, + im.height - bbox[1] * yscale, + bbox[2] * xscale, + im.height - bbox[3] * yscale] pixcoords = [int(round(c)) for c in pixcoords] log.debug('blanking %r', pixcoords) draw.rectangle(pixcoords, fill=white) From f564aaf485aa530b8fcb50fcc5ca7ee949b7eb2d Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Sun, 28 Oct 2018 22:41:18 -0700 Subject: [PATCH 16/68] Remove only_ocr_text --- src/ocrmypdf/pdfinfo/__init__.py | 8 -------- tests/test_pdfinfo.py | 2 -- 2 files changed, 10 deletions(-) diff --git a/src/ocrmypdf/pdfinfo/__init__.py b/src/ocrmypdf/pdfinfo/__init__.py index 04d00105..5d836f06 100644 --- a/src/ocrmypdf/pdfinfo/__init__.py +++ b/src/ocrmypdf/pdfinfo/__init__.py @@ -634,10 +634,6 @@ def _pdf_get_pageinfo(pdf, pageno: int, infile, xmltext): if any(isinstance(ci, VectorInfo) for ci in contentsinfo): pageinfo['has_vector'] = True - textinfos = (ti for ti in contentsinfo if isinstance(ti, TextInfo)) - all_invisible = not any(ti.visible for ti in textinfos) - pageinfo['only_ocr_text'] = all_invisible - pageinfo['images'] = [im for im in contentsinfo if isinstance(im, ImageInfo)] if pageinfo['images']: @@ -706,10 +702,6 @@ class PageInfo: def has_vector(self): return self._pageinfo['has_vector'] - @property - def only_ocr_text(self): - return self._pageinfo['only_ocr_text'] - @property def width_inches(self): return self._pageinfo['width_inches'] diff --git a/tests/test_pdfinfo.py b/tests/test_pdfinfo.py index 6057737e..51bdf820 100644 --- a/tests/test_pdfinfo.py +++ b/tests/test_pdfinfo.py @@ -175,7 +175,6 @@ def test_vector(resources): filename = resources / 'vector.pdf' pdf = pdfinfo.PdfInfo(filename) assert pdf[0].has_vector - assert not pdf[0].only_ocr_text assert not pdf[0].has_text @@ -183,5 +182,4 @@ def test_ocr_detection(resources): filename = resources / 'graph_ocred.pdf' pdf = pdfinfo.PdfInfo(filename) assert not pdf[0].has_vector - assert pdf[0].only_ocr_text assert pdf[0].has_text From 00ef53195ee7572d938ec21e10480672b53ec22b Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Mon, 29 Oct 2018 01:30:19 -0700 Subject: [PATCH 17/68] Fix corrupt Unicode mapping detection's false positives --- src/ocrmypdf/pdfinfo/layout.py | 31 ++++++++----------------------- 1 file changed, 8 insertions(+), 23 deletions(-) diff --git a/src/ocrmypdf/pdfinfo/layout.py b/src/ocrmypdf/pdfinfo/layout.py index b580a1c8..861eee5e 100644 --- a/src/ocrmypdf/pdfinfo/layout.py +++ b/src/ocrmypdf/pdfinfo/layout.py @@ -15,6 +15,8 @@ # You should have received a copy of the GNU General Public License # along with OCRmyPDF. If not, see . +import re + import pdfminer.encodingdb import pdfminer.pdfinterp import pdfminer.pdfdevice @@ -30,29 +32,12 @@ from pdfminer.pdfpage import PDFPage from pdfminer.utils import matrix2str, bbox2str, fsplit -def PDFType3Font_to_unichr(self, cid): - """Patch Type3 fonts to fix misinterpretation of gids as Unicode mapping - - In a Type3 font the /Encoding /Differences [ ] array describes the mapping - of character codes to glyph numbers like /g178 where 178 is an index into - Type3 font's /CharProcs data structure. - - See PDF RM 1.7: 9.6.6.3 Encodings for Type 3 Fonts - - There is no correspondence between glyph numbers and Unicode, however, - except by coincidence. So, if there is no ToUnicode table, then Unicode - mapping is impossible. - - """ - try: - if self.unicode_map: - return self.unicode_map.get_unichr(cid) - except KeyError: - pass - raise PDFUnicodeNotDefined(None, cid) - -PDFType3Font.to_unichr = PDFType3Font_to_unichr - +# Fix pdfminer's regex in name2unicode function +# Font cids that are mapped to names of the form /g123 seem to be, by convention +# characters with no corresponding Unicode entry. These can be subsetted fonts +# or symbolic fonts. There seems to be no way to map /g123 fonts to Unicode, +# barring a ToUnicode data structure. +pdfminer.encodingdb.STRIP_NAME = re.compile(r'[^g][0-9]+') class LTStateAwareChar(LTChar): """A subclass of LTChar that tracks text render mode at time of drawing""" From efec6da377f1c7f43c8b854525f567e8a6efb6a0 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Mon, 29 Oct 2018 02:02:00 -0700 Subject: [PATCH 18/68] Fix error on serializing bad character markers (Since they held a reference to their font, which in turn, had an open file handle.) --- src/ocrmypdf/pdfinfo/layout.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ocrmypdf/pdfinfo/layout.py b/src/ocrmypdf/pdfinfo/layout.py index 861eee5e..90c8c627 100644 --- a/src/ocrmypdf/pdfinfo/layout.py +++ b/src/ocrmypdf/pdfinfo/layout.py @@ -121,7 +121,7 @@ class TextPositionTracker(PDFLayoutAnalyzer): def handle_undefined_char(self, font, cid): #log.info('undefined: %r, %r', font, cid) - return (font, cid) + return (font.fontname, cid) def receive_layout(self, ltpage): self.result = ltpage From 8e396f4be2932851ae5e3a794a6b7968bf1030a6 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Mon, 29 Oct 2018 02:03:58 -0700 Subject: [PATCH 19/68] Document --redo-ocr more accurately --- src/ocrmypdf/__main__.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/ocrmypdf/__main__.py b/src/ocrmypdf/__main__.py index c21e2f06..bea152cf 100755 --- a/src/ocrmypdf/__main__.py +++ b/src/ocrmypdf/__main__.py @@ -264,8 +264,10 @@ ocrsettings.add_argument( "images, text pages, and/or previously OCRed pages") ocrsettings.add_argument( '--redo-ocr', action='store_true', - help="Remove any invisible text, and apply OCR") - + help="Attempt to detect and remove the hidden OCR layer from files that " + "were previously OCRed with OCRmyPDF or another program. Apply OCR " + "to text found in raster images. Existing visible text objects will " + "not be changed. If there is no existing OCR, OCR will be added.") ocrsettings.add_argument( '--skip-big', type=numeric(float, 0, 5000), metavar='MPixels', help="Skip OCR on pages larger than the specified amount of megapixels, " From de80fb6bc8edb7fc4f57a75255f554d11da97647 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Mon, 29 Oct 2018 11:49:38 -0700 Subject: [PATCH 20/68] Fix some failing tests after --redo-ocr changes --- src/ocrmypdf/pdfinfo/__init__.py | 35 ++++++++++++++++++++++++-------- src/ocrmypdf/pdfinfo/layout.py | 8 +++++++- tests/test_main.py | 2 +- 3 files changed, 35 insertions(+), 10 deletions(-) diff --git a/src/ocrmypdf/pdfinfo/__init__.py b/src/ocrmypdf/pdfinfo/__init__.py index 5d836f06..2f866fa3 100644 --- a/src/ocrmypdf/pdfinfo/__init__.py +++ b/src/ocrmypdf/pdfinfo/__init__.py @@ -589,6 +589,18 @@ def _page_has_text(text_blocks, page_width, page_height): return has_text +def simplify_textboxes(miner): + for box in filter_textboxes(miner, lambda x: True): + result = {} + first_line = box._objs[0] + first_char = first_line._objs[0] + + result['is_visible'] = (first_char.rendermode != 3) + result['is_corrupt'] = (first_char.get_text() == '\ufffd') + result['bbox'] = box.bbox + yield result + + def _pdf_get_pageinfo(pdf, pageno: int, infile, xmltext): pageinfo = {} pageinfo['pageno'] = pageno @@ -600,15 +612,14 @@ def _pdf_get_pageinfo(pdf, pageno: int, infile, xmltext): # fspath(infile), pageno, xmltext=xmltext) with Path(infile).open('rb') as f: - pageinfo['objects'] = get_textblocks(f, pageno) + miner = get_textblocks(f, pageno) + pageinfo['textobjs'] = list(simplify_textboxes(miner)) mediabox = [Decimal(d) for d in page.MediaBox.as_list()] width_pt = mediabox[2] - mediabox[0] height_pt = mediabox[3] - mediabox[1] - bboxes = (textbox.bbox for textbox in filter_textboxes( - pageinfo['objects'], lambda obj: True) - ) + bboxes = (obj['bbox'] for obj in pageinfo['textobjs']) pageinfo['has_text'] = _page_has_text( bboxes, width_pt, height_pt ) @@ -734,10 +745,18 @@ class PageInfo: return self._pageinfo['images'] def get_textareas(self, visible=None, corrupt=None): - return (obj.bbox for obj in filter_textboxes( - self._pageinfo['objects'], - textbox_predicate(visible=visible, corrupt=corrupt) - )) + def predicate(obj, want_visible, want_corrupt): + result = True + if want_visible is not None: + if obj['is_visible'] != want_visible: + result = False + if want_corrupt is not None: + if obj['is_corrupt'] != want_corrupt: + result = False + return result + + return (obj['bbox'] for obj in self._pageinfo['textobjs'] + if predicate(obj, visible, corrupt)) @property def xres(self): diff --git a/src/ocrmypdf/pdfinfo/layout.py b/src/ocrmypdf/pdfinfo/layout.py index 90c8c627..ffef6870 100644 --- a/src/ocrmypdf/pdfinfo/layout.py +++ b/src/ocrmypdf/pdfinfo/layout.py @@ -22,6 +22,7 @@ import pdfminer.pdfinterp import pdfminer.pdfdevice from pdfminer.converter import PDFLayoutAnalyzer +from pdfminer.pdfdocument import PDFTextExtractionNotAllowed from pdfminer.glyphlist import glyphname2unicode from pdfminer.layout import ( LTChar, LTContainer, LTLayoutContainer, LTPage, LTTextLine, LAParams, @@ -31,6 +32,8 @@ from pdfminer.pdffont import PDFUnicodeNotDefined, PDFType3Font from pdfminer.pdfpage import PDFPage from pdfminer.utils import matrix2str, bbox2str, fsplit +from ..exceptions import EncryptedPdfError + # Fix pdfminer's regex in name2unicode function # Font cids that are mapped to names of the form /g123 seem to be, by convention @@ -137,7 +140,10 @@ def get_textblocks(infile, pageno): page = PDFPage.get_pages(infile, pagenos=[pageno], maxpages=0) - interp.process_page(next(page)) + try: + interp.process_page(next(page)) + except PDFTextExtractionNotAllowed as e: + raise EncryptedPdfError() return dev.get_result() diff --git a/tests/test_main.py b/tests/test_main.py index 8f18a8b2..785f6d85 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -377,7 +377,7 @@ def test_tesseract_image_too_big(renderer, spoof_tesseract_big_image_error, def test_algo4(resources, spoof_tesseract_noop, outpdf): p, _, _ = run_ocrmypdf(resources / 'encrypted_algo4.pdf', outpdf, env=spoof_tesseract_noop) - assert p.returncode == ExitCode.ok + assert p.returncode == ExitCode.encrypted_pdf @pytest.mark.parametrize('renderer', RENDERERS) From 05aa43c856499d1f6b7d1d6f328fb71f0881ca08 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Mon, 29 Oct 2018 12:45:15 -0700 Subject: [PATCH 21/68] Require pdfminer --- requirements/main.txt | 3 ++- setup.py | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/requirements/main.txt b/requirements/main.txt index c4f7021a..63df1da1 100644 --- a/requirements/main.txt +++ b/requirements/main.txt @@ -3,7 +3,8 @@ # installation cffi == 1.11.5 img2pdf == 0.3.0 -pikepdf == 0.3.4 +pdfminer == 20170720 +pikepdf == 0.3.6 Pillow >= 5.0.0, != 5.1.0 ; sys_platform == "darwin" pycparser == 2.18 python-xmp-toolkit == 2.0.1 diff --git a/setup.py b/setup.py index 2838dc9b..8fc71313 100644 --- a/setup.py +++ b/setup.py @@ -251,6 +251,7 @@ setup( install_requires=[ 'cffi >= 1.9.1', # must be a setup and install requirement 'img2pdf >= 0.2.4, < 0.4', # pure Python, so track HEAD closely + 'pdfminer.six == 20170720', 'pikepdf >= 0.3.3, < 0.4', 'Pillow >= 4.0.0, != 5.1.0 ; sys_platform == "darwin"', # Pillow < 4 has BytesIO/TIFF bug w/img2pdf 0.2.3 From d71fd089cb9beddfca6e15eb66e09de297f9baae Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Mon, 29 Oct 2018 14:45:59 -0700 Subject: [PATCH 22/68] layout: allow names beginning with /i0123 for now Showed up in GGastro2.pdf. Need to check if this pattern has valid Unicode mappings but allow for now. --- src/ocrmypdf/pdfinfo/layout.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ocrmypdf/pdfinfo/layout.py b/src/ocrmypdf/pdfinfo/layout.py index ffef6870..4f2d002b 100644 --- a/src/ocrmypdf/pdfinfo/layout.py +++ b/src/ocrmypdf/pdfinfo/layout.py @@ -40,7 +40,7 @@ from ..exceptions import EncryptedPdfError # characters with no corresponding Unicode entry. These can be subsetted fonts # or symbolic fonts. There seems to be no way to map /g123 fonts to Unicode, # barring a ToUnicode data structure. -pdfminer.encodingdb.STRIP_NAME = re.compile(r'[^g][0-9]+') +pdfminer.encodingdb.STRIP_NAME = re.compile(r'(?![g])([0-9]+)') class LTStateAwareChar(LTChar): """A subclass of LTChar that tracks text render mode at time of drawing""" From 93623b2226db2943940d9dff5d3ca30eb77a1a46 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Mon, 29 Oct 2018 14:46:40 -0700 Subject: [PATCH 23/68] Refactor TextboxInfo --- src/ocrmypdf/pdfinfo/__init__.py | 31 ++++++++++++++++++------------- src/ocrmypdf/pdfinfo/layout.py | 29 ++++------------------------- 2 files changed, 22 insertions(+), 38 deletions(-) diff --git a/src/ocrmypdf/pdfinfo/__init__.py b/src/ocrmypdf/pdfinfo/__init__.py index 2f866fa3..da14389f 100644 --- a/src/ocrmypdf/pdfinfo/__init__.py +++ b/src/ocrmypdf/pdfinfo/__init__.py @@ -28,7 +28,7 @@ import xml.etree.ElementTree as ET from pikepdf import PdfMatrix import pikepdf -from .layout import get_textblocks, filter_textboxes, textbox_predicate +from .layout import get_page_analysis, get_text_boxes from ..exec import ghostscript from ..helpers import fspath @@ -108,6 +108,9 @@ InlineSettings = namedtuple('InlineSettings', ContentsInfo = namedtuple('ContentsInfo', ['xobject_settings', 'inline_images', 'found_text', 'found_vector']) +TextBoxInfo = namedtuple('TextBoxInfo', + ['bbox', 'is_visible', 'is_corrupt']) + class VectorInfo: def __init__(self): @@ -590,15 +593,17 @@ def _page_has_text(text_blocks, page_width, page_height): def simplify_textboxes(miner): - for box in filter_textboxes(miner, lambda x: True): - result = {} + """Extract only limited content from text boxes + + We do this to save memory and ensure that our objects are pickleable. + """ + for box in get_text_boxes(miner): first_line = box._objs[0] first_char = first_line._objs[0] - result['is_visible'] = (first_char.rendermode != 3) - result['is_corrupt'] = (first_char.get_text() == '\ufffd') - result['bbox'] = box.bbox - yield result + visible = (first_char.rendermode != 3) + corrupt = (first_char.get_text() == '\ufffd') + yield TextBoxInfo(box.bbox, visible, corrupt) def _pdf_get_pageinfo(pdf, pageno: int, infile, xmltext): @@ -612,14 +617,14 @@ def _pdf_get_pageinfo(pdf, pageno: int, infile, xmltext): # fspath(infile), pageno, xmltext=xmltext) with Path(infile).open('rb') as f: - miner = get_textblocks(f, pageno) - pageinfo['textobjs'] = list(simplify_textboxes(miner)) + miner = get_page_analysis(f, pageno) + pageinfo['textboxes'] = list(simplify_textboxes(miner)) mediabox = [Decimal(d) for d in page.MediaBox.as_list()] width_pt = mediabox[2] - mediabox[0] height_pt = mediabox[3] - mediabox[1] - bboxes = (obj['bbox'] for obj in pageinfo['textobjs']) + bboxes = (box.bbox for box in pageinfo['textboxes']) pageinfo['has_text'] = _page_has_text( bboxes, width_pt, height_pt ) @@ -748,14 +753,14 @@ class PageInfo: def predicate(obj, want_visible, want_corrupt): result = True if want_visible is not None: - if obj['is_visible'] != want_visible: + if obj.is_visible != want_visible: result = False if want_corrupt is not None: - if obj['is_corrupt'] != want_corrupt: + if obj.is_corrupt != want_corrupt: result = False return result - return (obj['bbox'] for obj in self._pageinfo['textobjs'] + return (obj.bbox for obj in self._pageinfo['textboxes'] if predicate(obj, visible, corrupt)) @property diff --git a/src/ocrmypdf/pdfinfo/layout.py b/src/ocrmypdf/pdfinfo/layout.py index 4f2d002b..0991ceb4 100644 --- a/src/ocrmypdf/pdfinfo/layout.py +++ b/src/ocrmypdf/pdfinfo/layout.py @@ -133,7 +133,7 @@ class TextPositionTracker(PDFLayoutAnalyzer): return self.result -def get_textblocks(infile, pageno): +def get_page_analysis(infile, pageno): rman = pdfminer.pdfinterp.PDFResourceManager(caching=True) dev = TextPositionTracker(rman, laparams=LAParams()) interp = pdfminer.pdfinterp.PDFPageInterpreter(rman, dev) @@ -148,33 +148,12 @@ def get_textblocks(infile, pageno): return dev.get_result() -def textbox_predicate(*, visible, corrupt): - def real_predicate(textbox, want_visible=visible, want_corrupt=corrupt): - textline = textbox._objs[0] - first_char = textline._objs[0] - - result = True - - is_visible = (first_char.rendermode != 3) - if want_visible is not None: - if is_visible != want_visible: - result = False - is_corrupt = (first_char.get_text() == '\ufffd') - if want_corrupt is not None: - if is_corrupt != want_corrupt: - result = False - - return result - return real_predicate - - -def filter_textboxes(obj, predicate): +def get_text_boxes(obj): for child in obj: if isinstance(child, (LTTextBox)): - if predicate(child): - yield child + yield child else: try: - yield from filter_textboxes(child, predicate) + yield from get_text_boxes(child) except TypeError: continue From 7acd75f013830f8d170d7278743510db68bed0d7 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Mon, 29 Oct 2018 22:26:37 -0700 Subject: [PATCH 24/68] pipeline: fix bbox coordinates --- src/ocrmypdf/_pipeline.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ocrmypdf/_pipeline.py b/src/ocrmypdf/_pipeline.py index eef05497..ff4e60dc 100644 --- a/src/ocrmypdf/_pipeline.py +++ b/src/ocrmypdf/_pipeline.py @@ -593,9 +593,9 @@ def select_ocr_image( bbox = textarea xscale, yscale = float(xres) / 72.0, float(yres) / 72.0 pixcoords = [bbox[0] * xscale, - im.height - bbox[1] * yscale, + im.height - bbox[3] * yscale, bbox[2] * xscale, - im.height - bbox[3] * yscale] + im.height - bbox[1] * yscale] pixcoords = [int(round(c)) for c in pixcoords] log.debug('blanking %r', pixcoords) draw.rectangle(pixcoords, fill=white) From ebf6acb3186eacccdd48e703bb13ce9b3a4c547c Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Mon, 29 Oct 2018 22:27:25 -0700 Subject: [PATCH 25/68] pdfminer patch: Type3 font height calculation is incorrect Not sure where it goes wrong or why it needs special treatment, but this does address it. --- src/ocrmypdf/pdfinfo/layout.py | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/src/ocrmypdf/pdfinfo/layout.py b/src/ocrmypdf/pdfinfo/layout.py index 0991ceb4..edf48935 100644 --- a/src/ocrmypdf/pdfinfo/layout.py +++ b/src/ocrmypdf/pdfinfo/layout.py @@ -42,6 +42,23 @@ from ..exceptions import EncryptedPdfError # barring a ToUnicode data structure. pdfminer.encodingdb.STRIP_NAME = re.compile(r'(?![g])([0-9]+)') +from math import copysign +def PDFType3Font__get_height(self): + h = self.bbox[3]-self.bbox[1] + if h == 0: + h = self.ascent - self.descent + return h * copysign(1.0, self.vscale) + +def PDFType3Font__get_descent(self): + return self.descent * copysign(1.0, self.vscale) + +def PDFType3Font__get_ascent(self): + return self.ascent * copysign(1.0, self.vscale) + +PDFType3Font.get_height = PDFType3Font__get_height +PDFType3Font.get_ascent = PDFType3Font__get_ascent +PDFType3Font.get_descent = PDFType3Font__get_descent + class LTStateAwareChar(LTChar): """A subclass of LTChar that tracks text render mode at time of drawing""" @@ -50,8 +67,10 @@ class LTStateAwareChar(LTChar): 'width', 'height', 'bbox', 'x0', 'x1', 'y0', 'y1' ) - def __init__(self, matrix, font, fontsize, scaling, rise, text, textwidth, textdisp, textstate, *args): - super().__init__(matrix, font, fontsize, scaling, rise, text, textwidth, textdisp, *args) + def __init__(self, matrix, font, fontsize, scaling, rise, text, textwidth, + textdisp, textstate, *args): + super().__init__(matrix, font, fontsize, scaling, rise, text, textwidth, + textdisp, *args) self.rendermode = textstate.render def is_compatible(self, obj): From 559e5269d2ecadf06584310830b5898e60cbc7b2 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Mon, 29 Oct 2018 23:30:53 -0700 Subject: [PATCH 26/68] Ensure inline image is parsed correctly Requires pikepdf > 0.3.6 --- src/ocrmypdf/_weave.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/ocrmypdf/_weave.py b/src/ocrmypdf/_weave.py index c719796e..995878b6 100644 --- a/src/ocrmypdf/_weave.py +++ b/src/ocrmypdf/_weave.py @@ -75,8 +75,13 @@ def strip_invisible_text(pdf, page, log): return str(op).encode('ascii') lines = [] + for operands, operator in stream: - line = b' '.join(convert(op) for op in operands) + b' ' + operator.unparse() + if operator == pikepdf.Operator('INLINE IMAGE'): + iim = operands[0] + line = iim.unparse() + else: + line = b' '.join(convert(op) for op in operands) + b' ' + operator.unparse() lines.append(line) content_stream = b'\n'.join(lines) From 8b61d2d5214ca236ea4bef182901f7147456b626 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Tue, 30 Oct 2018 14:40:53 -0700 Subject: [PATCH 27/68] pdfminer: If font descent claims to be positive, treat it as negative --- src/ocrmypdf/pdfinfo/layout.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/ocrmypdf/pdfinfo/layout.py b/src/ocrmypdf/pdfinfo/layout.py index edf48935..4a393004 100644 --- a/src/ocrmypdf/pdfinfo/layout.py +++ b/src/ocrmypdf/pdfinfo/layout.py @@ -28,7 +28,7 @@ from pdfminer.layout import ( LTChar, LTContainer, LTLayoutContainer, LTPage, LTTextLine, LAParams, LTTextBox ) -from pdfminer.pdffont import PDFUnicodeNotDefined, PDFType3Font +from pdfminer.pdffont import PDFUnicodeNotDefined, PDFType3Font, PDFFont, PDFCIDFont from pdfminer.pdfpage import PDFPage from pdfminer.utils import matrix2str, bbox2str, fsplit @@ -59,6 +59,22 @@ PDFType3Font.get_height = PDFType3Font__get_height PDFType3Font.get_ascent = PDFType3Font__get_ascent PDFType3Font.get_descent = PDFType3Font__get_descent + +original_PDFFont_init = PDFFont.__init__ +def PDFFont__init__(self, descriptor, widths, default_width=None): + original_PDFFont_init(self, descriptor, widths, default_width) + # PDF spec says descent should be negative + # A font with a positive descent implies it floats entirely above the + # baseline, i.e. it's not really a baseline anymore. I have fonts that + # claim a positive descent, but treating descent as positive always seems + # to misposition text. + if self.descent > 0: + self.descent = -self.descent + +PDFFont.__init__ = PDFFont__init__ + + + class LTStateAwareChar(LTChar): """A subclass of LTChar that tracks text render mode at time of drawing""" From 22a7cd34210334334b3a5c8035b4db78e9d3d5d3 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Tue, 30 Oct 2018 16:19:13 -0700 Subject: [PATCH 28/68] Add argument checks for --redo-ocr --- 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 bea152cf..8fb4589f 100755 --- a/src/ocrmypdf/__main__.py +++ b/src/ocrmypdf/__main__.py @@ -477,6 +477,13 @@ def check_options_output(options, log): lossless_reconstruction = True options.lossless_reconstruction = lossless_reconstruction + if not options.lossless_reconstruction and options.redo_ocr: + raise argparse.ArgumentError( + None, + "--redo-ocr is not currently compatible with --deskew, " + "--clean-final, and --remove-background" + ) + def check_options_sidecar(options, log): if options.sidecar == '\0': @@ -521,10 +528,15 @@ def check_options_preprocessing(options, log): def check_options_ocr_behavior(options, log): - if options.force_ocr and options.skip_text: + exclusive_options = sum( + [(1 if opt else 0) + for opt in (options.force_ocr, options.skip_text, options.redo_ocr) + ] + ) + if exclusive_options >= 2: raise argparse.ArgumentError( None, - "Error: --force-ocr and --skip-text are mutually exclusive.") + "Error: choose only one of --force-ocr, --skip-text, --redo-ocr.") def check_options_optimizing(options, log): From be31cec33217977bf58ab85a93c08e31d6e49f03 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Tue, 30 Oct 2018 16:19:58 -0700 Subject: [PATCH 29/68] Add corrupt text warning (when using --redo-ocr) --- src/ocrmypdf/_pipeline.py | 7 +++++++ src/ocrmypdf/pdfinfo/__init__.py | 8 ++++++-- src/ocrmypdf/pdfinfo/layout.py | 27 +++++++++++++++++++++------ 3 files changed, 34 insertions(+), 8 deletions(-) diff --git a/src/ocrmypdf/_pipeline.py b/src/ocrmypdf/_pipeline.py index ff4e60dc..2ba691b1 100644 --- a/src/ocrmypdf/_pipeline.py +++ b/src/ocrmypdf/_pipeline.py @@ -252,6 +252,13 @@ def is_ocr_required(pageinfo, log, options): "rasterizing text and running OCR anyway")) ocr_required = True elif options.redo_ocr: + if pageinfo.has_corrupt_text: + log.warning(msg.format( + page, + "some text on this page cannot be mapped to characters: " + "consider using --force-ocr instead") + ) + else: log.info(msg.format(page, "redoing OCR")) ocr_required = True diff --git a/src/ocrmypdf/pdfinfo/__init__.py b/src/ocrmypdf/pdfinfo/__init__.py index da14389f..842340bd 100644 --- a/src/ocrmypdf/pdfinfo/__init__.py +++ b/src/ocrmypdf/pdfinfo/__init__.py @@ -108,7 +108,7 @@ InlineSettings = namedtuple('InlineSettings', ContentsInfo = namedtuple('ContentsInfo', ['xobject_settings', 'inline_images', 'found_text', 'found_vector']) -TextBoxInfo = namedtuple('TextBoxInfo', +TextboxInfo = namedtuple('TextboxInfo', ['bbox', 'is_visible', 'is_corrupt']) @@ -603,7 +603,7 @@ def simplify_textboxes(miner): visible = (first_char.rendermode != 3) corrupt = (first_char.get_text() == '\ufffd') - yield TextBoxInfo(box.bbox, visible, corrupt) + yield TextboxInfo(box.bbox, visible, corrupt) def _pdf_get_pageinfo(pdf, pageno: int, infile, xmltext): @@ -714,6 +714,10 @@ class PageInfo: def has_text(self): return self._pageinfo['has_text'] + @property + def has_corrupt_text(self): + return any(tbox.is_corrupt for tbox in self._pageinfo['textboxes']) + @property def has_vector(self): return self._pageinfo['has_vector'] diff --git a/src/ocrmypdf/pdfinfo/layout.py b/src/ocrmypdf/pdfinfo/layout.py index 4a393004..b8a1bc59 100644 --- a/src/ocrmypdf/pdfinfo/layout.py +++ b/src/ocrmypdf/pdfinfo/layout.py @@ -35,12 +35,27 @@ from pdfminer.utils import matrix2str, bbox2str, fsplit from ..exceptions import EncryptedPdfError -# Fix pdfminer's regex in name2unicode function -# Font cids that are mapped to names of the form /g123 seem to be, by convention -# characters with no corresponding Unicode entry. These can be subsetted fonts -# or symbolic fonts. There seems to be no way to map /g123 fonts to Unicode, -# barring a ToUnicode data structure. -pdfminer.encodingdb.STRIP_NAME = re.compile(r'(?![g])([0-9]+)') + +STRIP_NAME = re.compile(r'[0-9]+') + +def name2unicode(name): + """Fix pdfminer's regex in name2unicode function + + Font cids that are mapped to names of the form /g123 seem to be, by convention + characters with no corresponding Unicode entry. These can be subsetted fonts + or symbolic fonts. There seems to be no way to map /g123 fonts to Unicode, + barring a ToUnicode data structure. + """ + if name in glyphname2unicode: + return glyphname2unicode[name] + if name.startswith('g'): + raise KeyError(name) + m = STRIP_NAME.search(name) + if not m: + raise KeyError(name) + return chr(int(m.group(0))) + +pdfminer.encodingdb.name2unicode = name2unicode from math import copysign def PDFType3Font__get_height(self): From 600d31a9075406dc04616bf707f09232a2d9de3b Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Tue, 30 Oct 2018 16:22:05 -0700 Subject: [PATCH 30/68] Require pikepdf 0.3.7 --- requirements/main.txt | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements/main.txt b/requirements/main.txt index 63df1da1..8f1fd896 100644 --- a/requirements/main.txt +++ b/requirements/main.txt @@ -4,7 +4,7 @@ cffi == 1.11.5 img2pdf == 0.3.0 pdfminer == 20170720 -pikepdf == 0.3.6 +pikepdf == 0.3.7 Pillow >= 5.0.0, != 5.1.0 ; sys_platform == "darwin" pycparser == 2.18 python-xmp-toolkit == 2.0.1 diff --git a/setup.py b/setup.py index 8fc71313..c2336467 100644 --- a/setup.py +++ b/setup.py @@ -252,7 +252,7 @@ setup( 'cffi >= 1.9.1', # must be a setup and install requirement 'img2pdf >= 0.2.4, < 0.4', # pure Python, so track HEAD closely 'pdfminer.six == 20170720', - 'pikepdf >= 0.3.3, < 0.4', + 'pikepdf >= 0.3.7, < 0.4', 'Pillow >= 4.0.0, != 5.1.0 ; sys_platform == "darwin"', # Pillow < 4 has BytesIO/TIFF bug w/img2pdf 0.2.3 # block 5.1.0, broken wheels From a195713bb4355f847858804652abdcf5cd572e01 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Tue, 30 Oct 2018 16:35:09 -0700 Subject: [PATCH 31/68] Throw exception on corrupt text --- src/ocrmypdf/_pipeline.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/ocrmypdf/_pipeline.py b/src/ocrmypdf/_pipeline.py index 2ba691b1..0d4dc87c 100644 --- a/src/ocrmypdf/_pipeline.py +++ b/src/ocrmypdf/_pipeline.py @@ -258,9 +258,10 @@ def is_ocr_required(pageinfo, log, options): "some text on this page cannot be mapped to characters: " "consider using --force-ocr instead") ) + raise PriorOcrFoundError() # Wrong error but will do for now else: - log.info(msg.format(page, - "redoing OCR")) + log.info(msg.format(page, + "redoing OCR")) ocr_required = True elif options.skip_text: log.info(msg.format(page, From 4ba9e8fe256f6c28c816da0fa4d183760751b44d Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Tue, 30 Oct 2018 22:28:34 -0700 Subject: [PATCH 32/68] Add AcroForm detection --- src/ocrmypdf/_pipeline.py | 19 +++++++++++++++++++ src/ocrmypdf/pdfinfo/__init__.py | 5 +++++ 2 files changed, 24 insertions(+) diff --git a/src/ocrmypdf/_pipeline.py b/src/ocrmypdf/_pipeline.py index 0d4dc87c..1e494b39 100644 --- a/src/ocrmypdf/_pipeline.py +++ b/src/ocrmypdf/_pipeline.py @@ -193,6 +193,25 @@ def repair_and_parse_pdf( "high page count files. Python 3.6 or newer is recommended." ) + if pdfinfo.has_acroform: + if options.redo_ocr: + log.error( + "This PDF has a user fillable form. --redo-ocr is not " + "currently possible on such files." + ) + raise PriorOcrFoundError() + else: + log.warning( + "This PDF has a fillable form. Chances are it is a pure digital " + "document that does not need OCR." + ) + if not options.force_ocr: + log.info( + "Use the option --force-ocr to produce an image of the " + "form and all filled form fields. The output PDF will be " + "'flattened' and will no longer be fillable." + ) + context.set_pdfinfo(pdfinfo) log.debug(pdfinfo) diff --git a/src/ocrmypdf/pdfinfo/__init__.py b/src/ocrmypdf/pdfinfo/__init__.py index 842340bd..b2f23879 100644 --- a/src/ocrmypdf/pdfinfo/__init__.py +++ b/src/ocrmypdf/pdfinfo/__init__.py @@ -803,6 +803,7 @@ class PdfInfo: self._infile = infile self._pages, pdf = _pdf_get_all_pageinfo(infile, log=log) self._needs_rendering = pdf.root.get('/NeedsRendering', False) + self._has_acroform = pdf.root.get('/AcroForm', False) @property def pages(self): @@ -817,6 +818,10 @@ class PdfInfo: def has_userunit(self): return any(page.userunit != 1.0 for page in self.pages) + @property + def has_acroform(self): + return self._has_acroform + @property def filename(self): if not isinstance(self._infile, (str, Path)): From 1364c63b7c89046af4ccb959177639cac68d0dad Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Thu, 1 Nov 2018 20:07:53 -0700 Subject: [PATCH 33/68] Fix failure to pickle file with AcroForm --- src/ocrmypdf/pdfinfo/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ocrmypdf/pdfinfo/__init__.py b/src/ocrmypdf/pdfinfo/__init__.py index b2f23879..f93acb8e 100644 --- a/src/ocrmypdf/pdfinfo/__init__.py +++ b/src/ocrmypdf/pdfinfo/__init__.py @@ -803,7 +803,7 @@ class PdfInfo: self._infile = infile self._pages, pdf = _pdf_get_all_pageinfo(infile, log=log) self._needs_rendering = pdf.root.get('/NeedsRendering', False) - self._has_acroform = pdf.root.get('/AcroForm', False) + self._has_acroform = '/AcroForm' in pdf.root @property def pages(self): From 86816939942444ac7a12041762cb4319bae160e5 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Fri, 2 Nov 2018 00:31:50 -0700 Subject: [PATCH 34/68] Set up code coverage (it works with multiprocessing now!) --- .gitignore | 2 +- tests/conftest.py | 13 +++++++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index d2e03498..610fe588 100644 --- a/.gitignore +++ b/.gitignore @@ -22,7 +22,7 @@ docs/Makefile ocrmypdf/lib/_*.py # Code coverage -.coverage +.coverage* htmlcov/ # Testing diff --git a/tests/conftest.py b/tests/conftest.py index 2b5a90f7..6bcbd4e1 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -18,13 +18,18 @@ import sys import os import platform - -pytest_plugins = ['helpers_namespace'] - -import pytest from pathlib import Path from subprocess import Popen, PIPE +pytest_plugins = ['helpers_namespace'] +import pytest + +try: + from pytest_cov.embed import cleanup_on_sigterm +except ImportError: + pass +else: + cleanup_on_sigterm() # pylint: disable=E1101 # pytest.helpers is dynamic so it confuses pylint From b8214b3c492b1dcaa0268022f2790045314bed43 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Fri, 2 Nov 2018 00:33:08 -0700 Subject: [PATCH 35/68] coverage: exclude unicodefun.py --- src/ocrmypdf/_unicodefun.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ocrmypdf/_unicodefun.py b/src/ocrmypdf/_unicodefun.py index e80d3d21..6834884d 100644 --- a/src/ocrmypdf/_unicodefun.py +++ b/src/ocrmypdf/_unicodefun.py @@ -38,7 +38,7 @@ import sys import codecs -def verify_python3_env(): +def verify_python3_env(): # pragma: no cover """Ensures that the environment is good for unicode on Python 3.""" # PEP 538 changes in Python 3.7 should make this wrangling unnecessary From 288e28328f21099210a1cb92aca7a956b8ebfb91 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Fri, 2 Nov 2018 00:37:33 -0700 Subject: [PATCH 36/68] coverage: add qpdf --- tests/test_qpdf.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 tests/test_qpdf.py diff --git a/tests/test_qpdf.py b/tests/test_qpdf.py new file mode 100644 index 00000000..c6387834 --- /dev/null +++ b/tests/test_qpdf.py @@ -0,0 +1,24 @@ +# © 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 pytest + +import ocrmypdf.exec.qpdf as qpdf + +def test_qpdf_error(resources): + assert qpdf.check(resources / 'blank.pdf') + assert not qpdf.check(__file__) From 2cba62dc4f2358e6cae4b6d58245e1af26845a1e Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Fri, 2 Nov 2018 00:40:56 -0700 Subject: [PATCH 37/68] coverage: ensure rotation is actually tested --- tests/test_rotation.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_rotation.py b/tests/test_rotation.py index a829ef2e..cc541e4d 100644 --- a/tests/test_rotation.py +++ b/tests/test_rotation.py @@ -169,6 +169,8 @@ def test_rotate_deskew_timeout(resources, outdir): check_ocrmypdf( resources / 'rotated_skew.pdf', outdir / 'deskewed.pdf', + '--rotate-pages', + '--rotate-pages-threshold', '0', '--deskew', '--tesseract-timeout', '0', '--pdf-renderer', 'sandwich' From 5b8d197812656fe0c3bfd23e9a63ed95c4915e34 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Fri, 2 Nov 2018 00:41:15 -0700 Subject: [PATCH 38/68] coverage: make it more likely timeout is tested --- 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 785f6d85..fb048427 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -208,7 +208,7 @@ def test_argsfile(spoof_tesseract_noop, resources, outdir): @pytest.mark.parametrize('renderer', RENDERERS) def test_ocr_timeout(renderer, resources, outpdf): out = check_ocrmypdf(resources / 'skew.pdf', outpdf, - '--tesseract-timeout', '0.01', + '--tesseract-timeout', '0', '--pdf-renderer', renderer) pdfinfo = PdfInfo(out) assert not pdfinfo[0].has_text From 64c9ede979059f580a1f02f0dbad84e943e2e4cc Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Wed, 17 Oct 2018 23:46:53 -0700 Subject: [PATCH 39/68] leptonica: barcodes, BOXA --- src/ocrmypdf/leptonica.py | 42 +++++++++++++++++++++++++++ src/ocrmypdf/lib/_leptonica.py | 10 +++---- src/ocrmypdf/lib/compile_leptonica.py | 34 +++++++++++++++++----- 3 files changed, 73 insertions(+), 13 deletions(-) diff --git a/src/ocrmypdf/leptonica.py b/src/ocrmypdf/leptonica.py index 436281d7..6d382795 100644 --- a/src/ocrmypdf/leptonica.py +++ b/src/ocrmypdf/leptonica.py @@ -485,6 +485,16 @@ class Pix: def invert(self): return Pix(lept.pixInvert(ffi.NULL, self._pix)) + def locate_barcodes(self, threshold=20): + p_mask = ffi.new('PIX **') + with _LeptonicaErrorTrap(): + result = lept.pixLocateBarcodes(self._pix, threshold, ffi.NULL, + p_mask) + if result == ffi.NULL: + return None, None + return BoxArray(result), Pix(p_mask[0]) + + @staticmethod def _pix_destroy(pix): p_pix = ffi.new('PIX **', pix) @@ -569,6 +579,38 @@ class Box: lept.boxDestroy(p_box) +class BoxArray: + """Wrapper around Leptonica's BOXA (Array of BOX) objects.""" + + def __init__(self, boxa): + self._boxa = ffi.gc(boxa, BoxArray._boxa_destroy) + + def __repr__(self): + if not self._boxa: + return '' + boxes = (repr(box) for box in self) + return '' + + def __iter__(self): + for n in range(len(self)): + yield self[n] + + def __len__(self): + return self._boxa.n + + def __getitem__(self, n): + if not isinstance(n, int): + raise TypeError('list indices must be integers') + if 0 <= n < len(self): + return Box(lept.boxClone(self._boxa.box[n])) + raise IndexError(n) + + @staticmethod + def _boxa_destroy(boxa): + p_boxa = ffi.new('BOXA **', boxa) + lept.boxaDestroy(p_boxa) + + @lru_cache(maxsize=1) def get_leptonica_version(): """Get Leptonica version string. diff --git a/src/ocrmypdf/lib/_leptonica.py b/src/ocrmypdf/lib/_leptonica.py index b2611d5b..e4bbc59b 100644 --- a/src/ocrmypdf/lib/_leptonica.py +++ b/src/ocrmypdf/lib/_leptonica.py @@ -3,9 +3,9 @@ import _cffi_backend ffi = _cffi_backend.FFI('ocrmypdf.lib._leptonica', _version = 0x2601, - _types = b'\x00\x00\x0F\x0D\x00\x00\xCD\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\xD0\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\xCA\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\xDC\x03\x00\x00\x1C\x01\x00\x00\x00\x0F\x00\x00\xCF\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\xCB\x03\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\xA4\x11\x00\x00\x00\x0F\x00\x00\x02\x0D\x00\x00\x01\x11\x00\x00\xDB\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\x92\x11\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\xD9\x03\x00\x00\xAA\x11\x00\x00\x01\x11\x00\x00\x0D\x01\x00\x00\x00\x0F\x00\x00\xDF\x0D\x00\x00\x10\x11\x00\x00\x00\x0F\x00\x00\xDF\x0D\x00\x00\x92\x11\x00\x00\x00\x0F\x00\x00\xDF\x0D\x00\x00\xA4\x11\x00\x00\x00\x0F\x00\x00\xDF\x0D\x00\x00\xDF\x03\x00\x00\x00\x0F\x00\x00\x00\x09\x00\x00\xCC\x03\x00\x00\x01\x09\x00\x00\x02\x09\x00\x00\x03\x09\x00\x00\xD0\x03\x00\x00\x02\x01\x00\x00\x0E\x01\x00\x00\x00\x0B\x00\x00\x01\x0B\x00\x00\x02\x0B\x00\x00\x0B\x01\x00\x00\x05\x01\x00\x00\x03\x01\x00\x00\xCE\x03\x00\x00\xDA\x03\x00\x00\x04\x01\x00\x00\xDC\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_DEFAULT_ENCODE',0,b'\xFF\xFF\xFF\x0BL_FLATE_ENCODE',3,b'\xFF\xFF\xFF\x0BL_G4_ENCODE',2,b'\xFF\xFF\xFF\x0BL_INSERT',0,b'\xFF\xFF\xFF\x0BL_JP2K_ENCODE',4,b'\xFF\xFF\xFF\x0BL_JPEG_ENCODE',1,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\xBE\x23boxDestroy',0,b'\x00\x00\x67\x23getLeptonicaVersion',0,b'\x00\x00\xC1\x23l_CIDataDestroy',0,b'\x00\x00\xAC\x23l_generateCIDataForPdf',0,b'\x00\x00\xC7\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\x94\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\xC4\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\x8D\x23pixGenerateCIData',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\x9D\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\xA7\x23pixSerializeToMemory',0,b'\x00\x00\xB2\x23pixWriteImpliedFormat',0,b'\x00\x00\xB8\x23pixWriteMemPng',0), - _struct_unions = ((b'\x00\x00\x00\xCA\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\xDC\x11refcount'),(b'\x00\x00\x00\xCC\x00\x00\x00\x02L_Compressed_Data',b'\x00\x00\x02\x11type',b'\x00\x00\xD9\x11datacomp',b'\x00\x00\x65\x11nbytescomp',b'\x00\x00\xCF\x11data85',b'\x00\x00\x65\x11nbytes85',b'\x00\x00\xCF\x11cmapdata85',b'\x00\x00\xCF\x11cmapdatahex',b'\x00\x00\x02\x11ncolors',b'\x00\x00\x02\x11w',b'\x00\x00\x02\x11h',b'\x00\x00\x02\x11bps',b'\x00\x00\x02\x11spp',b'\x00\x00\x02\x11minisblack',b'\x00\x00\x02\x11predictor',b'\x00\x00\x65\x11nbytes',b'\x00\x00\x02\x11res'),(b'\x00\x00\x00\xCD\x00\x00\x00\x02Pix',b'\x00\x00\xDC\x11w',b'\x00\x00\xDC\x11h',b'\x00\x00\xDC\x11d',b'\x00\x00\xDC\x11spp',b'\x00\x00\xDC\x11wpl',b'\x00\x00\xDC\x11refcount',b'\x00\x00\x02\x11xres',b'\x00\x00\x02\x11yres',b'\x00\x00\x02\x11informat',b'\x00\x00\x02\x11special',b'\x00\x00\xCF\x11text',b'\x00\x00\xD8\x11colormap',b'\x00\x00\xDB\x11data'),(b'\x00\x00\x00\xCE\x00\x00\x00\x02PixColormap',b'\x00\x00\xC8\x11array',b'\x00\x00\x02\x11depth',b'\x00\x00\x02\x11nalloc',b'\x00\x00\x02\x11n')), - _enums = (b'\x00\x00\x00\xD2\x00\x00\x00\x16$1\x00L_DEFAULT_ENCODE,L_JPEG_ENCODE,L_G4_ENCODE,L_FLATE_ENCODE,L_JP2K_ENCODE',b'\x00\x00\x00\xD3\x00\x00\x00\x16$2\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\xD4\x00\x00\x00\x16$3\x00L_NOCOPY,L_INSERT,L_COPY,L_CLONE,L_COPY_CLONE'), - _typenames = (b'\x00\x00\x00\xCABOX',b'\x00\x00\x00\xCCL_COMP_DATA',b'\x00\x00\x00\xCDPIX',b'\x00\x00\x00\xCEPIXCMAP',b'\x00\x00\x00\x1Fl_float32',b'\x00\x00\x00\xD1l_float64',b'\x00\x00\x00\xD6l_int16',b'\x00\x00\x00\x02l_int32',b'\x00\x00\x00\xD5l_int64',b'\x00\x00\x00\xD7l_int8',b'\x00\x00\x00\xDEl_uint16',b'\x00\x00\x00\xDCl_uint32',b'\x00\x00\x00\xDDl_uint64',b'\x00\x00\x00\xDAl_uint8'), + _types = b'\x00\x00\x01\x0D\x00\x00\xD6\x03\x00\x00\x00\x0F\x00\x00\x01\x0D\x00\x00\xDB\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\xDE\x03\x00\x00\x00\x0F\x00\x00\xD7\x0D\x00\x00\x04\x11\x00\x00\x07\x01\x00\x00\x04\x03\x00\x00\x10\x11\x00\x00\x00\x0F\x00\x00\x04\x0D\x00\x00\x04\x11\x00\x00\x00\x0F\x00\x00\x04\x0D\x00\x00\x04\x11\x00\x00\x01\x11\x00\x00\x01\x03\x00\x00\x00\x0F\x00\x00\x04\x0D\x00\x00\x04\x11\x00\x00\x04\x11\x00\x00\x00\x0F\x00\x00\x04\x0D\x00\x00\x04\x11\x00\x00\x04\x11\x00\x00\x04\x11\x00\x00\x00\x0F\x00\x00\x04\x0D\x00\x00\x04\x11\x00\x00\x04\x11\x00\x00\x04\x11\x00\x00\x0D\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x04\x0D\x00\x00\x04\x11\x00\x00\x04\x11\x00\x00\x04\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\x04\x0D\x00\x00\x04\x11\x00\x00\x04\x11\x00\x00\x0D\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x04\x0D\x00\x00\x04\x11\x00\x00\x04\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x04\x0D\x00\x00\x04\x11\x00\x00\x04\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\x05\x03\x00\x00\x00\x0F\x00\x00\x04\x0D\x00\x00\x04\x11\x00\x00\x0D\x01\x00\x00\x0D\x01\x00\x00\x00\x0F\x00\x00\x04\x0D\x00\x00\x04\x11\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x04\x0D\x00\x00\x04\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x04\x0D\x00\x00\x04\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x04\x0D\x00\x00\x0B\x11\x00\x00\x00\x0F\x00\x00\x04\x0D\x00\x00\xEA\x03\x00\x00\x1C\x01\x00\x00\x00\x0F\x00\x00\xDD\x0D\x00\x00\x00\x0F\x00\x00\x52\x0D\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x04\x11\x00\x00\x04\x11\x00\x00\x28\x03\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x04\x11\x00\x00\x04\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x77\x11\x00\x00\x77\x11\x00\x00\x77\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x04\x11\x00\x00\x77\x11\x00\x00\x77\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x04\x11\x00\x00\x52\x11\x00\x00\x52\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x04\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x0D\x01\x00\x00\x07\x01\x00\x00\x52\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x04\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\xD9\x03\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x04\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x77\x11\x00\x00\x77\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x04\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x0D\x01\x00\x00\x10\x11\x00\x00\x10\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x04\x11\x00\x00\xE9\x03\x00\x00\x6E\x03\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x0B\x11\x00\x00\x04\x11\x00\x00\x07\x01\x00\x00\x9B\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x0B\x11\x00\x00\x04\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\xE7\x03\x00\x00\xB3\x11\x00\x00\x04\x11\x00\x00\x0D\x01\x00\x00\x00\x0F\x00\x00\xED\x0D\x00\x00\x19\x11\x00\x00\x00\x0F\x00\x00\xED\x0D\x00\x00\xD7\x03\x00\x00\x00\x0F\x00\x00\xED\x0D\x00\x00\x9B\x11\x00\x00\x00\x0F\x00\x00\xED\x0D\x00\x00\x10\x11\x00\x00\x00\x0F\x00\x00\xED\x0D\x00\x00\xED\x03\x00\x00\x00\x0F\x00\x00\x00\x09\x00\x00\xD8\x03\x00\x00\x01\x09\x00\x00\xDA\x03\x00\x00\x02\x09\x00\x00\x03\x09\x00\x00\x04\x09\x00\x00\xDE\x03\x00\x00\x02\x01\x00\x00\x0E\x01\x00\x00\x00\x0B\x00\x00\x01\x0B\x00\x00\x02\x0B\x00\x00\x0B\x01\x00\x00\x05\x01\x00\x00\x03\x01\x00\x00\xDC\x03\x00\x00\xE8\x03\x00\x00\x04\x01\x00\x00\xEA\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_DEFAULT_ENCODE',0,b'\xFF\xFF\xFF\x0BL_FLATE_ENCODE',3,b'\xFF\xFF\xFF\x0BL_G4_ENCODE',2,b'\xFF\xFF\xFF\x0BL_INSERT',0,b'\xFF\xFF\xFF\x0BL_JP2K_ENCODE',4,b'\xFF\xFF\xFF\x0BL_JPEG_ENCODE',1,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\x00\x23boxClone',0,b'\x00\x00\xC7\x23boxDestroy',0,b'\x00\x00\xCA\x23boxaDestroy',0,b'\x00\x00\x70\x23getLeptonicaVersion',0,b'\x00\x00\xCD\x23l_CIDataDestroy',0,b'\x00\x00\xB5\x23l_generateCIDataForPdf',0,b'\x00\x00\xD3\x23lept_free',0,b'\x00\x00\x72\x23makePixelSumTab8',0,b'\x00\x00\x1F\x23pixAnd',0,b'\x00\x00\x2C\x23pixBackgroundNorm',0,b'\x00\x00\x24\x23pixCleanBackgroundToWhite',0,b'\x00\x00\x16\x23pixClipRectangle',0,b'\x00\x00\x9D\x23pixColorFraction',0,b'\x00\x00\x62\x23pixColorMagnitude',0,b'\x00\x00\x13\x23pixConvertRGBToLuminance',0,b'\x00\x00\x74\x23pixCorrelationBinary',0,b'\x00\x00\x89\x23pixCountPixels',0,b'\x00\x00\x6C\x23pixDeserializeFromMemory',0,b'\x00\x00\x59\x23pixDeskew',0,b'\x00\x00\xD0\x23pixDestroy',0,b'\x00\x00\x13\x23pixEndianByteSwapNew',0,b'\x00\x00\x03\x23pixFindPageForeground',0,b'\x00\x00\x84\x23pixFindSkew',0,b'\x00\x00\x38\x23pixGammaTRC',0,b'\x00\x00\x96\x23pixGenerateCIData',0,b'\x00\x00\x79\x23pixGetAverageMaskedRGB',0,b'\x00\x00\x3F\x23pixGlobalNormRGB',0,b'\x00\x00\x1B\x23pixInvert',0,b'\x00\x00\x0D\x23pixLocateBarcodes',0,b'\x00\x00\x5D\x23pixMaskOverColorPixels',0,b'\x00\x00\x8E\x23pixNumSignificantGrayColors',0,b'\x00\x00\xA6\x23pixOtsuAdaptiveThreshold',0,b'\x00\x00\x47\x23pixOtsuThreshOnBackgroundNorm',0,b'\x00\x00\x69\x23pixRead',0,b'\x00\x00\x59\x23pixRemoveColormap',0,b'\x00\x00\x5D\x23pixRemoveColormapGeneral',0,b'\x00\x00\x1B\x23pixRotate180',0,b'\x00\x00\x59\x23pixRotateOrth',0,b'\x00\x00\x54\x23pixScale',0,b'\x00\x00\xB0\x23pixSerializeToMemory',0,b'\x00\x00\xBB\x23pixWriteImpliedFormat',0,b'\x00\x00\xC1\x23pixWriteMemPng',0), + _struct_unions = ((b'\x00\x00\x00\xD6\x00\x00\x00\x02Box',b'\x00\x00\x05\x11x',b'\x00\x00\x05\x11y',b'\x00\x00\x05\x11w',b'\x00\x00\x05\x11h',b'\x00\x00\xEA\x11refcount'),(b'\x00\x00\x00\xD8\x00\x00\x00\x02Boxa',b'\x00\x00\x05\x11n',b'\x00\x00\x05\x11nalloc',b'\x00\x00\xEA\x11refcount',b'\x00\x00\x19\x11box'),(b'\x00\x00\x00\xDA\x00\x00\x00\x02L_Compressed_Data',b'\x00\x00\x05\x11type',b'\x00\x00\xE7\x11datacomp',b'\x00\x00\x6E\x11nbytescomp',b'\x00\x00\xDD\x11data85',b'\x00\x00\x6E\x11nbytes85',b'\x00\x00\xDD\x11cmapdata85',b'\x00\x00\xDD\x11cmapdatahex',b'\x00\x00\x05\x11ncolors',b'\x00\x00\x05\x11w',b'\x00\x00\x05\x11h',b'\x00\x00\x05\x11bps',b'\x00\x00\x05\x11spp',b'\x00\x00\x05\x11minisblack',b'\x00\x00\x05\x11predictor',b'\x00\x00\x6E\x11nbytes',b'\x00\x00\x05\x11res'),(b'\x00\x00\x00\xDB\x00\x00\x00\x02Pix',b'\x00\x00\xEA\x11w',b'\x00\x00\xEA\x11h',b'\x00\x00\xEA\x11d',b'\x00\x00\xEA\x11spp',b'\x00\x00\xEA\x11wpl',b'\x00\x00\xEA\x11refcount',b'\x00\x00\x05\x11xres',b'\x00\x00\x05\x11yres',b'\x00\x00\x05\x11informat',b'\x00\x00\x05\x11special',b'\x00\x00\xDD\x11text',b'\x00\x00\xE6\x11colormap',b'\x00\x00\xE9\x11data'),(b'\x00\x00\x00\xDC\x00\x00\x00\x02PixColormap',b'\x00\x00\xD4\x11array',b'\x00\x00\x05\x11depth',b'\x00\x00\x05\x11nalloc',b'\x00\x00\x05\x11n')), + _enums = (b'\x00\x00\x00\xE0\x00\x00\x00\x16$1\x00L_DEFAULT_ENCODE,L_JPEG_ENCODE,L_G4_ENCODE,L_FLATE_ENCODE,L_JP2K_ENCODE',b'\x00\x00\x00\xE1\x00\x00\x00\x16$2\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\xE2\x00\x00\x00\x16$3\x00L_NOCOPY,L_INSERT,L_COPY,L_CLONE,L_COPY_CLONE'), + _typenames = (b'\x00\x00\x00\xD6BOX',b'\x00\x00\x00\xD8BOXA',b'\x00\x00\x00\xDAL_COMP_DATA',b'\x00\x00\x00\xDBPIX',b'\x00\x00\x00\xDCPIXCMAP',b'\x00\x00\x00\x28l_float32',b'\x00\x00\x00\xDFl_float64',b'\x00\x00\x00\xE4l_int16',b'\x00\x00\x00\x05l_int32',b'\x00\x00\x00\xE3l_int64',b'\x00\x00\x00\xE5l_int8',b'\x00\x00\x00\xECl_uint16',b'\x00\x00\x00\xEAl_uint32',b'\x00\x00\x00\xEBl_uint64',b'\x00\x00\x00\xE8l_uint8'), ) diff --git a/src/ocrmypdf/lib/compile_leptonica.py b/src/ocrmypdf/lib/compile_leptonica.py index 5f739f28..066af029 100644 --- a/src/ocrmypdf/lib/compile_leptonica.py +++ b/src/ocrmypdf/lib/compile_leptonica.py @@ -72,6 +72,15 @@ struct Box }; typedef struct Box BOX; +/*! Array of Box */ +struct Boxa +{ + l_int32 n; /*!< number of box in ptr array */ + l_int32 nalloc; /*!< number of box ptrs allocated */ + l_uint32 refcount; /*!< reference count (1 if no clones) */ + struct Box **box; /*!< box ptr array */ +}; +typedef struct Boxa BOXA; /*! Pdf formatted encoding types */ enum { @@ -265,13 +274,13 @@ pixGetAverageMaskedRGB(PIX *pixs, l_float32 *pgval, l_float32 *pbval); -PIX * +PIX * pixGlobalNormRGB(PIX * pixd, PIX * pixs, l_int32 rval, l_int32 gval, l_int32 bval, - l_int32 mapval); + l_int32 mapval); PIX * pixInvert(PIX * pixd, @@ -289,20 +298,29 @@ pixGenerateCIData(PIX *pixs, l_int32 ascii85, L_COMP_DATA **pcid); -l_int32 -l_generateCIDataForPdf(const char *fname, - PIX *pix, - l_int32 quality, +BOXA * +pixLocateBarcodes ( PIX *pixs, l_int32 thresh, PIX **ppixb, PIX **ppixm ); + +l_int32 +l_generateCIDataForPdf(const char *fname, + PIX *pix, + l_int32 quality, L_COMP_DATA **pcid); -void +BOX * +boxClone ( BOX *box ); + +void boxDestroy(BOX **pbox); +void +boxaDestroy ( BOXA **pboxa ); + void l_CIDataDestroy(L_COMP_DATA **pcid); void -lept_free(void *ptr); +lept_free(void *ptr); """) From 3be02e1e8debdf1aedf5f9cb5ec02f4ccad11b4c Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Fri, 2 Nov 2018 01:10:10 -0700 Subject: [PATCH 40/68] coverage: improve leptonic; don't create objects with null pointers --- src/ocrmypdf/leptonica.py | 9 +++++-- tests/test_lept.py | 52 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 58 insertions(+), 3 deletions(-) diff --git a/src/ocrmypdf/leptonica.py b/src/ocrmypdf/leptonica.py index 6d382795..ab26f750 100644 --- a/src/ocrmypdf/leptonica.py +++ b/src/ocrmypdf/leptonica.py @@ -139,6 +139,8 @@ class Pix: """ def __init__(self, pix): + if not pix: + raise ValueError('NULL box') self._pix = ffi.gc(pix, Pix._pix_destroy) def __repr__(self): @@ -403,8 +405,6 @@ class Pix: display, pdfdir)) - print(repr(cropbox)) - cropped_pix = lept.pixClipRectangle( self._pix, cropbox._box, @@ -549,8 +549,11 @@ class Box: """ def __init__(self, box): + if not box: + raise ValueError('NULL box') self._box = ffi.gc(box, Box._box_destroy) + def __repr__(self): if self._box: return ''.format( @@ -583,6 +586,8 @@ class BoxArray: """Wrapper around Leptonica's BOXA (Array of BOX) objects.""" def __init__(self, boxa): + if not boxa: + raise ValueError('NULL boxa') self._boxa = ffi.gc(boxa, BoxArray._boxa_destroy) def __repr__(self): diff --git a/tests/test_lept.py b/tests/test_lept.py index 9c03a6b8..38efebc2 100644 --- a/tests/test_lept.py +++ b/tests/test_lept.py @@ -18,8 +18,12 @@ import os import shutil -import pytest import sys +from pickle import dumps, loads + +import pytest +from PIL import Image, ImageChops + import ocrmypdf.leptonica as lept @@ -28,3 +32,49 @@ def test_colormap_backgroundnorm(resources): # can handle that case pix = lept.Pix.open(resources / 'baiona_colormapped.png') pix.background_norm() + + +@pytest.fixture +def crom_pix(resources): + pix = lept.Pix.open(resources / 'crom.png') + im = Image.open(resources / 'crom.png') + return pix, im + + +def test_pix_basic(crom_pix): + pix, im = crom_pix + + assert pix.width == im.width + assert pix.height == im.height + assert pix.mode == im.mode + + +def test_pil_conversion(crom_pix): + pix, im = crom_pix + + # Check for pixel perfect + assert ImageChops.difference(pix.topil(), im).getbbox() is None + + +def test_pix_otsu(crom_pix): + pix, _ = crom_pix + im1bpp = pix.otsu_adaptive_threshold() + assert im1bpp.mode == '1' + + +def test_crop(resources): + pix = lept.Pix.open(resources / 'linn.png') + foreground = pix.crop_to_foreground() + assert foreground.width < pix.width + + +def test_clean_bg(crom_pix): + pix, _ = crom_pix + imbg = pix.clean_background_to_white() + + +def test_pickle(crom_pix): + pix, _ = crom_pix + pickled = dumps(pix) + pix2 = loads(pickled) + assert pix.mode == pix2.mode From 77e87abe8f39f0511e28359b3265a6b742420b1b Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Fri, 2 Nov 2018 01:32:20 -0700 Subject: [PATCH 41/68] coverage: ensure get_orientation is checked --- tests/test_rotation.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/tests/test_rotation.py b/tests/test_rotation.py index cc541e4d..ae3751e9 100644 --- a/tests/test_rotation.py +++ b/tests/test_rotation.py @@ -17,6 +17,7 @@ import logging from io import BytesIO +from unittest.mock import Mock from PIL import Image import pytest @@ -25,7 +26,7 @@ import pikepdf from ocrmypdf import leptonica from ocrmypdf.pdfinfo import PdfInfo -from ocrmypdf.exec import ghostscript +from ocrmypdf.exec import ghostscript, tesseract from ocrmypdf.helpers import fspath @@ -228,3 +229,13 @@ def test_rotate_page_level(image_angle, page_angle, resources, outdir): assert p.returncode == 0, err assert check_monochrome_correlation(outdir, reference, 1, out, 1) > 0.2 + + +def test_tesseract_orientation(resources, tmpdir): + pix = leptonica.Pix.open(resources / 'crom.png') + pix_rotated = pix.rotate_orth(2) # 180 degrees clockwise + pix_rotated.write_implied_format(tmpdir / '000001.png') + + log = Mock() + tesseract.get_orientation( # Test results of this are unreliable + tmpdir / '000001.png', engine_mode='3', timeout=10, log=log) From 8b9ab25125d0b38152e03d2104b3a1c53d9a13b3 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Fri, 2 Nov 2018 01:55:25 -0700 Subject: [PATCH 42/68] coverage: test compile leptonica --- src/ocrmypdf/lib/compile_leptonica.py | 3 ++- tests/test_lept.py | 9 +++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/ocrmypdf/lib/compile_leptonica.py b/src/ocrmypdf/lib/compile_leptonica.py index 066af029..963e45db 100644 --- a/src/ocrmypdf/lib/compile_leptonica.py +++ b/src/ocrmypdf/lib/compile_leptonica.py @@ -19,7 +19,6 @@ from cffi import FFI ffibuilder = FFI() -ffibuilder.set_source("ocrmypdf.lib._leptonica", None) ffibuilder.cdef(""" typedef signed char l_int8; typedef unsigned char l_uint8; @@ -324,5 +323,7 @@ lept_free(void *ptr); """) +ffibuilder.set_source("ocrmypdf.lib._leptonica", None) + if __name__ == '__main__': ffibuilder.compile(verbose=True) diff --git a/tests/test_lept.py b/tests/test_lept.py index 38efebc2..67d47bc5 100644 --- a/tests/test_lept.py +++ b/tests/test_lept.py @@ -78,3 +78,12 @@ def test_pickle(crom_pix): pickled = dumps(pix) pix2 = loads(pickled) assert pix.mode == pix2.mode + + +def test_leptonica_compile(tmpdir): + from ocrmypdf.lib.compile_leptonica import ffibuilder + + # Compile the library but build it somewhere that won't interfere with + # existing compiled library. Also compile in API mode so that we test + # the interfaces, even though we use it ABI mode. + ffibuilder.compile(tmpdir=tmpdir, target=(tmpdir /'lepttest.*')) From c023cae299ddb5bfd5c8bfa32ab7a3c750105ebe Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Sun, 4 Nov 2018 01:53:53 -0700 Subject: [PATCH 43/68] Make pdfminer Type3 patch conditional on PScript5.dll It appears that PDFs created by this software have a bug in their BBox which will cause us to misjudge the space occupied by the font. Other programs probably work around this by ignoring BBox and reading each character procedure. --- src/ocrmypdf/pdfinfo/__init__.py | 5 +- src/ocrmypdf/pdfinfo/layout.py | 85 ++++++++++++++++++-------------- 2 files changed, 50 insertions(+), 40 deletions(-) diff --git a/src/ocrmypdf/pdfinfo/__init__.py b/src/ocrmypdf/pdfinfo/__init__.py index f93acb8e..8a513a9a 100644 --- a/src/ocrmypdf/pdfinfo/__init__.py +++ b/src/ocrmypdf/pdfinfo/__init__.py @@ -615,9 +615,8 @@ def _pdf_get_pageinfo(pdf, pageno: int, infile, xmltext): # pageinfo['textinfo'] = _page_get_textblocks( # fspath(infile), pageno, xmltext=xmltext) - - with Path(infile).open('rb') as f: - miner = get_page_analysis(f, pageno) + pscript5_mode = str(pdf.metadata.get('/Creator')).startswith('PScript5') + miner = get_page_analysis(infile, pageno, pscript5_mode) pageinfo['textboxes'] = list(simplify_textboxes(miner)) mediabox = [Decimal(d) for d in page.MediaBox.as_list()] diff --git a/src/ocrmypdf/pdfinfo/layout.py b/src/ocrmypdf/pdfinfo/layout.py index b8a1bc59..9aef17b1 100644 --- a/src/ocrmypdf/pdfinfo/layout.py +++ b/src/ocrmypdf/pdfinfo/layout.py @@ -16,28 +16,31 @@ # along with OCRmyPDF. If not, see . import re +from math import copysign +from pathlib import Path +from unittest.mock import patch import pdfminer.encodingdb -import pdfminer.pdfinterp import pdfminer.pdfdevice - +import pdfminer.pdfinterp from pdfminer.converter import PDFLayoutAnalyzer -from pdfminer.pdfdocument import PDFTextExtractionNotAllowed from pdfminer.glyphlist import glyphname2unicode -from pdfminer.layout import ( - LTChar, LTContainer, LTLayoutContainer, LTPage, LTTextLine, LAParams, - LTTextBox -) -from pdfminer.pdffont import PDFUnicodeNotDefined, PDFType3Font, PDFFont, PDFCIDFont +from pdfminer.layout import (LAParams, LTChar, LTContainer, LTLayoutContainer, + LTPage, LTTextBox, LTTextLine) +from pdfminer.pdfdocument import PDFTextExtractionNotAllowed +from pdfminer.pdffont import (PDFCIDFont, PDFFont, PDFType3Font, + PDFUnicodeNotDefined) from pdfminer.pdfpage import PDFPage -from pdfminer.utils import matrix2str, bbox2str, fsplit +from pdfminer.utils import bbox2str, fsplit, matrix2str from ..exceptions import EncryptedPdfError - - STRIP_NAME = re.compile(r'[0-9]+') +# +# Unconditional pdfminer patches +# + def name2unicode(name): """Fix pdfminer's regex in name2unicode function @@ -54,27 +57,8 @@ def name2unicode(name): if not m: raise KeyError(name) return chr(int(m.group(0))) - pdfminer.encodingdb.name2unicode = name2unicode -from math import copysign -def PDFType3Font__get_height(self): - h = self.bbox[3]-self.bbox[1] - if h == 0: - h = self.ascent - self.descent - return h * copysign(1.0, self.vscale) - -def PDFType3Font__get_descent(self): - return self.descent * copysign(1.0, self.vscale) - -def PDFType3Font__get_ascent(self): - return self.ascent * copysign(1.0, self.vscale) - -PDFType3Font.get_height = PDFType3Font__get_height -PDFType3Font.get_ascent = PDFType3Font__get_ascent -PDFType3Font.get_descent = PDFType3Font__get_descent - - original_PDFFont_init = PDFFont.__init__ def PDFFont__init__(self, descriptor, widths, default_width=None): original_PDFFont_init(self, descriptor, widths, default_width) @@ -85,9 +69,23 @@ def PDFFont__init__(self, descriptor, widths, default_width=None): # to misposition text. if self.descent > 0: self.descent = -self.descent - PDFFont.__init__ = PDFFont__init__ +# +# pdfminer patches when creator is PScript5.dll +# + +def PDFType3Font__PScript5_get_height(self): + h = self.bbox[3]-self.bbox[1] + if h == 0: + h = self.ascent - self.descent + return h * copysign(1.0, self.vscale) + +def PDFType3Font__PScript5_get_descent(self): + return self.descent * copysign(1.0, self.vscale) + +def PDFType3Font__PScript5_get_ascent(self): + return self.ascent * copysign(1.0, self.vscale) class LTStateAwareChar(LTChar): @@ -183,17 +181,30 @@ class TextPositionTracker(PDFLayoutAnalyzer): return self.result -def get_page_analysis(infile, pageno): +def get_page_analysis(infile, pageno, pscript5_mode): rman = pdfminer.pdfinterp.PDFResourceManager(caching=True) dev = TextPositionTracker(rman, laparams=LAParams()) interp = pdfminer.pdfinterp.PDFPageInterpreter(rman, dev) - page = PDFPage.get_pages(infile, pagenos=[pageno], maxpages=0) + if pscript5_mode: + patcher = patch.multiple( + 'pdfminer.pdffont.PDFType3Font', + spec=True, + get_ascent=PDFType3Font__PScript5_get_ascent, + get_descent=PDFType3Font__PScript5_get_descent, + get_height=PDFType3Font__PScript5_get_height + ) + patcher.start() - try: - interp.process_page(next(page)) - except PDFTextExtractionNotAllowed as e: - raise EncryptedPdfError() + with Path(infile).open('rb') as f: + page = PDFPage.get_pages(f, pagenos=[pageno], maxpages=0) + try: + interp.process_page(next(page)) + except PDFTextExtractionNotAllowed as e: + raise EncryptedPdfError() + finally: + if pscript5_mode: + patcher.stop() return dev.get_result() From 995fc58466535ffe26b9e833f3f4c0893e91f4b6 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Sun, 4 Nov 2018 14:55:48 -0800 Subject: [PATCH 44/68] Move Ghostscript text analysis into its own module --- src/ocrmypdf/pdfinfo/__init__.py | 75 +----------------------- src/ocrmypdf/pdfinfo/ghosttext.py | 94 +++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+), 72 deletions(-) create mode 100644 src/ocrmypdf/pdfinfo/ghosttext.py diff --git a/src/ocrmypdf/pdfinfo/__init__.py b/src/ocrmypdf/pdfinfo/__init__.py index 8a513a9a..7a6972a3 100644 --- a/src/ocrmypdf/pdfinfo/__init__.py +++ b/src/ocrmypdf/pdfinfo/__init__.py @@ -23,13 +23,13 @@ from math import hypot, isclose from pathlib import Path from unittest.mock import Mock import re -import xml.etree.ElementTree as ET from pikepdf import PdfMatrix import pikepdf +from .ghosttext import extract_text_xml from .layout import get_page_analysis, get_text_boxes -from ..exec import ghostscript + from ..helpers import fspath @@ -40,17 +40,6 @@ Encoding = Enum('Encoding', 'ccitt jpeg jpeg2000 jbig2 asciihex ascii85 lzw flate ' + \ 'runlength') -# Forgive me for I have sinned -# I am using regular expressions to parse XML. However the XML in this case, -# generated by Ghostscript, is self-consistent enough to be parseable. -regex_remove_char_tags = re.compile(br""" - ] # anything single character but > - | \">\" # special case: trap ">" - )* - /> # terminate with '/>' -""", re.VERBOSE) - FRIENDLY_COLORSPACE = { '/DeviceGray': Colorspace.gray, '/CalGray': Colorspace.gray, @@ -526,43 +515,6 @@ def _process_content_streams(*, pdf, container, shorthand=None): yield from _find_form_xobject_images(pdf, container, contentsinfo) -def _page_get_textblocks(infile, pageno, xmltext): - """Smarter text detection""" - - root = xmltext - if not hasattr(xmltext, 'findall'): - return [] - - def blocks(): - for span in root.findall('.//span'): - bbox_str = span.attrib['bbox'] - font_size = span.attrib['size'] - pts = [int(pt) for pt in bbox_str.split()] - pts[1] = pts[1] - int(float(font_size) + 0.5) - bbox = tuple(pts) - yield bbox - - def joined_blocks(): - prev = None - for bbox in blocks(): - if prev is None: - prev = bbox - if bbox[1] == prev[1] and bbox[3] == prev[3]: - gap = prev[2] - bbox[0] - height = bbox[3] - bbox[1] - if gap < height: - # Join boxes - prev = (prev[0], prev[1], bbox[2], bbox[3]) - continue - # yield previously joined bboxes and start anew - yield prev - prev = bbox - if prev is not None: - yield prev - - return [block for block in joined_blocks()] - - def _page_has_text(text_blocks, page_width, page_height): """Smarter text detection that ignores text in margins""" @@ -668,28 +620,7 @@ def _pdf_get_all_pageinfo(infile, log=None): log = Mock() pdf = pikepdf.open(infile) - - existing_text = ghostscript.extract_text(infile, pageno=None) - existing_text = regex_remove_char_tags.sub(b' ', existing_text) - - try: - root = ET.fromstringlist([ - b'\n', existing_text, b'\n' - ]) - page_xml = root.findall('page') - except ET.ParseError as e: - log.error( - "An error occurred while attempting to retrieve existing text in " - "the input file. Will attempt to continue assuming that there is " - "no existing text in the file. The error was:") - log.error(e) - page_xml = [None] * len(pdf.pages) - - page_count_difference = len(pdf.pages) - len(page_xml) - if page_count_difference != 0: - log.error("The number of pages in the input file is inconsistent.") - if page_count_difference > 0: - page_xml.extend([None] * page_count_difference) + page_xml = extract_text_xml(infile, pdf, pageno=None, log=log) pages = [] for n in range(len(pdf.pages)): diff --git a/src/ocrmypdf/pdfinfo/ghosttext.py b/src/ocrmypdf/pdfinfo/ghosttext.py new file mode 100644 index 00000000..1545afb8 --- /dev/null +++ b/src/ocrmypdf/pdfinfo/ghosttext.py @@ -0,0 +1,94 @@ +# © 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 re +import xml.etree.ElementTree as ET + +from ..exec import ghostscript + +# Forgive me for I have sinned +# I am using regular expressions to parse XML. However the XML in this case, +# generated by Ghostscript, is self-consistent enough to be parseable. +regex_remove_char_tags = re.compile(br""" + ] # anything single character but > + | \">\" # special case: trap ">" + )* + /> # terminate with '/>' +""", re.VERBOSE) + + +def _page_get_textblocks(infile, pageno, xmltext): + """Smarter text detection""" + + root = xmltext + if not hasattr(xmltext, 'findall'): + return [] + + def blocks(): + for span in root.findall('.//span'): + bbox_str = span.attrib['bbox'] + font_size = span.attrib['size'] + pts = [int(pt) for pt in bbox_str.split()] + pts[1] = pts[1] - int(float(font_size) + 0.5) + bbox = tuple(pts) + yield bbox + + def joined_blocks(): + prev = None + for bbox in blocks(): + if prev is None: + prev = bbox + if bbox[1] == prev[1] and bbox[3] == prev[3]: + gap = prev[2] - bbox[0] + height = bbox[3] - bbox[1] + if gap < height: + # Join boxes + prev = (prev[0], prev[1], bbox[2], bbox[3]) + continue + # yield previously joined bboxes and start anew + yield prev + prev = bbox + if prev is not None: + yield prev + + return [block for block in joined_blocks()] + + +def extract_text_xml(infile, pdf, pageno=None, log=None): + existing_text = ghostscript.extract_text(infile, pageno=None) + existing_text = regex_remove_char_tags.sub(b' ', existing_text) + + try: + root = ET.fromstringlist([ + b'\n', existing_text, b'\n' + ]) + page_xml = root.findall('page') + except ET.ParseError as e: + log.error( + "An error occurred while attempting to retrieve existing text in " + "the input file. Will attempt to continue assuming that there is " + "no existing text in the file. The error was:") + log.error(e) + page_xml = [None] * len(pdf.pages) + + page_count_difference = len(pdf.pages) - len(page_xml) + if page_count_difference != 0: + log.error("The number of pages in the input file is inconsistent.") + if page_count_difference > 0: + page_xml.extend([None] * page_count_difference) + return page_xml From b96532caa4f95990d82df091929a39bd4da9c3d6 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Sun, 4 Nov 2018 15:40:49 -0800 Subject: [PATCH 45/68] Only do detailed page analysis when needed by --redo-ocr --- src/ocrmypdf/_pipeline.py | 4 +++ src/ocrmypdf/pdfinfo/__init__.py | 42 ++++++++++++++++++++----------- src/ocrmypdf/pdfinfo/ghosttext.py | 12 +++++---- 3 files changed, 39 insertions(+), 19 deletions(-) diff --git a/src/ocrmypdf/_pipeline.py b/src/ocrmypdf/_pipeline.py index 1e494b39..8240ba90 100644 --- a/src/ocrmypdf/_pipeline.py +++ b/src/ocrmypdf/_pipeline.py @@ -162,6 +162,10 @@ def repair_and_parse_pdf( options = context.get_options() copyfile(input_file, output_file) + detailed_page_analysis = False + if options.redo_ocr: + detailed_page_analysis = True + try: pdfinfo = PdfInfo(output_file, log=log) except pikepdf.PasswordError as e: diff --git a/src/ocrmypdf/pdfinfo/__init__.py b/src/ocrmypdf/pdfinfo/__init__.py index 7a6972a3..851d7af4 100644 --- a/src/ocrmypdf/pdfinfo/__init__.py +++ b/src/ocrmypdf/pdfinfo/__init__.py @@ -27,7 +27,7 @@ import re from pikepdf import PdfMatrix import pikepdf -from .ghosttext import extract_text_xml +from . import ghosttext from .layout import get_page_analysis, get_text_boxes from ..helpers import fspath @@ -564,18 +564,20 @@ def _pdf_get_pageinfo(pdf, pageno: int, infile, xmltext): pageinfo['images'] = [] page = pdf.pages[pageno] - - # pageinfo['textinfo'] = _page_get_textblocks( - # fspath(infile), pageno, xmltext=xmltext) - pscript5_mode = str(pdf.metadata.get('/Creator')).startswith('PScript5') - miner = get_page_analysis(infile, pageno, pscript5_mode) - pageinfo['textboxes'] = list(simplify_textboxes(miner)) - mediabox = [Decimal(d) for d in page.MediaBox.as_list()] width_pt = mediabox[2] - mediabox[0] height_pt = mediabox[3] - mediabox[1] - bboxes = (box.bbox for box in pageinfo['textboxes']) + if xmltext: + bboxes = ghosttext.page_get_textblocks( + fspath(infile), pageno, xmltext=xmltext, height=height_pt) + pageinfo['bboxes'] = bboxes + else: + pscript5_mode = str(pdf.metadata.get('/Creator')).startswith('PScript5') + miner = get_page_analysis(infile, pageno, pscript5_mode) + pageinfo['textboxes'] = list(simplify_textboxes(miner)) + bboxes = (box.bbox for box in pageinfo['textboxes']) + pageinfo['has_text'] = _page_has_text( bboxes, width_pt, height_pt ) @@ -615,16 +617,20 @@ def _pdf_get_pageinfo(pdf, pageno: int, infile, xmltext): return pageinfo -def _pdf_get_all_pageinfo(infile, log=None): +def _pdf_get_all_pageinfo(infile, detailed_page_analysis, log=None): if not log: log = Mock() pdf = pikepdf.open(infile) - page_xml = extract_text_xml(infile, pdf, pageno=None, log=log) + if not detailed_page_analysis: + pages_xml = None + else: + pages_xml = ghosttext.extract_text_xml(infile, pdf, pageno=None, log=log) pages = [] for n in range(len(pdf.pages)): - page = PageInfo(pdf, n, infile, page_xml[n]) + page_xml = pages_xml[n] if pages_xml else None + page = PageInfo(pdf, n, infile, page_xml) pages.append(page) return pages, pdf @@ -694,9 +700,16 @@ class PageInfo: result = False return result + if 'textboxes' not in self._pageinfo: + if visible is not None and corrupt is not None: + raise NotImplementedError( + 'Ghostscript textboxes cannot be classified') + return self._pageinfo['bboxes'] + return (obj.bbox for obj in self._pageinfo['textboxes'] if predicate(obj, visible, corrupt)) + @property def xres(self): return self._pageinfo.get('xres', None) @@ -729,9 +742,10 @@ class PageInfo: class PdfInfo: """Get summary information about a PDF""" - def __init__(self, infile, log=None): + def __init__(self, infile, detailed_page_analysis=False, log=None): self._infile = infile - self._pages, pdf = _pdf_get_all_pageinfo(infile, log=log) + self._pages, pdf = _pdf_get_all_pageinfo( + infile, detailed_page_analysis, log=log) self._needs_rendering = pdf.root.get('/NeedsRendering', False) self._has_acroform = '/AcroForm' in pdf.root diff --git a/src/ocrmypdf/pdfinfo/ghosttext.py b/src/ocrmypdf/pdfinfo/ghosttext.py index 1545afb8..4b9e13a0 100644 --- a/src/ocrmypdf/pdfinfo/ghosttext.py +++ b/src/ocrmypdf/pdfinfo/ghosttext.py @@ -32,8 +32,8 @@ regex_remove_char_tags = re.compile(br""" """, re.VERBOSE) -def _page_get_textblocks(infile, pageno, xmltext): - """Smarter text detection""" +def page_get_textblocks(infile, pageno, xmltext, height): + """Get text boxes out of Ghostscript txtwrite xml""" root = xmltext if not hasattr(xmltext, 'findall'): @@ -45,8 +45,10 @@ def _page_get_textblocks(infile, pageno, xmltext): font_size = span.attrib['size'] pts = [int(pt) for pt in bbox_str.split()] pts[1] = pts[1] - int(float(font_size) + 0.5) - bbox = tuple(pts) - yield bbox + bbox_topdown = tuple(pts) + bb = bbox_topdown + bbox_bottomup = (bb[0], height - bb[3], bb[2], height - bb[1]) + yield bbox_bottomup def joined_blocks(): prev = None @@ -55,7 +57,7 @@ def _page_get_textblocks(infile, pageno, xmltext): prev = bbox if bbox[1] == prev[1] and bbox[3] == prev[3]: gap = prev[2] - bbox[0] - height = bbox[3] - bbox[1] + height = abs(bbox[3] - bbox[1]) if gap < height: # Join boxes prev = (prev[0], prev[1], bbox[2], bbox[3]) From 2125b5bfab7913b1a765bc12b5c911ed124a8122 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Sun, 4 Nov 2018 15:47:55 -0800 Subject: [PATCH 46/68] Remove text detection from our parser interpret_contents It's redundant now --- src/ocrmypdf/pdfinfo/__init__.py | 35 +++----------------------------- 1 file changed, 3 insertions(+), 32 deletions(-) diff --git a/src/ocrmypdf/pdfinfo/__init__.py b/src/ocrmypdf/pdfinfo/__init__.py index 851d7af4..f77418e6 100644 --- a/src/ocrmypdf/pdfinfo/__init__.py +++ b/src/ocrmypdf/pdfinfo/__init__.py @@ -95,7 +95,7 @@ InlineSettings = namedtuple('InlineSettings', ['iimage', 'shorthand', 'stack_depth']) ContentsInfo = namedtuple('ContentsInfo', - ['xobject_settings', 'inline_images', 'found_text', 'found_vector']) + ['xobject_settings', 'inline_images', 'found_vector']) TextboxInfo = namedtuple('TextboxInfo', ['bbox', 'is_visible', 'is_corrupt']) @@ -106,15 +106,6 @@ class VectorInfo: pass -class TextInfo: - def __init__(self, invisible, visible): - self.invisible = invisible - self.visible = visible - - def __bool__(self): - return self.invisible or self.visible - - def _normalize_stack(graphobjs): """Convert runs of qQ's in the stack into single graphobjs""" for operands, operator in graphobjs: @@ -150,15 +141,10 @@ def _interpret_contents(contentstream, initial_shorthand=UNIT_SQUARE): ctm = PdfMatrix(initial_shorthand) xobject_settings = [] inline_images = [] - found_text, found_vector = False, False - found_invisible_text, found_visible_text = False, False - text_mode_ops = set("""BT ET Tr""".split()) - text_showing_ops = set("""Tj " ' TJ""".split()) + found_vector = False vector_ops = set('S s f F f* B B* b b*'.split()) image_ops = set('BI ID EI q Q Do cm'.split()) - text_render_mode = 0 - operator_whitelist = ' '.join( - text_mode_ops | text_showing_ops | vector_ops | image_ops) + operator_whitelist = ' '.join(vector_ops | image_ops) for n, graphobj in enumerate(_normalize_stack( pikepdf.parse_content_stream(contentstream, operator_whitelist))): @@ -189,25 +175,12 @@ def _interpret_contents(contentstream, initial_shorthand=UNIT_SQUARE): iimage=iimage, shorthand=ctm.shorthand, stack_depth=len(stack)) inline_images.append(inline) - elif operator in text_mode_ops: - if operator == 'BT': - text_render_mode = 0 - elif operator == 'Tr': - text_render_mode = operands[0] - elif operator in text_showing_ops: - found_text = True - if text_render_mode == 3: - found_invisible_text = True - else: - found_visible_text = True elif operator in vector_ops: found_vector = True return ContentsInfo( xobject_settings=xobject_settings, inline_images=inline_images, - found_text=TextInfo(invisible=found_invisible_text, - visible=found_visible_text), found_vector=found_vector) @@ -508,8 +481,6 @@ def _process_content_streams(*, pdf, container, shorthand=None): if contentsinfo.found_vector: yield VectorInfo() - if contentsinfo.found_text: - yield contentsinfo.found_text yield from _find_inline_images(contentsinfo) yield from _find_regular_images(container, contentsinfo) yield from _find_form_xobject_images(pdf, container, contentsinfo) From 2ac028c7590611cd1af09f51668a13b42838b0dd Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Sun, 4 Nov 2018 15:54:41 -0800 Subject: [PATCH 47/68] test: Add a basic redo OCR test --- tests/test_main.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/tests/test_main.py b/tests/test_main.py index fb048427..2492648c 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -190,11 +190,22 @@ def test_force_ocr(spoof_tesseract_cache, resources, outpdf): def test_skip_ocr(spoof_tesseract_cache, resources, outpdf): out = check_ocrmypdf(resources / 'graph_ocred.pdf', outpdf, '-s', - env=spoof_tesseract_cache) + env=spoof_tesseract_cache) pdfinfo = PdfInfo(out) assert pdfinfo[0].has_text +def test_redo_ocr(spoof_tesseract_cache, resources, outpdf): + in_ = resources / 'graph_ocred.pdf' + before = PdfInfo(in_, detailed_page_analysis=True) + out = check_ocrmypdf(in_, outpdf, '--redo-ocr', + env=spoof_tesseract_cache) + after = PdfInfo(out, detailed_page_analysis=True) + assert before[0].has_text and after[0].has_text + assert before[0].get_textareas() != after[0].get_textareas(), \ + "Expected text to be different after re-OCR" + + def test_argsfile(spoof_tesseract_noop, resources, outdir): path_argsfile = outdir / 'test_argsfile.txt' with open(str(path_argsfile), 'w') as argsfile: From 590942ad14c91df4a1455496bfbf8fadb0541bf2 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Mon, 5 Nov 2018 01:48:38 -0800 Subject: [PATCH 48/68] Leptonica: Add barcode API --- src/ocrmypdf/leptonica.py | 43 ++++++++++++---- src/ocrmypdf/lib/_leptonica.py | 10 ++-- src/ocrmypdf/lib/compile_leptonica.py | 71 +++++++++++++++++++++++++++ 3 files changed, 109 insertions(+), 15 deletions(-) diff --git a/src/ocrmypdf/leptonica.py b/src/ocrmypdf/leptonica.py index ab26f750..a3a31c52 100644 --- a/src/ocrmypdf/leptonica.py +++ b/src/ocrmypdf/leptonica.py @@ -140,7 +140,7 @@ class Pix: def __init__(self, pix): if not pix: - raise ValueError('NULL box') + raise ValueError('NULL pix') self._pix = ffi.gc(pix, Pix._pix_destroy) def __repr__(self): @@ -485,14 +485,24 @@ class Pix: def invert(self): return Pix(lept.pixInvert(ffi.NULL, self._pix)) - def locate_barcodes(self, threshold=20): - p_mask = ffi.new('PIX **') - with _LeptonicaErrorTrap(): - result = lept.pixLocateBarcodes(self._pix, threshold, ffi.NULL, - p_mask) - if result == ffi.NULL: - return None, None - return BoxArray(result), Pix(p_mask[0]) + def locate_barcodes(self): + pixa_candidates = lept.pixExtractBarcodes(self._pix, 0) + if not pixa_candidates: + return + sarray = lept.pixReadBarcodes(pixa_candidates, + lept.L_BF_ANY, + lept.L_USE_WIDTHS, + ffi.NULL, + 0) + if not sarray: + return + for n in range(sarray[0].n): + decoded = ffi.string(sarray[0].array[n]).decode() + if decoded.strip() == '': + continue + box = pixa_candidates[0].boxa[0].box[n][0] + left, top, right, bottom = box.x, box.y, box.x + box.w, box.y + box.h + yield (decoded, (left, top, right, bottom)) @staticmethod @@ -542,6 +552,19 @@ class CompressedData: lept.l_CIDataDestroy(pp) +class PixArray: + + def __init__(self, pixa): + if not pixa: + raise ValueError('NULL pixa') + self._pixa = ffi.gc(pixa, PixArray._pixa_destroy) + + @staticmethod + def _pixa_destroy(pixa): + pp = ffi.new('PIXA **', pixa) + lept.pixaDestroy(pp) + + class Box: """Wrapper around Leptonica's BOX objects. @@ -607,7 +630,7 @@ class BoxArray: if not isinstance(n, int): raise TypeError('list indices must be integers') if 0 <= n < len(self): - return Box(lept.boxClone(self._boxa.box[n])) + return Box(lept.boxaGetBox(self._boxa, n, lept.L_CLONE)) raise IndexError(n) @staticmethod diff --git a/src/ocrmypdf/lib/_leptonica.py b/src/ocrmypdf/lib/_leptonica.py index e4bbc59b..a606c77f 100644 --- a/src/ocrmypdf/lib/_leptonica.py +++ b/src/ocrmypdf/lib/_leptonica.py @@ -3,9 +3,9 @@ import _cffi_backend ffi = _cffi_backend.FFI('ocrmypdf.lib._leptonica', _version = 0x2601, - _types = b'\x00\x00\x01\x0D\x00\x00\xD6\x03\x00\x00\x00\x0F\x00\x00\x01\x0D\x00\x00\xDB\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\xDE\x03\x00\x00\x00\x0F\x00\x00\xD7\x0D\x00\x00\x04\x11\x00\x00\x07\x01\x00\x00\x04\x03\x00\x00\x10\x11\x00\x00\x00\x0F\x00\x00\x04\x0D\x00\x00\x04\x11\x00\x00\x00\x0F\x00\x00\x04\x0D\x00\x00\x04\x11\x00\x00\x01\x11\x00\x00\x01\x03\x00\x00\x00\x0F\x00\x00\x04\x0D\x00\x00\x04\x11\x00\x00\x04\x11\x00\x00\x00\x0F\x00\x00\x04\x0D\x00\x00\x04\x11\x00\x00\x04\x11\x00\x00\x04\x11\x00\x00\x00\x0F\x00\x00\x04\x0D\x00\x00\x04\x11\x00\x00\x04\x11\x00\x00\x04\x11\x00\x00\x0D\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x04\x0D\x00\x00\x04\x11\x00\x00\x04\x11\x00\x00\x04\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\x04\x0D\x00\x00\x04\x11\x00\x00\x04\x11\x00\x00\x0D\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x04\x0D\x00\x00\x04\x11\x00\x00\x04\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x04\x0D\x00\x00\x04\x11\x00\x00\x04\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\x05\x03\x00\x00\x00\x0F\x00\x00\x04\x0D\x00\x00\x04\x11\x00\x00\x0D\x01\x00\x00\x0D\x01\x00\x00\x00\x0F\x00\x00\x04\x0D\x00\x00\x04\x11\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x04\x0D\x00\x00\x04\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x04\x0D\x00\x00\x04\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x04\x0D\x00\x00\x0B\x11\x00\x00\x00\x0F\x00\x00\x04\x0D\x00\x00\xEA\x03\x00\x00\x1C\x01\x00\x00\x00\x0F\x00\x00\xDD\x0D\x00\x00\x00\x0F\x00\x00\x52\x0D\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x04\x11\x00\x00\x04\x11\x00\x00\x28\x03\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x04\x11\x00\x00\x04\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x77\x11\x00\x00\x77\x11\x00\x00\x77\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x04\x11\x00\x00\x77\x11\x00\x00\x77\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x04\x11\x00\x00\x52\x11\x00\x00\x52\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x04\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x0D\x01\x00\x00\x07\x01\x00\x00\x52\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x04\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\xD9\x03\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x04\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x77\x11\x00\x00\x77\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x04\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x0D\x01\x00\x00\x10\x11\x00\x00\x10\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x04\x11\x00\x00\xE9\x03\x00\x00\x6E\x03\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x0B\x11\x00\x00\x04\x11\x00\x00\x07\x01\x00\x00\x9B\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x0B\x11\x00\x00\x04\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\xE7\x03\x00\x00\xB3\x11\x00\x00\x04\x11\x00\x00\x0D\x01\x00\x00\x00\x0F\x00\x00\xED\x0D\x00\x00\x19\x11\x00\x00\x00\x0F\x00\x00\xED\x0D\x00\x00\xD7\x03\x00\x00\x00\x0F\x00\x00\xED\x0D\x00\x00\x9B\x11\x00\x00\x00\x0F\x00\x00\xED\x0D\x00\x00\x10\x11\x00\x00\x00\x0F\x00\x00\xED\x0D\x00\x00\xED\x03\x00\x00\x00\x0F\x00\x00\x00\x09\x00\x00\xD8\x03\x00\x00\x01\x09\x00\x00\xDA\x03\x00\x00\x02\x09\x00\x00\x03\x09\x00\x00\x04\x09\x00\x00\xDE\x03\x00\x00\x02\x01\x00\x00\x0E\x01\x00\x00\x00\x0B\x00\x00\x01\x0B\x00\x00\x02\x0B\x00\x00\x0B\x01\x00\x00\x05\x01\x00\x00\x03\x01\x00\x00\xDC\x03\x00\x00\xE8\x03\x00\x00\x04\x01\x00\x00\xEA\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_DEFAULT_ENCODE',0,b'\xFF\xFF\xFF\x0BL_FLATE_ENCODE',3,b'\xFF\xFF\xFF\x0BL_G4_ENCODE',2,b'\xFF\xFF\xFF\x0BL_INSERT',0,b'\xFF\xFF\xFF\x0BL_JP2K_ENCODE',4,b'\xFF\xFF\xFF\x0BL_JPEG_ENCODE',1,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\x00\x23boxClone',0,b'\x00\x00\xC7\x23boxDestroy',0,b'\x00\x00\xCA\x23boxaDestroy',0,b'\x00\x00\x70\x23getLeptonicaVersion',0,b'\x00\x00\xCD\x23l_CIDataDestroy',0,b'\x00\x00\xB5\x23l_generateCIDataForPdf',0,b'\x00\x00\xD3\x23lept_free',0,b'\x00\x00\x72\x23makePixelSumTab8',0,b'\x00\x00\x1F\x23pixAnd',0,b'\x00\x00\x2C\x23pixBackgroundNorm',0,b'\x00\x00\x24\x23pixCleanBackgroundToWhite',0,b'\x00\x00\x16\x23pixClipRectangle',0,b'\x00\x00\x9D\x23pixColorFraction',0,b'\x00\x00\x62\x23pixColorMagnitude',0,b'\x00\x00\x13\x23pixConvertRGBToLuminance',0,b'\x00\x00\x74\x23pixCorrelationBinary',0,b'\x00\x00\x89\x23pixCountPixels',0,b'\x00\x00\x6C\x23pixDeserializeFromMemory',0,b'\x00\x00\x59\x23pixDeskew',0,b'\x00\x00\xD0\x23pixDestroy',0,b'\x00\x00\x13\x23pixEndianByteSwapNew',0,b'\x00\x00\x03\x23pixFindPageForeground',0,b'\x00\x00\x84\x23pixFindSkew',0,b'\x00\x00\x38\x23pixGammaTRC',0,b'\x00\x00\x96\x23pixGenerateCIData',0,b'\x00\x00\x79\x23pixGetAverageMaskedRGB',0,b'\x00\x00\x3F\x23pixGlobalNormRGB',0,b'\x00\x00\x1B\x23pixInvert',0,b'\x00\x00\x0D\x23pixLocateBarcodes',0,b'\x00\x00\x5D\x23pixMaskOverColorPixels',0,b'\x00\x00\x8E\x23pixNumSignificantGrayColors',0,b'\x00\x00\xA6\x23pixOtsuAdaptiveThreshold',0,b'\x00\x00\x47\x23pixOtsuThreshOnBackgroundNorm',0,b'\x00\x00\x69\x23pixRead',0,b'\x00\x00\x59\x23pixRemoveColormap',0,b'\x00\x00\x5D\x23pixRemoveColormapGeneral',0,b'\x00\x00\x1B\x23pixRotate180',0,b'\x00\x00\x59\x23pixRotateOrth',0,b'\x00\x00\x54\x23pixScale',0,b'\x00\x00\xB0\x23pixSerializeToMemory',0,b'\x00\x00\xBB\x23pixWriteImpliedFormat',0,b'\x00\x00\xC1\x23pixWriteMemPng',0), - _struct_unions = ((b'\x00\x00\x00\xD6\x00\x00\x00\x02Box',b'\x00\x00\x05\x11x',b'\x00\x00\x05\x11y',b'\x00\x00\x05\x11w',b'\x00\x00\x05\x11h',b'\x00\x00\xEA\x11refcount'),(b'\x00\x00\x00\xD8\x00\x00\x00\x02Boxa',b'\x00\x00\x05\x11n',b'\x00\x00\x05\x11nalloc',b'\x00\x00\xEA\x11refcount',b'\x00\x00\x19\x11box'),(b'\x00\x00\x00\xDA\x00\x00\x00\x02L_Compressed_Data',b'\x00\x00\x05\x11type',b'\x00\x00\xE7\x11datacomp',b'\x00\x00\x6E\x11nbytescomp',b'\x00\x00\xDD\x11data85',b'\x00\x00\x6E\x11nbytes85',b'\x00\x00\xDD\x11cmapdata85',b'\x00\x00\xDD\x11cmapdatahex',b'\x00\x00\x05\x11ncolors',b'\x00\x00\x05\x11w',b'\x00\x00\x05\x11h',b'\x00\x00\x05\x11bps',b'\x00\x00\x05\x11spp',b'\x00\x00\x05\x11minisblack',b'\x00\x00\x05\x11predictor',b'\x00\x00\x6E\x11nbytes',b'\x00\x00\x05\x11res'),(b'\x00\x00\x00\xDB\x00\x00\x00\x02Pix',b'\x00\x00\xEA\x11w',b'\x00\x00\xEA\x11h',b'\x00\x00\xEA\x11d',b'\x00\x00\xEA\x11spp',b'\x00\x00\xEA\x11wpl',b'\x00\x00\xEA\x11refcount',b'\x00\x00\x05\x11xres',b'\x00\x00\x05\x11yres',b'\x00\x00\x05\x11informat',b'\x00\x00\x05\x11special',b'\x00\x00\xDD\x11text',b'\x00\x00\xE6\x11colormap',b'\x00\x00\xE9\x11data'),(b'\x00\x00\x00\xDC\x00\x00\x00\x02PixColormap',b'\x00\x00\xD4\x11array',b'\x00\x00\x05\x11depth',b'\x00\x00\x05\x11nalloc',b'\x00\x00\x05\x11n')), - _enums = (b'\x00\x00\x00\xE0\x00\x00\x00\x16$1\x00L_DEFAULT_ENCODE,L_JPEG_ENCODE,L_G4_ENCODE,L_FLATE_ENCODE,L_JP2K_ENCODE',b'\x00\x00\x00\xE1\x00\x00\x00\x16$2\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\xE2\x00\x00\x00\x16$3\x00L_NOCOPY,L_INSERT,L_COPY,L_CLONE,L_COPY_CLONE'), - _typenames = (b'\x00\x00\x00\xD6BOX',b'\x00\x00\x00\xD8BOXA',b'\x00\x00\x00\xDAL_COMP_DATA',b'\x00\x00\x00\xDBPIX',b'\x00\x00\x00\xDCPIXCMAP',b'\x00\x00\x00\x28l_float32',b'\x00\x00\x00\xDFl_float64',b'\x00\x00\x00\xE4l_int16',b'\x00\x00\x00\x05l_int32',b'\x00\x00\x00\xE3l_int64',b'\x00\x00\x00\xE5l_int8',b'\x00\x00\x00\xECl_uint16',b'\x00\x00\x00\xEAl_uint32',b'\x00\x00\x00\xEBl_uint64',b'\x00\x00\x00\xE8l_uint8'), + _types = b'\x00\x00\x01\x0D\x00\x00\xF6\x03\x00\x00\x00\x0F\x00\x00\x01\x0D\x00\x00\xF7\x03\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x01\x0D\x00\x00\xFA\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\x01\x01\x03\x00\x00\x00\x0F\x00\x00\x04\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x09\x03\x00\x00\x15\x11\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x01\x11\x00\x00\x01\x03\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x0D\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x09\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\x09\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x0D\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x09\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\x05\x03\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x0D\x01\x00\x00\x0D\x01\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x10\x11\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x01\x0F\x03\x00\x00\x1C\x01\x00\x00\x00\x0F\x00\x00\x81\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\xFD\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\xFD\x03\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\xFD\x0D\x00\x00\xFB\x03\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x7D\x11\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x01\x00\x0D\x00\x00\x00\x0F\x00\x00\x57\x0D\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x04\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x2D\x03\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x94\x11\x00\x00\x94\x11\x00\x00\x94\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x94\x11\x00\x00\x94\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x57\x11\x00\x00\x57\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x0D\x01\x00\x00\x07\x01\x00\x00\x57\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\xF8\x03\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x94\x11\x00\x00\x94\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x0D\x01\x00\x00\x15\x11\x00\x00\x15\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x01\x0E\x03\x00\x00\x73\x03\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x10\x11\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\xB8\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x10\x11\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x01\x0C\x03\x00\x00\xD0\x11\x00\x00\x09\x11\x00\x00\x0D\x01\x00\x00\x00\x0F\x00\x01\x12\x0D\x00\x00\x1E\x11\x00\x00\x00\x0F\x00\x01\x12\x0D\x00\x00\x04\x03\x00\x00\x00\x0F\x00\x01\x12\x0D\x00\x00\xB8\x11\x00\x00\x00\x0F\x00\x01\x12\x0D\x00\x00\x15\x11\x00\x00\x00\x0F\x00\x01\x12\x0D\x00\x00\x81\x03\x00\x00\x00\x0F\x00\x01\x12\x0D\x00\x01\x12\x03\x00\x00\x00\x0F\x00\x00\x00\x09\x00\x00\x01\x09\x00\x00\xF9\x03\x00\x00\x02\x09\x00\x00\x03\x09\x00\x00\x05\x09\x00\x00\x04\x09\x00\x00\xFE\x03\x00\x00\x06\x09\x00\x01\x00\x03\x00\x01\x01\x03\x00\x00\x02\x01\x00\x00\x0E\x01\x00\x00\x00\x0B\x00\x00\x01\x0B\x00\x00\x02\x0B\x00\x00\x03\x0B\x00\x00\x04\x0B\x00\x00\x0B\x01\x00\x00\x05\x01\x00\x00\x03\x01\x00\x00\xFC\x03\x00\x01\x0D\x03\x00\x00\x04\x01\x00\x01\x0F\x03\x00\x00\x08\x01\x00\x00\x0C\x01\x00\x00\x06\x01\x00\x00\x00\x01', + _globals = (b'\xFF\xFF\xFF\x0BL_BF_ANY',1,b'\xFF\xFF\xFF\x0BL_BF_CODABAR',9,b'\xFF\xFF\xFF\x0BL_BF_CODE128',2,b'\xFF\xFF\xFF\x0BL_BF_CODE2OF5',5,b'\xFF\xFF\xFF\x0BL_BF_CODE39',7,b'\xFF\xFF\xFF\x0BL_BF_CODE93',8,b'\xFF\xFF\xFF\x0BL_BF_CODEI2OF5',6,b'\xFF\xFF\xFF\x0BL_BF_EAN13',4,b'\xFF\xFF\xFF\x0BL_BF_EAN8',3,b'\xFF\xFF\xFF\x0BL_BF_UNKNOWN',0,b'\xFF\xFF\xFF\x0BL_BF_UPCA',10,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_DEFAULT_ENCODE',0,b'\xFF\xFF\xFF\x0BL_FLATE_ENCODE',3,b'\xFF\xFF\xFF\x0BL_G4_ENCODE',2,b'\xFF\xFF\xFF\x0BL_INSERT',0,b'\xFF\xFF\xFF\x0BL_JP2K_ENCODE',4,b'\xFF\xFF\xFF\x0BL_JPEG_ENCODE',1,b'\xFF\xFF\xFF\x0BL_NOCOPY',0,b'\xFF\xFF\xFF\x0BL_USE_WIDTHS',1,b'\xFF\xFF\xFF\x0BL_USE_WINDOWS',2,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\x00\x23boxClone',0,b'\x00\x00\xE4\x23boxDestroy',0,b'\x00\x00\xE7\x23boxaDestroy',0,b'\x00\x00\x03\x23boxaGetBox',0,b'\x00\x00\x87\x23getLeptonicaVersion',0,b'\x00\x00\xEA\x23l_CIDataDestroy',0,b'\x00\x00\xD2\x23l_generateCIDataForPdf',0,b'\x00\x00\xF3\x23lept_free',0,b'\x00\x00\x89\x23makePixelSumTab8',0,b'\x00\x00\x24\x23pixAnd',0,b'\x00\x00\x31\x23pixBackgroundNorm',0,b'\x00\x00\x29\x23pixCleanBackgroundToWhite',0,b'\x00\x00\x1B\x23pixClipRectangle',0,b'\x00\x00\xBA\x23pixColorFraction',0,b'\x00\x00\x67\x23pixColorMagnitude',0,b'\x00\x00\x18\x23pixConvertRGBToLuminance',0,b'\x00\x00\x91\x23pixCorrelationBinary',0,b'\x00\x00\xA6\x23pixCountPixels',0,b'\x00\x00\x71\x23pixDeserializeFromMemory',0,b'\x00\x00\x5E\x23pixDeskew',0,b'\x00\x00\xED\x23pixDestroy',0,b'\x00\x00\x18\x23pixEndianByteSwapNew',0,b'\x00\x00\x75\x23pixExtractBarcodes',0,b'\x00\x00\x08\x23pixFindPageForeground',0,b'\x00\x00\xA1\x23pixFindSkew',0,b'\x00\x00\x3D\x23pixGammaTRC',0,b'\x00\x00\xB3\x23pixGenerateCIData',0,b'\x00\x00\x96\x23pixGetAverageMaskedRGB',0,b'\x00\x00\x44\x23pixGlobalNormRGB',0,b'\x00\x00\x20\x23pixInvert',0,b'\x00\x00\x12\x23pixLocateBarcodes',0,b'\x00\x00\x62\x23pixMaskOverColorPixels',0,b'\x00\x00\xAB\x23pixNumSignificantGrayColors',0,b'\x00\x00\xC3\x23pixOtsuAdaptiveThreshold',0,b'\x00\x00\x4C\x23pixOtsuThreshOnBackgroundNorm',0,b'\x00\x00\x79\x23pixProcessBarcodes',0,b'\x00\x00\x6E\x23pixRead',0,b'\x00\x00\x80\x23pixReadBarcodes',0,b'\x00\x00\x5E\x23pixRemoveColormap',0,b'\x00\x00\x62\x23pixRemoveColormapGeneral',0,b'\x00\x00\x8B\x23pixRenderBoxa',0,b'\x00\x00\x20\x23pixRotate180',0,b'\x00\x00\x5E\x23pixRotateOrth',0,b'\x00\x00\x59\x23pixScale',0,b'\x00\x00\xCD\x23pixSerializeToMemory',0,b'\x00\x00\xD8\x23pixWriteImpliedFormat',0,b'\x00\x00\xDE\x23pixWriteMemPng',0,b'\x00\x00\xF0\x23pixaDestroy',0), + _struct_unions = ((b'\x00\x00\x00\xF6\x00\x00\x00\x02Box',b'\x00\x00\x05\x11x',b'\x00\x00\x05\x11y',b'\x00\x00\x05\x11w',b'\x00\x00\x05\x11h',b'\x00\x01\x0F\x11refcount'),(b'\x00\x00\x00\xF7\x00\x00\x00\x02Boxa',b'\x00\x00\x05\x11n',b'\x00\x00\x05\x11nalloc',b'\x00\x01\x0F\x11refcount',b'\x00\x00\x1E\x11box'),(b'\x00\x00\x00\xF9\x00\x00\x00\x02L_Compressed_Data',b'\x00\x00\x05\x11type',b'\x00\x01\x0C\x11datacomp',b'\x00\x00\x73\x11nbytescomp',b'\x00\x01\x00\x11data85',b'\x00\x00\x73\x11nbytes85',b'\x00\x01\x00\x11cmapdata85',b'\x00\x01\x00\x11cmapdatahex',b'\x00\x00\x05\x11ncolors',b'\x00\x00\x05\x11w',b'\x00\x00\x05\x11h',b'\x00\x00\x05\x11bps',b'\x00\x00\x05\x11spp',b'\x00\x00\x05\x11minisblack',b'\x00\x00\x05\x11predictor',b'\x00\x00\x73\x11nbytes',b'\x00\x00\x05\x11res'),(b'\x00\x00\x00\xFA\x00\x00\x00\x02Pix',b'\x00\x01\x0F\x11w',b'\x00\x01\x0F\x11h',b'\x00\x01\x0F\x11d',b'\x00\x01\x0F\x11spp',b'\x00\x01\x0F\x11wpl',b'\x00\x01\x0F\x11refcount',b'\x00\x00\x05\x11xres',b'\x00\x00\x05\x11yres',b'\x00\x00\x05\x11informat',b'\x00\x00\x05\x11special',b'\x00\x01\x00\x11text',b'\x00\x01\x0B\x11colormap',b'\x00\x01\x0E\x11data'),(b'\x00\x00\x00\xFC\x00\x00\x00\x02PixColormap',b'\x00\x00\xF4\x11array',b'\x00\x00\x05\x11depth',b'\x00\x00\x05\x11nalloc',b'\x00\x00\x05\x11n'),(b'\x00\x00\x00\xFB\x00\x00\x00\x02Pixa',b'\x00\x00\x05\x11n',b'\x00\x00\x05\x11nalloc',b'\x00\x01\x0F\x11refcount',b'\x00\x00\x15\x11pix',b'\x00\x00\x04\x11boxa'),(b'\x00\x00\x00\xFE\x00\x00\x00\x02Sarray',b'\x00\x00\x05\x11nalloc',b'\x00\x00\x05\x11n',b'\x00\x00\x05\x11refcount',b'\x00\x00\xFF\x11array')), + _enums = (b'\x00\x00\x01\x03\x00\x00\x00\x16$1\x00L_DEFAULT_ENCODE,L_JPEG_ENCODE,L_G4_ENCODE,L_FLATE_ENCODE,L_JP2K_ENCODE',b'\x00\x00\x01\x04\x00\x00\x00\x16$2\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\x01\x05\x00\x00\x00\x16$3\x00L_NOCOPY,L_INSERT,L_COPY,L_CLONE,L_COPY_CLONE',b'\x00\x00\x01\x06\x00\x00\x00\x16$4\x00L_USE_WIDTHS,L_USE_WINDOWS',b'\x00\x00\x01\x07\x00\x00\x00\x16$5\x00L_BF_UNKNOWN,L_BF_ANY,L_BF_CODE128,L_BF_EAN8,L_BF_EAN13,L_BF_CODE2OF5,L_BF_CODEI2OF5,L_BF_CODE39,L_BF_CODE93,L_BF_CODABAR,L_BF_UPCA'), + _typenames = (b'\x00\x00\x00\xF6BOX',b'\x00\x00\x00\xF7BOXA',b'\x00\x00\x00\xF9L_COMP_DATA',b'\x00\x00\x00\xFAPIX',b'\x00\x00\x00\xFBPIXA',b'\x00\x00\x00\xFCPIXCMAP',b'\x00\x00\x00\xFESARRAY',b'\x00\x00\x00\x2Dl_float32',b'\x00\x00\x01\x02l_float64',b'\x00\x00\x01\x09l_int16',b'\x00\x00\x00\x05l_int32',b'\x00\x00\x01\x08l_int64',b'\x00\x00\x01\x0Al_int8',b'\x00\x00\x00\x05l_ok',b'\x00\x00\x01\x11l_uint16',b'\x00\x00\x01\x0Fl_uint32',b'\x00\x00\x01\x10l_uint64',b'\x00\x00\x01\x0Dl_uint8'), ) diff --git a/src/ocrmypdf/lib/compile_leptonica.py b/src/ocrmypdf/lib/compile_leptonica.py index 963e45db..8582af2c 100644 --- a/src/ocrmypdf/lib/compile_leptonica.py +++ b/src/ocrmypdf/lib/compile_leptonica.py @@ -31,6 +31,8 @@ typedef double l_float64; typedef long long l_int64; typedef unsigned long long l_uint64; +typedef int l_ok; /*!< return type 0 if OK, 1 on error */ + struct Pix { l_uint32 w; /* width in pixels */ @@ -60,6 +62,17 @@ struct PixColormap }; typedef struct PixColormap PIXCMAP; +/*! Array of pix */ +struct Pixa +{ + l_int32 n; /*!< number of Pix in ptr array */ + l_int32 nalloc; /*!< number of Pix ptrs allocated */ + l_uint32 refcount; /*!< reference count (1 if no clones) */ + struct Pix **pix; /*!< the array of ptrs to pix */ + struct Boxa *boxa; /*!< array of boxes */ +}; +typedef struct Pixa PIXA; + struct Box { l_int32 x; @@ -81,6 +94,16 @@ struct Boxa }; typedef struct Boxa BOXA; +/*! String array: an array of C strings */ +struct Sarray +{ + l_int32 nalloc; /*!< size of allocated ptr array */ + l_int32 n; /*!< number of strings allocated */ + l_int32 refcount; /*!< reference count (1 if no clones) */ + char **array; /*!< string array */ +}; +typedef struct Sarray SARRAY; + /*! Pdf formatted encoding types */ enum { L_DEFAULT_ENCODE = 0, /*!< use default encoding based on image */ @@ -130,6 +153,27 @@ enum { /*!< the array with clones (e.g., pix) */ }; +/*! Flags for method of extracting barcode widths */ +enum { + L_USE_WIDTHS = 1, /*!< use histogram of barcode widths */ + L_USE_WINDOWS = 2 /*!< find best window for decoding transitions */ +}; + +/*! Flags for barcode formats */ +enum { + L_BF_UNKNOWN = 0, /*!< unknown format */ + L_BF_ANY = 1, /*!< try decoding with all known formats */ + L_BF_CODE128 = 2, /*!< decode with Code128 format */ + L_BF_EAN8 = 3, /*!< decode with EAN8 format */ + L_BF_EAN13 = 4, /*!< decode with EAN13 format */ + L_BF_CODE2OF5 = 5, /*!< decode with Code 2 of 5 format */ + L_BF_CODEI2OF5 = 6, /*!< decode with Interleaved 2 of 5 format */ + L_BF_CODE39 = 7, /*!< decode with Code39 format */ + L_BF_CODE93 = 8, /*!< decode with Code93 format */ + L_BF_CODABAR = 9, /*!< decode with Code93 format */ + L_BF_UPCA = 10 /*!< decode with UPC A format */ +}; + """) ffibuilder.cdef(""" @@ -297,9 +341,27 @@ pixGenerateCIData(PIX *pixs, l_int32 ascii85, L_COMP_DATA **pcid); +SARRAY * +pixProcessBarcodes(PIX *pixs, + l_int32 format, + l_int32 method, + SARRAY **psaw, + l_int32 debugflag); + +PIXA * +pixExtractBarcodes(PIX *pixs, + l_int32 debugflag); + BOXA * pixLocateBarcodes ( PIX *pixs, l_int32 thresh, PIX **ppixb, PIX **ppixm ); +SARRAY * +pixReadBarcodes(PIXA *pixa, + l_int32 format, + l_int32 method, + SARRAY **psaw, + l_int32 debugflag); + l_int32 l_generateCIDataForPdf(const char *fname, PIX *pix, @@ -309,12 +371,21 @@ l_generateCIDataForPdf(const char *fname, BOX * boxClone ( BOX *box ); +BOX * +boxaGetBox ( BOXA *boxa, l_int32 index, l_int32 accessflag ); + void boxDestroy(BOX **pbox); void boxaDestroy ( BOXA **pboxa ); +void +pixaDestroy(PIXA **ppixa); + +l_ok +pixRenderBoxa ( PIX *pix, BOXA *boxa, l_int32 width, l_int32 op ); + void l_CIDataDestroy(L_COMP_DATA **pcid); From 02f37293ee8459bddea26e499bab4a3020a09149 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Mon, 5 Nov 2018 13:01:13 -0800 Subject: [PATCH 49/68] Integrate barcode masking --- src/ocrmypdf/__main__.py | 10 +++++++--- src/ocrmypdf/_pipeline.py | 8 ++++++++ src/ocrmypdf/leptonica.py | 25 +++++++++++++++++++------ src/ocrmypdf/lib/_leptonica.py | 10 +++++----- src/ocrmypdf/lib/compile_leptonica.py | 12 ++++++++++++ 5 files changed, 51 insertions(+), 14 deletions(-) diff --git a/src/ocrmypdf/__main__.py b/src/ocrmypdf/__main__.py index 8fb4589f..511b0cf1 100755 --- a/src/ocrmypdf/__main__.py +++ b/src/ocrmypdf/__main__.py @@ -246,9 +246,13 @@ preprocessing.add_argument( "results slightly") preprocessing.add_argument( '--remove-vectors', action='store_true', - help="EXPERIMENTAL. Remove any vector graphics objects from the PDF, " - "including text rendered as curves. Useful when these objects " - "interfere with OCR.") + help="EXPERIMENTAL. Mask out any vector objects in the PDF so that they " + "will not be included in OCR. This can eliminate false characters.") +preprocessing.add_argument( + '--mask-barcodes', action='store_true', + help="Mask out any barcodes that appear in the PDF so they are not " + "considered during OCR. Barcodes can introduce false characters into " + "OCR.") ocrsettings = parser.add_argument_group( "OCR options", diff --git a/src/ocrmypdf/_pipeline.py b/src/ocrmypdf/_pipeline.py index 8240ba90..13fa51ce 100644 --- a/src/ocrmypdf/_pipeline.py +++ b/src/ocrmypdf/_pipeline.py @@ -632,6 +632,14 @@ def select_ocr_image( draw.rectangle(pixcoords, fill=white) #draw.rectangle(pixcoords, outline=pink) + if options.mask_barcodes: + pix = leptonica.Pix.open(image) + barcodes = pix.locate_barcodes() + for barcode in barcodes: + decoded, rect = barcode + log.info('masking barcode %s %r', decoded, rect) + draw.rectangle(rect, fill=white) + del draw # Pillow requires integer DPI diff --git a/src/ocrmypdf/leptonica.py b/src/ocrmypdf/leptonica.py index a3a31c52..e7648239 100644 --- a/src/ocrmypdf/leptonica.py +++ b/src/ocrmypdf/leptonica.py @@ -103,6 +103,8 @@ class _LeptonicaErrorTrap: raise FileNotFoundError() if 'pixWrite: stream not opened' in leptonica_output: raise LeptonicaIOError() + if 'index not valid' in leptonica_output: + raise IndexError() raise LeptonicaError(leptonica_output) return False @@ -486,10 +488,9 @@ class Pix: return Pix(lept.pixInvert(ffi.NULL, self._pix)) def locate_barcodes(self): - pixa_candidates = lept.pixExtractBarcodes(self._pix, 0) - if not pixa_candidates: - return - sarray = lept.pixReadBarcodes(pixa_candidates, + pix = Pix(lept.pixConvertTo8(self._pix, 0)) + pixa_candidates = PixArray(lept.pixExtractBarcodes(pix._pix, 0)) + sarray = lept.pixReadBarcodes(pixa_candidates._pixa, lept.L_BF_ANY, lept.L_USE_WIDTHS, ffi.NULL, @@ -500,7 +501,7 @@ class Pix: decoded = ffi.string(sarray[0].array[n]).decode() if decoded.strip() == '': continue - box = pixa_candidates[0].boxa[0].box[n][0] + box = pixa_candidates.get_box(n) left, top, right, bottom = box.x, box.y, box.x + box.w, box.y + box.h yield (decoded, (left, top, right, bottom)) @@ -553,12 +554,24 @@ class CompressedData: class PixArray: + """Wrapper around PIXA (array of PIX)""" def __init__(self, pixa): if not pixa: raise ValueError('NULL pixa') self._pixa = ffi.gc(pixa, PixArray._pixa_destroy) + def __len__(self): + return self._pixa[0].n + + def __getitem__(self, n): + with _LeptonicaErrorTrap(): + return Pix(lept.pixaGetPix(self._pixa, n, lept.L_CLONE)) + + def get_box(self, n): + with _LeptonicaErrorTrap(): + return Box(lept.pixaGetBox(self._pixa, n, lept.L_CLONE)) + @staticmethod def _pixa_destroy(pixa): pp = ffi.new('PIXA **', pixa) @@ -566,7 +579,7 @@ class PixArray: class Box: - """Wrapper around Leptonica's BOX objects. + """Wrapper around Leptonica's BOX objects (a pixel rectangle) See class Pix for notes about reference counting. """ diff --git a/src/ocrmypdf/lib/_leptonica.py b/src/ocrmypdf/lib/_leptonica.py index a606c77f..b26d3777 100644 --- a/src/ocrmypdf/lib/_leptonica.py +++ b/src/ocrmypdf/lib/_leptonica.py @@ -3,9 +3,9 @@ import _cffi_backend ffi = _cffi_backend.FFI('ocrmypdf.lib._leptonica', _version = 0x2601, - _types = b'\x00\x00\x01\x0D\x00\x00\xF6\x03\x00\x00\x00\x0F\x00\x00\x01\x0D\x00\x00\xF7\x03\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x01\x0D\x00\x00\xFA\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\x01\x01\x03\x00\x00\x00\x0F\x00\x00\x04\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x09\x03\x00\x00\x15\x11\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x01\x11\x00\x00\x01\x03\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x0D\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x09\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\x09\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x0D\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x09\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\x05\x03\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x0D\x01\x00\x00\x0D\x01\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x10\x11\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x01\x0F\x03\x00\x00\x1C\x01\x00\x00\x00\x0F\x00\x00\x81\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\xFD\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\xFD\x03\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\xFD\x0D\x00\x00\xFB\x03\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x7D\x11\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x01\x00\x0D\x00\x00\x00\x0F\x00\x00\x57\x0D\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x04\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x2D\x03\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x94\x11\x00\x00\x94\x11\x00\x00\x94\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x94\x11\x00\x00\x94\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x57\x11\x00\x00\x57\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x0D\x01\x00\x00\x07\x01\x00\x00\x57\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\xF8\x03\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x94\x11\x00\x00\x94\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x0D\x01\x00\x00\x15\x11\x00\x00\x15\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x01\x0E\x03\x00\x00\x73\x03\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x10\x11\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\xB8\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x10\x11\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x01\x0C\x03\x00\x00\xD0\x11\x00\x00\x09\x11\x00\x00\x0D\x01\x00\x00\x00\x0F\x00\x01\x12\x0D\x00\x00\x1E\x11\x00\x00\x00\x0F\x00\x01\x12\x0D\x00\x00\x04\x03\x00\x00\x00\x0F\x00\x01\x12\x0D\x00\x00\xB8\x11\x00\x00\x00\x0F\x00\x01\x12\x0D\x00\x00\x15\x11\x00\x00\x00\x0F\x00\x01\x12\x0D\x00\x00\x81\x03\x00\x00\x00\x0F\x00\x01\x12\x0D\x00\x01\x12\x03\x00\x00\x00\x0F\x00\x00\x00\x09\x00\x00\x01\x09\x00\x00\xF9\x03\x00\x00\x02\x09\x00\x00\x03\x09\x00\x00\x05\x09\x00\x00\x04\x09\x00\x00\xFE\x03\x00\x00\x06\x09\x00\x01\x00\x03\x00\x01\x01\x03\x00\x00\x02\x01\x00\x00\x0E\x01\x00\x00\x00\x0B\x00\x00\x01\x0B\x00\x00\x02\x0B\x00\x00\x03\x0B\x00\x00\x04\x0B\x00\x00\x0B\x01\x00\x00\x05\x01\x00\x00\x03\x01\x00\x00\xFC\x03\x00\x01\x0D\x03\x00\x00\x04\x01\x00\x01\x0F\x03\x00\x00\x08\x01\x00\x00\x0C\x01\x00\x00\x06\x01\x00\x00\x00\x01', - _globals = (b'\xFF\xFF\xFF\x0BL_BF_ANY',1,b'\xFF\xFF\xFF\x0BL_BF_CODABAR',9,b'\xFF\xFF\xFF\x0BL_BF_CODE128',2,b'\xFF\xFF\xFF\x0BL_BF_CODE2OF5',5,b'\xFF\xFF\xFF\x0BL_BF_CODE39',7,b'\xFF\xFF\xFF\x0BL_BF_CODE93',8,b'\xFF\xFF\xFF\x0BL_BF_CODEI2OF5',6,b'\xFF\xFF\xFF\x0BL_BF_EAN13',4,b'\xFF\xFF\xFF\x0BL_BF_EAN8',3,b'\xFF\xFF\xFF\x0BL_BF_UNKNOWN',0,b'\xFF\xFF\xFF\x0BL_BF_UPCA',10,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_DEFAULT_ENCODE',0,b'\xFF\xFF\xFF\x0BL_FLATE_ENCODE',3,b'\xFF\xFF\xFF\x0BL_G4_ENCODE',2,b'\xFF\xFF\xFF\x0BL_INSERT',0,b'\xFF\xFF\xFF\x0BL_JP2K_ENCODE',4,b'\xFF\xFF\xFF\x0BL_JPEG_ENCODE',1,b'\xFF\xFF\xFF\x0BL_NOCOPY',0,b'\xFF\xFF\xFF\x0BL_USE_WIDTHS',1,b'\xFF\xFF\xFF\x0BL_USE_WINDOWS',2,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\x00\x23boxClone',0,b'\x00\x00\xE4\x23boxDestroy',0,b'\x00\x00\xE7\x23boxaDestroy',0,b'\x00\x00\x03\x23boxaGetBox',0,b'\x00\x00\x87\x23getLeptonicaVersion',0,b'\x00\x00\xEA\x23l_CIDataDestroy',0,b'\x00\x00\xD2\x23l_generateCIDataForPdf',0,b'\x00\x00\xF3\x23lept_free',0,b'\x00\x00\x89\x23makePixelSumTab8',0,b'\x00\x00\x24\x23pixAnd',0,b'\x00\x00\x31\x23pixBackgroundNorm',0,b'\x00\x00\x29\x23pixCleanBackgroundToWhite',0,b'\x00\x00\x1B\x23pixClipRectangle',0,b'\x00\x00\xBA\x23pixColorFraction',0,b'\x00\x00\x67\x23pixColorMagnitude',0,b'\x00\x00\x18\x23pixConvertRGBToLuminance',0,b'\x00\x00\x91\x23pixCorrelationBinary',0,b'\x00\x00\xA6\x23pixCountPixels',0,b'\x00\x00\x71\x23pixDeserializeFromMemory',0,b'\x00\x00\x5E\x23pixDeskew',0,b'\x00\x00\xED\x23pixDestroy',0,b'\x00\x00\x18\x23pixEndianByteSwapNew',0,b'\x00\x00\x75\x23pixExtractBarcodes',0,b'\x00\x00\x08\x23pixFindPageForeground',0,b'\x00\x00\xA1\x23pixFindSkew',0,b'\x00\x00\x3D\x23pixGammaTRC',0,b'\x00\x00\xB3\x23pixGenerateCIData',0,b'\x00\x00\x96\x23pixGetAverageMaskedRGB',0,b'\x00\x00\x44\x23pixGlobalNormRGB',0,b'\x00\x00\x20\x23pixInvert',0,b'\x00\x00\x12\x23pixLocateBarcodes',0,b'\x00\x00\x62\x23pixMaskOverColorPixels',0,b'\x00\x00\xAB\x23pixNumSignificantGrayColors',0,b'\x00\x00\xC3\x23pixOtsuAdaptiveThreshold',0,b'\x00\x00\x4C\x23pixOtsuThreshOnBackgroundNorm',0,b'\x00\x00\x79\x23pixProcessBarcodes',0,b'\x00\x00\x6E\x23pixRead',0,b'\x00\x00\x80\x23pixReadBarcodes',0,b'\x00\x00\x5E\x23pixRemoveColormap',0,b'\x00\x00\x62\x23pixRemoveColormapGeneral',0,b'\x00\x00\x8B\x23pixRenderBoxa',0,b'\x00\x00\x20\x23pixRotate180',0,b'\x00\x00\x5E\x23pixRotateOrth',0,b'\x00\x00\x59\x23pixScale',0,b'\x00\x00\xCD\x23pixSerializeToMemory',0,b'\x00\x00\xD8\x23pixWriteImpliedFormat',0,b'\x00\x00\xDE\x23pixWriteMemPng',0,b'\x00\x00\xF0\x23pixaDestroy',0), - _struct_unions = ((b'\x00\x00\x00\xF6\x00\x00\x00\x02Box',b'\x00\x00\x05\x11x',b'\x00\x00\x05\x11y',b'\x00\x00\x05\x11w',b'\x00\x00\x05\x11h',b'\x00\x01\x0F\x11refcount'),(b'\x00\x00\x00\xF7\x00\x00\x00\x02Boxa',b'\x00\x00\x05\x11n',b'\x00\x00\x05\x11nalloc',b'\x00\x01\x0F\x11refcount',b'\x00\x00\x1E\x11box'),(b'\x00\x00\x00\xF9\x00\x00\x00\x02L_Compressed_Data',b'\x00\x00\x05\x11type',b'\x00\x01\x0C\x11datacomp',b'\x00\x00\x73\x11nbytescomp',b'\x00\x01\x00\x11data85',b'\x00\x00\x73\x11nbytes85',b'\x00\x01\x00\x11cmapdata85',b'\x00\x01\x00\x11cmapdatahex',b'\x00\x00\x05\x11ncolors',b'\x00\x00\x05\x11w',b'\x00\x00\x05\x11h',b'\x00\x00\x05\x11bps',b'\x00\x00\x05\x11spp',b'\x00\x00\x05\x11minisblack',b'\x00\x00\x05\x11predictor',b'\x00\x00\x73\x11nbytes',b'\x00\x00\x05\x11res'),(b'\x00\x00\x00\xFA\x00\x00\x00\x02Pix',b'\x00\x01\x0F\x11w',b'\x00\x01\x0F\x11h',b'\x00\x01\x0F\x11d',b'\x00\x01\x0F\x11spp',b'\x00\x01\x0F\x11wpl',b'\x00\x01\x0F\x11refcount',b'\x00\x00\x05\x11xres',b'\x00\x00\x05\x11yres',b'\x00\x00\x05\x11informat',b'\x00\x00\x05\x11special',b'\x00\x01\x00\x11text',b'\x00\x01\x0B\x11colormap',b'\x00\x01\x0E\x11data'),(b'\x00\x00\x00\xFC\x00\x00\x00\x02PixColormap',b'\x00\x00\xF4\x11array',b'\x00\x00\x05\x11depth',b'\x00\x00\x05\x11nalloc',b'\x00\x00\x05\x11n'),(b'\x00\x00\x00\xFB\x00\x00\x00\x02Pixa',b'\x00\x00\x05\x11n',b'\x00\x00\x05\x11nalloc',b'\x00\x01\x0F\x11refcount',b'\x00\x00\x15\x11pix',b'\x00\x00\x04\x11boxa'),(b'\x00\x00\x00\xFE\x00\x00\x00\x02Sarray',b'\x00\x00\x05\x11nalloc',b'\x00\x00\x05\x11n',b'\x00\x00\x05\x11refcount',b'\x00\x00\xFF\x11array')), - _enums = (b'\x00\x00\x01\x03\x00\x00\x00\x16$1\x00L_DEFAULT_ENCODE,L_JPEG_ENCODE,L_G4_ENCODE,L_FLATE_ENCODE,L_JP2K_ENCODE',b'\x00\x00\x01\x04\x00\x00\x00\x16$2\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\x01\x05\x00\x00\x00\x16$3\x00L_NOCOPY,L_INSERT,L_COPY,L_CLONE,L_COPY_CLONE',b'\x00\x00\x01\x06\x00\x00\x00\x16$4\x00L_USE_WIDTHS,L_USE_WINDOWS',b'\x00\x00\x01\x07\x00\x00\x00\x16$5\x00L_BF_UNKNOWN,L_BF_ANY,L_BF_CODE128,L_BF_EAN8,L_BF_EAN13,L_BF_CODE2OF5,L_BF_CODEI2OF5,L_BF_CODE39,L_BF_CODE93,L_BF_CODABAR,L_BF_UPCA'), - _typenames = (b'\x00\x00\x00\xF6BOX',b'\x00\x00\x00\xF7BOXA',b'\x00\x00\x00\xF9L_COMP_DATA',b'\x00\x00\x00\xFAPIX',b'\x00\x00\x00\xFBPIXA',b'\x00\x00\x00\xFCPIXCMAP',b'\x00\x00\x00\xFESARRAY',b'\x00\x00\x00\x2Dl_float32',b'\x00\x00\x01\x02l_float64',b'\x00\x00\x01\x09l_int16',b'\x00\x00\x00\x05l_int32',b'\x00\x00\x01\x08l_int64',b'\x00\x00\x01\x0Al_int8',b'\x00\x00\x00\x05l_ok',b'\x00\x00\x01\x11l_uint16',b'\x00\x00\x01\x0Fl_uint32',b'\x00\x00\x01\x10l_uint64',b'\x00\x00\x01\x0Dl_uint8'), + _types = b'\x00\x00\x01\x0D\x00\x01\x00\x03\x00\x00\x00\x0F\x00\x00\x01\x0D\x00\x01\x01\x03\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x01\x0D\x00\x01\x04\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\x01\x0B\x03\x00\x00\x00\x0F\x00\x00\x01\x0D\x00\x01\x05\x03\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x04\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x09\x03\x00\x00\x1A\x11\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x01\x11\x00\x00\x01\x03\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x0D\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x09\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\x09\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x0D\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x09\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\x05\x03\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x0D\x01\x00\x00\x0D\x01\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x13\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x10\x11\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x01\x19\x03\x00\x00\x1C\x01\x00\x00\x00\x0F\x00\x00\x13\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x01\x07\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x01\x07\x03\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x01\x07\x0D\x00\x00\x13\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x87\x11\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x01\x0A\x0D\x00\x00\x00\x0F\x00\x00\x5C\x0D\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x04\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x32\x03\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x9E\x11\x00\x00\x9E\x11\x00\x00\x9E\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x9E\x11\x00\x00\x9E\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x5C\x11\x00\x00\x5C\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x0D\x01\x00\x00\x07\x01\x00\x00\x5C\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x01\x02\x03\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x9E\x11\x00\x00\x9E\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x0D\x01\x00\x00\x1A\x11\x00\x00\x1A\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x01\x18\x03\x00\x00\x7D\x03\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x10\x11\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\xC2\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x10\x11\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x01\x16\x03\x00\x00\xDA\x11\x00\x00\x09\x11\x00\x00\x0D\x01\x00\x00\x00\x0F\x00\x01\x1C\x0D\x00\x00\x23\x11\x00\x00\x00\x0F\x00\x01\x1C\x0D\x00\x00\x04\x03\x00\x00\x00\x0F\x00\x01\x1C\x0D\x00\x00\xC2\x11\x00\x00\x00\x0F\x00\x01\x1C\x0D\x00\x00\x1A\x11\x00\x00\x00\x0F\x00\x01\x1C\x0D\x00\x00\x13\x03\x00\x00\x00\x0F\x00\x01\x1C\x0D\x00\x01\x1C\x03\x00\x00\x00\x0F\x00\x00\x00\x09\x00\x00\x01\x09\x00\x01\x03\x03\x00\x00\x02\x09\x00\x00\x03\x09\x00\x00\x05\x09\x00\x00\x04\x09\x00\x01\x08\x03\x00\x00\x06\x09\x00\x01\x0A\x03\x00\x01\x0B\x03\x00\x00\x02\x01\x00\x00\x0E\x01\x00\x00\x00\x0B\x00\x00\x01\x0B\x00\x00\x02\x0B\x00\x00\x03\x0B\x00\x00\x04\x0B\x00\x00\x0B\x01\x00\x00\x05\x01\x00\x00\x03\x01\x00\x01\x06\x03\x00\x01\x17\x03\x00\x00\x04\x01\x00\x01\x19\x03\x00\x00\x08\x01\x00\x00\x0C\x01\x00\x00\x06\x01\x00\x00\x00\x01', + _globals = (b'\xFF\xFF\xFF\x0BL_BF_ANY',1,b'\xFF\xFF\xFF\x0BL_BF_CODABAR',9,b'\xFF\xFF\xFF\x0BL_BF_CODE128',2,b'\xFF\xFF\xFF\x0BL_BF_CODE2OF5',5,b'\xFF\xFF\xFF\x0BL_BF_CODE39',7,b'\xFF\xFF\xFF\x0BL_BF_CODE93',8,b'\xFF\xFF\xFF\x0BL_BF_CODEI2OF5',6,b'\xFF\xFF\xFF\x0BL_BF_EAN13',4,b'\xFF\xFF\xFF\x0BL_BF_EAN8',3,b'\xFF\xFF\xFF\x0BL_BF_UNKNOWN',0,b'\xFF\xFF\xFF\x0BL_BF_UPCA',10,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_DEFAULT_ENCODE',0,b'\xFF\xFF\xFF\x0BL_FLATE_ENCODE',3,b'\xFF\xFF\xFF\x0BL_G4_ENCODE',2,b'\xFF\xFF\xFF\x0BL_INSERT',0,b'\xFF\xFF\xFF\x0BL_JP2K_ENCODE',4,b'\xFF\xFF\xFF\x0BL_JPEG_ENCODE',1,b'\xFF\xFF\xFF\x0BL_NOCOPY',0,b'\xFF\xFF\xFF\x0BL_USE_WIDTHS',1,b'\xFF\xFF\xFF\x0BL_USE_WINDOWS',2,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\x00\x23boxClone',0,b'\x00\x00\xEE\x23boxDestroy',0,b'\x00\x00\xF1\x23boxaDestroy',0,b'\x00\x00\x03\x23boxaGetBox',0,b'\x00\x00\x91\x23getLeptonicaVersion',0,b'\x00\x00\xF4\x23l_CIDataDestroy',0,b'\x00\x00\xDC\x23l_generateCIDataForPdf',0,b'\x00\x00\xFD\x23lept_free',0,b'\x00\x00\x93\x23makePixelSumTab8',0,b'\x00\x00\x29\x23pixAnd',0,b'\x00\x00\x36\x23pixBackgroundNorm',0,b'\x00\x00\x2E\x23pixCleanBackgroundToWhite',0,b'\x00\x00\x20\x23pixClipRectangle',0,b'\x00\x00\xC4\x23pixColorFraction',0,b'\x00\x00\x6C\x23pixColorMagnitude',0,b'\x00\x00\x1D\x23pixConvertRGBToLuminance',0,b'\x00\x00\x63\x23pixConvertTo8',0,b'\x00\x00\x9B\x23pixCorrelationBinary',0,b'\x00\x00\xB0\x23pixCountPixels',0,b'\x00\x00\x7B\x23pixDeserializeFromMemory',0,b'\x00\x00\x63\x23pixDeskew',0,b'\x00\x00\xF7\x23pixDestroy',0,b'\x00\x00\x1D\x23pixEndianByteSwapNew',0,b'\x00\x00\x7F\x23pixExtractBarcodes',0,b'\x00\x00\x08\x23pixFindPageForeground',0,b'\x00\x00\xAB\x23pixFindSkew',0,b'\x00\x00\x42\x23pixGammaTRC',0,b'\x00\x00\xBD\x23pixGenerateCIData',0,b'\x00\x00\xA0\x23pixGetAverageMaskedRGB',0,b'\x00\x00\x49\x23pixGlobalNormRGB',0,b'\x00\x00\x25\x23pixInvert',0,b'\x00\x00\x17\x23pixLocateBarcodes',0,b'\x00\x00\x67\x23pixMaskOverColorPixels',0,b'\x00\x00\xB5\x23pixNumSignificantGrayColors',0,b'\x00\x00\xCD\x23pixOtsuAdaptiveThreshold',0,b'\x00\x00\x51\x23pixOtsuThreshOnBackgroundNorm',0,b'\x00\x00\x83\x23pixProcessBarcodes',0,b'\x00\x00\x78\x23pixRead',0,b'\x00\x00\x8A\x23pixReadBarcodes',0,b'\x00\x00\x63\x23pixRemoveColormap',0,b'\x00\x00\x67\x23pixRemoveColormapGeneral',0,b'\x00\x00\x95\x23pixRenderBoxa',0,b'\x00\x00\x25\x23pixRotate180',0,b'\x00\x00\x63\x23pixRotateOrth',0,b'\x00\x00\x5E\x23pixScale',0,b'\x00\x00\xD7\x23pixSerializeToMemory',0,b'\x00\x00\xE2\x23pixWriteImpliedFormat',0,b'\x00\x00\xE8\x23pixWriteMemPng',0,b'\x00\x00\xFA\x23pixaDestroy',0,b'\x00\x00\x12\x23pixaGetBox',0,b'\x00\x00\x73\x23pixaGetPix',0), + _struct_unions = ((b'\x00\x00\x01\x00\x00\x00\x00\x02Box',b'\x00\x00\x05\x11x',b'\x00\x00\x05\x11y',b'\x00\x00\x05\x11w',b'\x00\x00\x05\x11h',b'\x00\x01\x19\x11refcount'),(b'\x00\x00\x01\x01\x00\x00\x00\x02Boxa',b'\x00\x00\x05\x11n',b'\x00\x00\x05\x11nalloc',b'\x00\x01\x19\x11refcount',b'\x00\x00\x23\x11box'),(b'\x00\x00\x01\x03\x00\x00\x00\x02L_Compressed_Data',b'\x00\x00\x05\x11type',b'\x00\x01\x16\x11datacomp',b'\x00\x00\x7D\x11nbytescomp',b'\x00\x01\x0A\x11data85',b'\x00\x00\x7D\x11nbytes85',b'\x00\x01\x0A\x11cmapdata85',b'\x00\x01\x0A\x11cmapdatahex',b'\x00\x00\x05\x11ncolors',b'\x00\x00\x05\x11w',b'\x00\x00\x05\x11h',b'\x00\x00\x05\x11bps',b'\x00\x00\x05\x11spp',b'\x00\x00\x05\x11minisblack',b'\x00\x00\x05\x11predictor',b'\x00\x00\x7D\x11nbytes',b'\x00\x00\x05\x11res'),(b'\x00\x00\x01\x04\x00\x00\x00\x02Pix',b'\x00\x01\x19\x11w',b'\x00\x01\x19\x11h',b'\x00\x01\x19\x11d',b'\x00\x01\x19\x11spp',b'\x00\x01\x19\x11wpl',b'\x00\x01\x19\x11refcount',b'\x00\x00\x05\x11xres',b'\x00\x00\x05\x11yres',b'\x00\x00\x05\x11informat',b'\x00\x00\x05\x11special',b'\x00\x01\x0A\x11text',b'\x00\x01\x15\x11colormap',b'\x00\x01\x18\x11data'),(b'\x00\x00\x01\x06\x00\x00\x00\x02PixColormap',b'\x00\x00\xFE\x11array',b'\x00\x00\x05\x11depth',b'\x00\x00\x05\x11nalloc',b'\x00\x00\x05\x11n'),(b'\x00\x00\x01\x05\x00\x00\x00\x02Pixa',b'\x00\x00\x05\x11n',b'\x00\x00\x05\x11nalloc',b'\x00\x01\x19\x11refcount',b'\x00\x00\x1A\x11pix',b'\x00\x00\x04\x11boxa'),(b'\x00\x00\x01\x08\x00\x00\x00\x02Sarray',b'\x00\x00\x05\x11nalloc',b'\x00\x00\x05\x11n',b'\x00\x00\x05\x11refcount',b'\x00\x01\x09\x11array')), + _enums = (b'\x00\x00\x01\x0D\x00\x00\x00\x16$1\x00L_DEFAULT_ENCODE,L_JPEG_ENCODE,L_G4_ENCODE,L_FLATE_ENCODE,L_JP2K_ENCODE',b'\x00\x00\x01\x0E\x00\x00\x00\x16$2\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\x01\x0F\x00\x00\x00\x16$3\x00L_NOCOPY,L_INSERT,L_COPY,L_CLONE,L_COPY_CLONE',b'\x00\x00\x01\x10\x00\x00\x00\x16$4\x00L_USE_WIDTHS,L_USE_WINDOWS',b'\x00\x00\x01\x11\x00\x00\x00\x16$5\x00L_BF_UNKNOWN,L_BF_ANY,L_BF_CODE128,L_BF_EAN8,L_BF_EAN13,L_BF_CODE2OF5,L_BF_CODEI2OF5,L_BF_CODE39,L_BF_CODE93,L_BF_CODABAR,L_BF_UPCA'), + _typenames = (b'\x00\x00\x01\x00BOX',b'\x00\x00\x01\x01BOXA',b'\x00\x00\x01\x03L_COMP_DATA',b'\x00\x00\x01\x04PIX',b'\x00\x00\x01\x05PIXA',b'\x00\x00\x01\x06PIXCMAP',b'\x00\x00\x01\x08SARRAY',b'\x00\x00\x00\x32l_float32',b'\x00\x00\x01\x0Cl_float64',b'\x00\x00\x01\x13l_int16',b'\x00\x00\x00\x05l_int32',b'\x00\x00\x01\x12l_int64',b'\x00\x00\x01\x14l_int8',b'\x00\x00\x00\x05l_ok',b'\x00\x00\x01\x1Bl_uint16',b'\x00\x00\x01\x19l_uint32',b'\x00\x00\x01\x1Al_uint64',b'\x00\x00\x01\x17l_uint8'), ) diff --git a/src/ocrmypdf/lib/compile_leptonica.py b/src/ocrmypdf/lib/compile_leptonica.py index 8582af2c..537ec1d5 100644 --- a/src/ocrmypdf/lib/compile_leptonica.py +++ b/src/ocrmypdf/lib/compile_leptonica.py @@ -209,6 +209,8 @@ l_int32 pixSerializeToMemory ( PIX *pixs, l_uint32 **pdata, size_t *pnbytes ); PIX * pixConvertRGBToLuminance(PIX *pixs); +PIX * pixConvertTo8(PIX *pixs, l_int32 cmapflag); + PIX * pixRemoveColormap(PIX *pixs, l_int32 type); l_int32 @@ -348,6 +350,16 @@ pixProcessBarcodes(PIX *pixs, SARRAY **psaw, l_int32 debugflag); +PIX * +pixaGetPix(PIXA *pixa, + l_int32 index, + l_int32 accesstype); + +BOX* +pixaGetBox (PIXA * pixa, + l_int32 index, + l_int32 accesstype ); + PIXA * pixExtractBarcodes(PIX *pixs, l_int32 debugflag); From 03076e89cee7715f07979b28c7b51160126d4ddb Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Tue, 6 Nov 2018 00:12:38 -0800 Subject: [PATCH 50/68] Leptonica: reduce verbosity, more error trapping, more garbage collection --- src/ocrmypdf/leptonica.py | 82 +++++++++++++++++++-------- src/ocrmypdf/lib/_leptonica.py | 10 ++-- src/ocrmypdf/lib/compile_leptonica.py | 21 +++++++ 3 files changed, 83 insertions(+), 30 deletions(-) diff --git a/src/ocrmypdf/leptonica.py b/src/ocrmypdf/leptonica.py index e7648239..cf184005 100644 --- a/src/ocrmypdf/leptonica.py +++ b/src/ocrmypdf/leptonica.py @@ -38,6 +38,7 @@ lept = ffi.dlopen(find_library('lept')) logger = logging.getLogger(__name__) +lept.setMsgSeverity(lept.L_SEVERITY_WARNING) def stderr(*objs): """Shorthand print to stderr.""" @@ -143,7 +144,7 @@ class Pix: def __init__(self, pix): if not pix: raise ValueError('NULL pix') - self._pix = ffi.gc(pix, Pix._pix_destroy) + self._pix = ffi.gc(pix, Pix._destroy) def __repr__(self): if self._pix: @@ -191,12 +192,18 @@ class Pix: cdata_bytes = ffi.new('char[]', state['data']) cdata_uint32 = ffi.cast('l_uint32 *', cdata_bytes) - pix = lept.pixDeserializeFromMemory( - cdata_uint32, len(state['data'])) + pix = lept.pixDeserializeFromMemory(cdata_uint32, len(state['data'])) Pix.__init__(self, pix) def __eq__(self, other): - return self.__getstate__() == other.__getstate__() + if not isinstance(other, Pix): + return NotImplemented + same = ffi.new('l_int32 *', 0) + with _LeptonicaErrorTrap(): + err = lept.pixEqual(self._pix, other._pix, same) + if err: + raise TypeError() + return bool(same[0]) @property def width(self): @@ -488,26 +495,26 @@ class Pix: return Pix(lept.pixInvert(ffi.NULL, self._pix)) def locate_barcodes(self): - pix = Pix(lept.pixConvertTo8(self._pix, 0)) - pixa_candidates = PixArray(lept.pixExtractBarcodes(pix._pix, 0)) - sarray = lept.pixReadBarcodes(pixa_candidates._pixa, - lept.L_BF_ANY, - lept.L_USE_WIDTHS, - ffi.NULL, - 0) - if not sarray: - return - for n in range(sarray[0].n): - decoded = ffi.string(sarray[0].array[n]).decode() - if decoded.strip() == '': - continue - box = pixa_candidates.get_box(n) - left, top, right, bottom = box.x, box.y, box.x + box.w, box.y + box.h - yield (decoded, (left, top, right, bottom)) + with _LeptonicaErrorTrap(): + pix = Pix(lept.pixConvertTo8(self._pix, 0)) + pixa_candidates = PixArray(lept.pixExtractBarcodes(pix._pix, 0)) + sarray = StringArray(lept.pixReadBarcodes(pixa_candidates._pixa, + lept.L_BF_ANY, + lept.L_USE_WIDTHS, + ffi.NULL, + 0)) + for n, s in enumerate(sarray): + decoded = s.decode() + if s.strip() == '': + continue + box = pixa_candidates.get_box(n) + left, top = box.x, box.y + right, bottom = box.x + box.w, box.y + box.h + yield (decoded, (left, top, right, bottom)) @staticmethod - def _pix_destroy(pix): + def _destroy(pix): p_pix = ffi.new('PIX **', pix) lept.pixDestroy(p_pix) # print('pix destroy ' + repr(pix)) @@ -559,7 +566,7 @@ class PixArray: def __init__(self, pixa): if not pixa: raise ValueError('NULL pixa') - self._pixa = ffi.gc(pixa, PixArray._pixa_destroy) + self._pixa = ffi.gc(pixa, PixArray._destroy) def __len__(self): return self._pixa[0].n @@ -573,7 +580,7 @@ class PixArray: return Box(lept.pixaGetBox(self._pixa, n, lept.L_CLONE)) @staticmethod - def _pixa_destroy(pixa): + def _destroy(pixa): pp = ffi.new('PIXA **', pixa) lept.pixaDestroy(pp) @@ -587,7 +594,7 @@ class Box: def __init__(self, box): if not box: raise ValueError('NULL box') - self._box = ffi.gc(box, Box._box_destroy) + self._box = ffi.gc(box, Box._destroy) def __repr__(self): @@ -613,7 +620,7 @@ class Box: return self._box.h @staticmethod - def _box_destroy(box): + def _destroy(box): p_box = ffi.new('BOX **', box) lept.boxDestroy(p_box) @@ -652,6 +659,31 @@ class BoxArray: lept.boxaDestroy(p_boxa) +class StringArray: + + def __init__(self, sarray): + if not sarray: + raise ValueError('NULL sarray') + self._sarray = ffi.gc(sarray, StringArray._sarray_destroy) + + def __len__(self): + return self._sarray.n + + def __getitem__(self, n): + if 0 <= n < len(self): + return ffi.string(self._sarray.array[n]) + raise IndexError(n) + + def __iter__(self): + for n in range(len(self)): + yield self[n] + + @staticmethod + def _sarray_destroy(sarray): + pp = ffi.new('SARRAY **', sarray) + lept.sarrayDestroy(pp) + + @lru_cache(maxsize=1) def get_leptonica_version(): """Get Leptonica version string. diff --git a/src/ocrmypdf/lib/_leptonica.py b/src/ocrmypdf/lib/_leptonica.py index b26d3777..0bfc8825 100644 --- a/src/ocrmypdf/lib/_leptonica.py +++ b/src/ocrmypdf/lib/_leptonica.py @@ -3,9 +3,9 @@ import _cffi_backend ffi = _cffi_backend.FFI('ocrmypdf.lib._leptonica', _version = 0x2601, - _types = b'\x00\x00\x01\x0D\x00\x01\x00\x03\x00\x00\x00\x0F\x00\x00\x01\x0D\x00\x01\x01\x03\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x01\x0D\x00\x01\x04\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\x01\x0B\x03\x00\x00\x00\x0F\x00\x00\x01\x0D\x00\x01\x05\x03\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x04\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x09\x03\x00\x00\x1A\x11\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x01\x11\x00\x00\x01\x03\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x0D\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x09\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\x09\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x0D\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x09\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\x05\x03\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x0D\x01\x00\x00\x0D\x01\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x13\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x10\x11\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x01\x19\x03\x00\x00\x1C\x01\x00\x00\x00\x0F\x00\x00\x13\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x01\x07\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x01\x07\x03\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x01\x07\x0D\x00\x00\x13\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x87\x11\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x01\x0A\x0D\x00\x00\x00\x0F\x00\x00\x5C\x0D\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x04\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x32\x03\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x9E\x11\x00\x00\x9E\x11\x00\x00\x9E\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x9E\x11\x00\x00\x9E\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x5C\x11\x00\x00\x5C\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x0D\x01\x00\x00\x07\x01\x00\x00\x5C\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x01\x02\x03\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x9E\x11\x00\x00\x9E\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x0D\x01\x00\x00\x1A\x11\x00\x00\x1A\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x01\x18\x03\x00\x00\x7D\x03\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x10\x11\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\xC2\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x10\x11\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x01\x16\x03\x00\x00\xDA\x11\x00\x00\x09\x11\x00\x00\x0D\x01\x00\x00\x00\x0F\x00\x01\x1C\x0D\x00\x00\x23\x11\x00\x00\x00\x0F\x00\x01\x1C\x0D\x00\x00\x04\x03\x00\x00\x00\x0F\x00\x01\x1C\x0D\x00\x00\xC2\x11\x00\x00\x00\x0F\x00\x01\x1C\x0D\x00\x00\x1A\x11\x00\x00\x00\x0F\x00\x01\x1C\x0D\x00\x00\x13\x03\x00\x00\x00\x0F\x00\x01\x1C\x0D\x00\x01\x1C\x03\x00\x00\x00\x0F\x00\x00\x00\x09\x00\x00\x01\x09\x00\x01\x03\x03\x00\x00\x02\x09\x00\x00\x03\x09\x00\x00\x05\x09\x00\x00\x04\x09\x00\x01\x08\x03\x00\x00\x06\x09\x00\x01\x0A\x03\x00\x01\x0B\x03\x00\x00\x02\x01\x00\x00\x0E\x01\x00\x00\x00\x0B\x00\x00\x01\x0B\x00\x00\x02\x0B\x00\x00\x03\x0B\x00\x00\x04\x0B\x00\x00\x0B\x01\x00\x00\x05\x01\x00\x00\x03\x01\x00\x01\x06\x03\x00\x01\x17\x03\x00\x00\x04\x01\x00\x01\x19\x03\x00\x00\x08\x01\x00\x00\x0C\x01\x00\x00\x06\x01\x00\x00\x00\x01', - _globals = (b'\xFF\xFF\xFF\x0BL_BF_ANY',1,b'\xFF\xFF\xFF\x0BL_BF_CODABAR',9,b'\xFF\xFF\xFF\x0BL_BF_CODE128',2,b'\xFF\xFF\xFF\x0BL_BF_CODE2OF5',5,b'\xFF\xFF\xFF\x0BL_BF_CODE39',7,b'\xFF\xFF\xFF\x0BL_BF_CODE93',8,b'\xFF\xFF\xFF\x0BL_BF_CODEI2OF5',6,b'\xFF\xFF\xFF\x0BL_BF_EAN13',4,b'\xFF\xFF\xFF\x0BL_BF_EAN8',3,b'\xFF\xFF\xFF\x0BL_BF_UNKNOWN',0,b'\xFF\xFF\xFF\x0BL_BF_UPCA',10,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_DEFAULT_ENCODE',0,b'\xFF\xFF\xFF\x0BL_FLATE_ENCODE',3,b'\xFF\xFF\xFF\x0BL_G4_ENCODE',2,b'\xFF\xFF\xFF\x0BL_INSERT',0,b'\xFF\xFF\xFF\x0BL_JP2K_ENCODE',4,b'\xFF\xFF\xFF\x0BL_JPEG_ENCODE',1,b'\xFF\xFF\xFF\x0BL_NOCOPY',0,b'\xFF\xFF\xFF\x0BL_USE_WIDTHS',1,b'\xFF\xFF\xFF\x0BL_USE_WINDOWS',2,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\x00\x23boxClone',0,b'\x00\x00\xEE\x23boxDestroy',0,b'\x00\x00\xF1\x23boxaDestroy',0,b'\x00\x00\x03\x23boxaGetBox',0,b'\x00\x00\x91\x23getLeptonicaVersion',0,b'\x00\x00\xF4\x23l_CIDataDestroy',0,b'\x00\x00\xDC\x23l_generateCIDataForPdf',0,b'\x00\x00\xFD\x23lept_free',0,b'\x00\x00\x93\x23makePixelSumTab8',0,b'\x00\x00\x29\x23pixAnd',0,b'\x00\x00\x36\x23pixBackgroundNorm',0,b'\x00\x00\x2E\x23pixCleanBackgroundToWhite',0,b'\x00\x00\x20\x23pixClipRectangle',0,b'\x00\x00\xC4\x23pixColorFraction',0,b'\x00\x00\x6C\x23pixColorMagnitude',0,b'\x00\x00\x1D\x23pixConvertRGBToLuminance',0,b'\x00\x00\x63\x23pixConvertTo8',0,b'\x00\x00\x9B\x23pixCorrelationBinary',0,b'\x00\x00\xB0\x23pixCountPixels',0,b'\x00\x00\x7B\x23pixDeserializeFromMemory',0,b'\x00\x00\x63\x23pixDeskew',0,b'\x00\x00\xF7\x23pixDestroy',0,b'\x00\x00\x1D\x23pixEndianByteSwapNew',0,b'\x00\x00\x7F\x23pixExtractBarcodes',0,b'\x00\x00\x08\x23pixFindPageForeground',0,b'\x00\x00\xAB\x23pixFindSkew',0,b'\x00\x00\x42\x23pixGammaTRC',0,b'\x00\x00\xBD\x23pixGenerateCIData',0,b'\x00\x00\xA0\x23pixGetAverageMaskedRGB',0,b'\x00\x00\x49\x23pixGlobalNormRGB',0,b'\x00\x00\x25\x23pixInvert',0,b'\x00\x00\x17\x23pixLocateBarcodes',0,b'\x00\x00\x67\x23pixMaskOverColorPixels',0,b'\x00\x00\xB5\x23pixNumSignificantGrayColors',0,b'\x00\x00\xCD\x23pixOtsuAdaptiveThreshold',0,b'\x00\x00\x51\x23pixOtsuThreshOnBackgroundNorm',0,b'\x00\x00\x83\x23pixProcessBarcodes',0,b'\x00\x00\x78\x23pixRead',0,b'\x00\x00\x8A\x23pixReadBarcodes',0,b'\x00\x00\x63\x23pixRemoveColormap',0,b'\x00\x00\x67\x23pixRemoveColormapGeneral',0,b'\x00\x00\x95\x23pixRenderBoxa',0,b'\x00\x00\x25\x23pixRotate180',0,b'\x00\x00\x63\x23pixRotateOrth',0,b'\x00\x00\x5E\x23pixScale',0,b'\x00\x00\xD7\x23pixSerializeToMemory',0,b'\x00\x00\xE2\x23pixWriteImpliedFormat',0,b'\x00\x00\xE8\x23pixWriteMemPng',0,b'\x00\x00\xFA\x23pixaDestroy',0,b'\x00\x00\x12\x23pixaGetBox',0,b'\x00\x00\x73\x23pixaGetPix',0), - _struct_unions = ((b'\x00\x00\x01\x00\x00\x00\x00\x02Box',b'\x00\x00\x05\x11x',b'\x00\x00\x05\x11y',b'\x00\x00\x05\x11w',b'\x00\x00\x05\x11h',b'\x00\x01\x19\x11refcount'),(b'\x00\x00\x01\x01\x00\x00\x00\x02Boxa',b'\x00\x00\x05\x11n',b'\x00\x00\x05\x11nalloc',b'\x00\x01\x19\x11refcount',b'\x00\x00\x23\x11box'),(b'\x00\x00\x01\x03\x00\x00\x00\x02L_Compressed_Data',b'\x00\x00\x05\x11type',b'\x00\x01\x16\x11datacomp',b'\x00\x00\x7D\x11nbytescomp',b'\x00\x01\x0A\x11data85',b'\x00\x00\x7D\x11nbytes85',b'\x00\x01\x0A\x11cmapdata85',b'\x00\x01\x0A\x11cmapdatahex',b'\x00\x00\x05\x11ncolors',b'\x00\x00\x05\x11w',b'\x00\x00\x05\x11h',b'\x00\x00\x05\x11bps',b'\x00\x00\x05\x11spp',b'\x00\x00\x05\x11minisblack',b'\x00\x00\x05\x11predictor',b'\x00\x00\x7D\x11nbytes',b'\x00\x00\x05\x11res'),(b'\x00\x00\x01\x04\x00\x00\x00\x02Pix',b'\x00\x01\x19\x11w',b'\x00\x01\x19\x11h',b'\x00\x01\x19\x11d',b'\x00\x01\x19\x11spp',b'\x00\x01\x19\x11wpl',b'\x00\x01\x19\x11refcount',b'\x00\x00\x05\x11xres',b'\x00\x00\x05\x11yres',b'\x00\x00\x05\x11informat',b'\x00\x00\x05\x11special',b'\x00\x01\x0A\x11text',b'\x00\x01\x15\x11colormap',b'\x00\x01\x18\x11data'),(b'\x00\x00\x01\x06\x00\x00\x00\x02PixColormap',b'\x00\x00\xFE\x11array',b'\x00\x00\x05\x11depth',b'\x00\x00\x05\x11nalloc',b'\x00\x00\x05\x11n'),(b'\x00\x00\x01\x05\x00\x00\x00\x02Pixa',b'\x00\x00\x05\x11n',b'\x00\x00\x05\x11nalloc',b'\x00\x01\x19\x11refcount',b'\x00\x00\x1A\x11pix',b'\x00\x00\x04\x11boxa'),(b'\x00\x00\x01\x08\x00\x00\x00\x02Sarray',b'\x00\x00\x05\x11nalloc',b'\x00\x00\x05\x11n',b'\x00\x00\x05\x11refcount',b'\x00\x01\x09\x11array')), - _enums = (b'\x00\x00\x01\x0D\x00\x00\x00\x16$1\x00L_DEFAULT_ENCODE,L_JPEG_ENCODE,L_G4_ENCODE,L_FLATE_ENCODE,L_JP2K_ENCODE',b'\x00\x00\x01\x0E\x00\x00\x00\x16$2\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\x01\x0F\x00\x00\x00\x16$3\x00L_NOCOPY,L_INSERT,L_COPY,L_CLONE,L_COPY_CLONE',b'\x00\x00\x01\x10\x00\x00\x00\x16$4\x00L_USE_WIDTHS,L_USE_WINDOWS',b'\x00\x00\x01\x11\x00\x00\x00\x16$5\x00L_BF_UNKNOWN,L_BF_ANY,L_BF_CODE128,L_BF_EAN8,L_BF_EAN13,L_BF_CODE2OF5,L_BF_CODEI2OF5,L_BF_CODE39,L_BF_CODE93,L_BF_CODABAR,L_BF_UPCA'), - _typenames = (b'\x00\x00\x01\x00BOX',b'\x00\x00\x01\x01BOXA',b'\x00\x00\x01\x03L_COMP_DATA',b'\x00\x00\x01\x04PIX',b'\x00\x00\x01\x05PIXA',b'\x00\x00\x01\x06PIXCMAP',b'\x00\x00\x01\x08SARRAY',b'\x00\x00\x00\x32l_float32',b'\x00\x00\x01\x0Cl_float64',b'\x00\x00\x01\x13l_int16',b'\x00\x00\x00\x05l_int32',b'\x00\x00\x01\x12l_int64',b'\x00\x00\x01\x14l_int8',b'\x00\x00\x00\x05l_ok',b'\x00\x00\x01\x1Bl_uint16',b'\x00\x00\x01\x19l_uint32',b'\x00\x00\x01\x1Al_uint64',b'\x00\x00\x01\x17l_uint8'), + _types = b'\x00\x00\x01\x0D\x00\x01\x0B\x03\x00\x00\x00\x0F\x00\x00\x01\x0D\x00\x01\x0C\x03\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x01\x0D\x00\x01\x0F\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\x01\x16\x03\x00\x00\x00\x0F\x00\x00\x01\x0D\x00\x01\x10\x03\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x04\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x09\x03\x00\x00\x1A\x11\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x01\x11\x00\x00\x01\x03\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x0D\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x09\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\x09\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x0D\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x09\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\x05\x03\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x0D\x01\x00\x00\x0D\x01\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x13\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x10\x11\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x01\x25\x03\x00\x00\x1C\x01\x00\x00\x00\x0F\x00\x00\x13\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x01\x12\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x01\x12\x03\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x01\x12\x0D\x00\x00\x13\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x87\x11\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x01\x15\x0D\x00\x00\x00\x0F\x00\x00\x5C\x0D\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x04\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x32\x03\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x5C\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x9E\x11\x00\x00\x9E\x11\x00\x00\x9E\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x9E\x11\x00\x00\x9E\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x5C\x11\x00\x00\x5C\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x0D\x01\x00\x00\x07\x01\x00\x00\x5C\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x01\x0D\x03\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x9E\x11\x00\x00\x9E\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x0D\x01\x00\x00\x1A\x11\x00\x00\x1A\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x01\x24\x03\x00\x00\x7D\x03\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x10\x11\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\xC7\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x10\x11\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x01\x22\x03\x00\x00\xDF\x11\x00\x00\x09\x11\x00\x00\x0D\x01\x00\x00\x00\x0F\x00\x01\x28\x0D\x00\x00\x23\x11\x00\x00\x00\x0F\x00\x01\x28\x0D\x00\x00\x04\x03\x00\x00\x00\x0F\x00\x01\x28\x0D\x00\x00\xC7\x11\x00\x00\x00\x0F\x00\x01\x28\x0D\x00\x00\x1A\x11\x00\x00\x00\x0F\x00\x01\x28\x0D\x00\x00\x13\x03\x00\x00\x00\x0F\x00\x01\x28\x0D\x00\x00\x87\x11\x00\x00\x00\x0F\x00\x01\x28\x0D\x00\x01\x28\x03\x00\x00\x00\x0F\x00\x00\x00\x09\x00\x00\x01\x09\x00\x01\x0E\x03\x00\x00\x02\x09\x00\x00\x03\x09\x00\x00\x05\x09\x00\x00\x04\x09\x00\x01\x13\x03\x00\x00\x06\x09\x00\x01\x15\x03\x00\x01\x16\x03\x00\x00\x02\x01\x00\x00\x0E\x01\x00\x00\x00\x0B\x00\x00\x01\x0B\x00\x00\x02\x0B\x00\x00\x03\x0B\x00\x00\x04\x0B\x00\x00\x05\x0B\x00\x00\x0B\x01\x00\x00\x05\x01\x00\x00\x03\x01\x00\x01\x11\x03\x00\x01\x23\x03\x00\x00\x04\x01\x00\x01\x25\x03\x00\x00\x08\x01\x00\x00\x0C\x01\x00\x00\x06\x01\x00\x00\x00\x01', + _globals = (b'\xFF\xFF\xFF\x0BL_BF_ANY',1,b'\xFF\xFF\xFF\x0BL_BF_CODABAR',9,b'\xFF\xFF\xFF\x0BL_BF_CODE128',2,b'\xFF\xFF\xFF\x0BL_BF_CODE2OF5',5,b'\xFF\xFF\xFF\x0BL_BF_CODE39',7,b'\xFF\xFF\xFF\x0BL_BF_CODE93',8,b'\xFF\xFF\xFF\x0BL_BF_CODEI2OF5',6,b'\xFF\xFF\xFF\x0BL_BF_EAN13',4,b'\xFF\xFF\xFF\x0BL_BF_EAN8',3,b'\xFF\xFF\xFF\x0BL_BF_UNKNOWN',0,b'\xFF\xFF\xFF\x0BL_BF_UPCA',10,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_DEFAULT_ENCODE',0,b'\xFF\xFF\xFF\x0BL_FLATE_ENCODE',3,b'\xFF\xFF\xFF\x0BL_G4_ENCODE',2,b'\xFF\xFF\xFF\x0BL_INSERT',0,b'\xFF\xFF\xFF\x0BL_JP2K_ENCODE',4,b'\xFF\xFF\xFF\x0BL_JPEG_ENCODE',1,b'\xFF\xFF\xFF\x0BL_NOCOPY',0,b'\xFF\xFF\xFF\x0BL_SEVERITY_ALL',1,b'\xFF\xFF\xFF\x0BL_SEVERITY_DEBUG',2,b'\xFF\xFF\xFF\x0BL_SEVERITY_ERROR',5,b'\xFF\xFF\xFF\x0BL_SEVERITY_EXTERNAL',0,b'\xFF\xFF\xFF\x0BL_SEVERITY_INFO',3,b'\xFF\xFF\xFF\x0BL_SEVERITY_NONE',6,b'\xFF\xFF\xFF\x0BL_SEVERITY_WARNING',4,b'\xFF\xFF\xFF\x0BL_USE_WIDTHS',1,b'\xFF\xFF\xFF\x0BL_USE_WINDOWS',2,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\x00\x23boxClone',0,b'\x00\x00\xF6\x23boxDestroy',0,b'\x00\x00\xF9\x23boxaDestroy',0,b'\x00\x00\x03\x23boxaGetBox',0,b'\x00\x00\x91\x23getLeptonicaVersion',0,b'\x00\x00\xFC\x23l_CIDataDestroy',0,b'\x00\x00\xE1\x23l_generateCIDataForPdf',0,b'\x00\x01\x08\x23lept_free',0,b'\x00\x00\x93\x23makePixelSumTab8',0,b'\x00\x00\x29\x23pixAnd',0,b'\x00\x00\x36\x23pixBackgroundNorm',0,b'\x00\x00\x2E\x23pixCleanBackgroundToWhite',0,b'\x00\x00\x20\x23pixClipRectangle',0,b'\x00\x00\xC9\x23pixColorFraction',0,b'\x00\x00\x6C\x23pixColorMagnitude',0,b'\x00\x00\x1D\x23pixConvertRGBToLuminance',0,b'\x00\x00\x63\x23pixConvertTo8',0,b'\x00\x00\x9B\x23pixCorrelationBinary',0,b'\x00\x00\xB5\x23pixCountPixels',0,b'\x00\x00\x7B\x23pixDeserializeFromMemory',0,b'\x00\x00\x63\x23pixDeskew',0,b'\x00\x00\xFF\x23pixDestroy',0,b'\x00\x00\x1D\x23pixEndianByteSwapNew',0,b'\x00\x00\xA0\x23pixEqual',0,b'\x00\x00\x7F\x23pixExtractBarcodes',0,b'\x00\x00\x08\x23pixFindPageForeground',0,b'\x00\x00\xB0\x23pixFindSkew',0,b'\x00\x00\x42\x23pixGammaTRC',0,b'\x00\x00\xC2\x23pixGenerateCIData',0,b'\x00\x00\xA5\x23pixGetAverageMaskedRGB',0,b'\x00\x00\x49\x23pixGlobalNormRGB',0,b'\x00\x00\x25\x23pixInvert',0,b'\x00\x00\x17\x23pixLocateBarcodes',0,b'\x00\x00\x67\x23pixMaskOverColorPixels',0,b'\x00\x00\xBA\x23pixNumSignificantGrayColors',0,b'\x00\x00\xD2\x23pixOtsuAdaptiveThreshold',0,b'\x00\x00\x51\x23pixOtsuThreshOnBackgroundNorm',0,b'\x00\x00\x83\x23pixProcessBarcodes',0,b'\x00\x00\x78\x23pixRead',0,b'\x00\x00\x8A\x23pixReadBarcodes',0,b'\x00\x00\x63\x23pixRemoveColormap',0,b'\x00\x00\x67\x23pixRemoveColormapGeneral',0,b'\x00\x00\x95\x23pixRenderBoxa',0,b'\x00\x00\x25\x23pixRotate180',0,b'\x00\x00\x63\x23pixRotateOrth',0,b'\x00\x00\x5E\x23pixScale',0,b'\x00\x00\xDC\x23pixSerializeToMemory',0,b'\x00\x00\xE7\x23pixWriteImpliedFormat',0,b'\x00\x00\xF0\x23pixWriteMemPng',0,b'\x00\x01\x02\x23pixaDestroy',0,b'\x00\x00\x12\x23pixaGetBox',0,b'\x00\x00\x73\x23pixaGetPix',0,b'\x00\x01\x05\x23sarrayDestroy',0,b'\x00\x00\xED\x23setMsgSeverity',0), + _struct_unions = ((b'\x00\x00\x01\x0B\x00\x00\x00\x02Box',b'\x00\x00\x05\x11x',b'\x00\x00\x05\x11y',b'\x00\x00\x05\x11w',b'\x00\x00\x05\x11h',b'\x00\x01\x25\x11refcount'),(b'\x00\x00\x01\x0C\x00\x00\x00\x02Boxa',b'\x00\x00\x05\x11n',b'\x00\x00\x05\x11nalloc',b'\x00\x01\x25\x11refcount',b'\x00\x00\x23\x11box'),(b'\x00\x00\x01\x0E\x00\x00\x00\x02L_Compressed_Data',b'\x00\x00\x05\x11type',b'\x00\x01\x22\x11datacomp',b'\x00\x00\x7D\x11nbytescomp',b'\x00\x01\x15\x11data85',b'\x00\x00\x7D\x11nbytes85',b'\x00\x01\x15\x11cmapdata85',b'\x00\x01\x15\x11cmapdatahex',b'\x00\x00\x05\x11ncolors',b'\x00\x00\x05\x11w',b'\x00\x00\x05\x11h',b'\x00\x00\x05\x11bps',b'\x00\x00\x05\x11spp',b'\x00\x00\x05\x11minisblack',b'\x00\x00\x05\x11predictor',b'\x00\x00\x7D\x11nbytes',b'\x00\x00\x05\x11res'),(b'\x00\x00\x01\x0F\x00\x00\x00\x02Pix',b'\x00\x01\x25\x11w',b'\x00\x01\x25\x11h',b'\x00\x01\x25\x11d',b'\x00\x01\x25\x11spp',b'\x00\x01\x25\x11wpl',b'\x00\x01\x25\x11refcount',b'\x00\x00\x05\x11xres',b'\x00\x00\x05\x11yres',b'\x00\x00\x05\x11informat',b'\x00\x00\x05\x11special',b'\x00\x01\x15\x11text',b'\x00\x01\x21\x11colormap',b'\x00\x01\x24\x11data'),(b'\x00\x00\x01\x11\x00\x00\x00\x02PixColormap',b'\x00\x01\x09\x11array',b'\x00\x00\x05\x11depth',b'\x00\x00\x05\x11nalloc',b'\x00\x00\x05\x11n'),(b'\x00\x00\x01\x10\x00\x00\x00\x02Pixa',b'\x00\x00\x05\x11n',b'\x00\x00\x05\x11nalloc',b'\x00\x01\x25\x11refcount',b'\x00\x00\x1A\x11pix',b'\x00\x00\x04\x11boxa'),(b'\x00\x00\x01\x13\x00\x00\x00\x02Sarray',b'\x00\x00\x05\x11nalloc',b'\x00\x00\x05\x11n',b'\x00\x00\x05\x11refcount',b'\x00\x01\x14\x11array')), + _enums = (b'\x00\x00\x01\x18\x00\x00\x00\x16$1\x00L_DEFAULT_ENCODE,L_JPEG_ENCODE,L_G4_ENCODE,L_FLATE_ENCODE,L_JP2K_ENCODE',b'\x00\x00\x01\x19\x00\x00\x00\x16$2\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\x01\x1A\x00\x00\x00\x16$3\x00L_NOCOPY,L_INSERT,L_COPY,L_CLONE,L_COPY_CLONE',b'\x00\x00\x01\x1B\x00\x00\x00\x16$4\x00L_USE_WIDTHS,L_USE_WINDOWS',b'\x00\x00\x01\x1C\x00\x00\x00\x16$5\x00L_BF_UNKNOWN,L_BF_ANY,L_BF_CODE128,L_BF_EAN8,L_BF_EAN13,L_BF_CODE2OF5,L_BF_CODEI2OF5,L_BF_CODE39,L_BF_CODE93,L_BF_CODABAR,L_BF_UPCA',b'\x00\x00\x01\x1D\x00\x00\x00\x16$6\x00L_SEVERITY_EXTERNAL,L_SEVERITY_ALL,L_SEVERITY_DEBUG,L_SEVERITY_INFO,L_SEVERITY_WARNING,L_SEVERITY_ERROR,L_SEVERITY_NONE'), + _typenames = (b'\x00\x00\x01\x0BBOX',b'\x00\x00\x01\x0CBOXA',b'\x00\x00\x01\x0EL_COMP_DATA',b'\x00\x00\x01\x0FPIX',b'\x00\x00\x01\x10PIXA',b'\x00\x00\x01\x11PIXCMAP',b'\x00\x00\x01\x13SARRAY',b'\x00\x00\x00\x32l_float32',b'\x00\x00\x01\x17l_float64',b'\x00\x00\x01\x1Fl_int16',b'\x00\x00\x00\x05l_int32',b'\x00\x00\x01\x1El_int64',b'\x00\x00\x01\x20l_int8',b'\x00\x00\x00\x05l_ok',b'\x00\x00\x01\x27l_uint16',b'\x00\x00\x01\x25l_uint32',b'\x00\x00\x01\x26l_uint64',b'\x00\x00\x01\x23l_uint8'), ) diff --git a/src/ocrmypdf/lib/compile_leptonica.py b/src/ocrmypdf/lib/compile_leptonica.py index 537ec1d5..6a3c088c 100644 --- a/src/ocrmypdf/lib/compile_leptonica.py +++ b/src/ocrmypdf/lib/compile_leptonica.py @@ -174,6 +174,16 @@ enum { L_BF_UPCA = 10 /*!< decode with UPC A format */ }; +enum { + L_SEVERITY_EXTERNAL = 0, /* Get the severity from the environment */ + L_SEVERITY_ALL = 1, /* Lowest severity: print all messages */ + L_SEVERITY_DEBUG = 2, /* Print debugging and higher messages */ + L_SEVERITY_INFO = 3, /* Print informational and higher messages */ + L_SEVERITY_WARNING = 4, /* Print warning and higher messages */ + L_SEVERITY_ERROR = 5, /* Print error and higher messages */ + L_SEVERITY_NONE = 6 /* Highest severity: print no messages */ +}; + """) ffibuilder.cdef(""" @@ -189,6 +199,11 @@ pixWriteMemPng(l_uint8 **pdata, void pixDestroy ( PIX **ppix ); +l_ok +pixEqual(PIX *pix1, + PIX *pix2, + l_int32 *psame); + PIX * pixEndianByteSwapNew(PIX *pixs); @@ -401,8 +416,14 @@ pixRenderBoxa ( PIX *pix, BOXA *boxa, l_int32 width, l_int32 op ); void l_CIDataDestroy(L_COMP_DATA **pcid); +void +sarrayDestroy(SARRAY **psa); + void lept_free(void *ptr); + +l_int32 +setMsgSeverity(l_int32 newsev); """) From 501ce726e719d789963cb7d8be74320b95e1ca5e Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Tue, 6 Nov 2018 11:15:02 -0800 Subject: [PATCH 51/68] Fix two failing tests --- tests/test_lept.py | 6 +++--- tests/test_pdfinfo.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/test_lept.py b/tests/test_lept.py index 67d47bc5..04280b83 100644 --- a/tests/test_lept.py +++ b/tests/test_lept.py @@ -68,8 +68,8 @@ def test_crop(resources): assert foreground.width < pix.width -def test_clean_bg(crom_pix): - pix, _ = crom_pix +def test_clean_bg(resources): + pix = lept.Pix.open(resources / 'congress.jpg') imbg = pix.clean_background_to_white() @@ -86,4 +86,4 @@ def test_leptonica_compile(tmpdir): # Compile the library but build it somewhere that won't interfere with # existing compiled library. Also compile in API mode so that we test # the interfaces, even though we use it ABI mode. - ffibuilder.compile(tmpdir=tmpdir, target=(tmpdir /'lepttest.*')) + ffibuilder.compile(tmpdir=tmpdir, target=(tmpdir / 'lepttest.*')) diff --git a/tests/test_pdfinfo.py b/tests/test_pdfinfo.py index 51bdf820..85f51311 100644 --- a/tests/test_pdfinfo.py +++ b/tests/test_pdfinfo.py @@ -151,7 +151,7 @@ def test_pickle(resources): def test_regex(): - rx = pdfinfo.regex_remove_char_tags + rx = pdfinfo.ghosttext.regex_remove_char_tags must_match = [ b'', From dd0174551932fe70a307c29d14928d9ba0064a5c Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Tue, 6 Nov 2018 19:31:06 -0800 Subject: [PATCH 52/68] Leptonica: add masked threshold fn --- src/ocrmypdf/leptonica.py | 24 ++++++++++++++++++++++++ src/ocrmypdf/lib/_leptonica.py | 10 +++++----- src/ocrmypdf/lib/compile_leptonica.py | 12 ++++++++++++ 3 files changed, 41 insertions(+), 5 deletions(-) diff --git a/src/ocrmypdf/leptonica.py b/src/ocrmypdf/leptonica.py index cf184005..e7b46904 100644 --- a/src/ocrmypdf/leptonica.py +++ b/src/ocrmypdf/leptonica.py @@ -400,6 +400,30 @@ class Pix: return None return Pix(thresh_pix) + def masked_threshold_on_background_norm( + self, mask=None, tile_size=(10, 15), thresh=100, mincount=50, + kernel_size=(2, 2), scorefract=0.1): + with _LeptonicaErrorTrap(): + sx, sy = tile_size + smoothx, smoothy = kernel_size + if mask is None: + mask = ffi.NULL + if isinstance(mask, Pix): + mask = mask._pix + + new_pix = lept.pixMaskedThreshOnBackgroundNorm( + self._pix, + mask, + sx, sy, + thresh, mincount, + smoothx, smoothy, + scorefract, + ffi.NULL + ) + if new_pix == ffi.NULL: + return None + return Pix(new_pix) + def crop_to_foreground( self, threshold=128, mindist=70, erasedist=30, pagenum=0, showmorph=0, display=0, pdfdir=ffi.NULL): diff --git a/src/ocrmypdf/lib/_leptonica.py b/src/ocrmypdf/lib/_leptonica.py index 0bfc8825..c4bc4ccd 100644 --- a/src/ocrmypdf/lib/_leptonica.py +++ b/src/ocrmypdf/lib/_leptonica.py @@ -3,9 +3,9 @@ import _cffi_backend ffi = _cffi_backend.FFI('ocrmypdf.lib._leptonica', _version = 0x2601, - _types = b'\x00\x00\x01\x0D\x00\x01\x0B\x03\x00\x00\x00\x0F\x00\x00\x01\x0D\x00\x01\x0C\x03\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x01\x0D\x00\x01\x0F\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\x01\x16\x03\x00\x00\x00\x0F\x00\x00\x01\x0D\x00\x01\x10\x03\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x04\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x09\x03\x00\x00\x1A\x11\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x01\x11\x00\x00\x01\x03\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x0D\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x09\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\x09\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x0D\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x09\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\x05\x03\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x0D\x01\x00\x00\x0D\x01\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x13\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x10\x11\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x01\x25\x03\x00\x00\x1C\x01\x00\x00\x00\x0F\x00\x00\x13\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x01\x12\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x01\x12\x03\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x01\x12\x0D\x00\x00\x13\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x87\x11\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x01\x15\x0D\x00\x00\x00\x0F\x00\x00\x5C\x0D\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x04\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x32\x03\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x5C\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x9E\x11\x00\x00\x9E\x11\x00\x00\x9E\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x9E\x11\x00\x00\x9E\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x5C\x11\x00\x00\x5C\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x0D\x01\x00\x00\x07\x01\x00\x00\x5C\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x01\x0D\x03\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x9E\x11\x00\x00\x9E\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x0D\x01\x00\x00\x1A\x11\x00\x00\x1A\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x01\x24\x03\x00\x00\x7D\x03\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x10\x11\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\xC7\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x10\x11\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x01\x22\x03\x00\x00\xDF\x11\x00\x00\x09\x11\x00\x00\x0D\x01\x00\x00\x00\x0F\x00\x01\x28\x0D\x00\x00\x23\x11\x00\x00\x00\x0F\x00\x01\x28\x0D\x00\x00\x04\x03\x00\x00\x00\x0F\x00\x01\x28\x0D\x00\x00\xC7\x11\x00\x00\x00\x0F\x00\x01\x28\x0D\x00\x00\x1A\x11\x00\x00\x00\x0F\x00\x01\x28\x0D\x00\x00\x13\x03\x00\x00\x00\x0F\x00\x01\x28\x0D\x00\x00\x87\x11\x00\x00\x00\x0F\x00\x01\x28\x0D\x00\x01\x28\x03\x00\x00\x00\x0F\x00\x00\x00\x09\x00\x00\x01\x09\x00\x01\x0E\x03\x00\x00\x02\x09\x00\x00\x03\x09\x00\x00\x05\x09\x00\x00\x04\x09\x00\x01\x13\x03\x00\x00\x06\x09\x00\x01\x15\x03\x00\x01\x16\x03\x00\x00\x02\x01\x00\x00\x0E\x01\x00\x00\x00\x0B\x00\x00\x01\x0B\x00\x00\x02\x0B\x00\x00\x03\x0B\x00\x00\x04\x0B\x00\x00\x05\x0B\x00\x00\x0B\x01\x00\x00\x05\x01\x00\x00\x03\x01\x00\x01\x11\x03\x00\x01\x23\x03\x00\x00\x04\x01\x00\x01\x25\x03\x00\x00\x08\x01\x00\x00\x0C\x01\x00\x00\x06\x01\x00\x00\x00\x01', - _globals = (b'\xFF\xFF\xFF\x0BL_BF_ANY',1,b'\xFF\xFF\xFF\x0BL_BF_CODABAR',9,b'\xFF\xFF\xFF\x0BL_BF_CODE128',2,b'\xFF\xFF\xFF\x0BL_BF_CODE2OF5',5,b'\xFF\xFF\xFF\x0BL_BF_CODE39',7,b'\xFF\xFF\xFF\x0BL_BF_CODE93',8,b'\xFF\xFF\xFF\x0BL_BF_CODEI2OF5',6,b'\xFF\xFF\xFF\x0BL_BF_EAN13',4,b'\xFF\xFF\xFF\x0BL_BF_EAN8',3,b'\xFF\xFF\xFF\x0BL_BF_UNKNOWN',0,b'\xFF\xFF\xFF\x0BL_BF_UPCA',10,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_DEFAULT_ENCODE',0,b'\xFF\xFF\xFF\x0BL_FLATE_ENCODE',3,b'\xFF\xFF\xFF\x0BL_G4_ENCODE',2,b'\xFF\xFF\xFF\x0BL_INSERT',0,b'\xFF\xFF\xFF\x0BL_JP2K_ENCODE',4,b'\xFF\xFF\xFF\x0BL_JPEG_ENCODE',1,b'\xFF\xFF\xFF\x0BL_NOCOPY',0,b'\xFF\xFF\xFF\x0BL_SEVERITY_ALL',1,b'\xFF\xFF\xFF\x0BL_SEVERITY_DEBUG',2,b'\xFF\xFF\xFF\x0BL_SEVERITY_ERROR',5,b'\xFF\xFF\xFF\x0BL_SEVERITY_EXTERNAL',0,b'\xFF\xFF\xFF\x0BL_SEVERITY_INFO',3,b'\xFF\xFF\xFF\x0BL_SEVERITY_NONE',6,b'\xFF\xFF\xFF\x0BL_SEVERITY_WARNING',4,b'\xFF\xFF\xFF\x0BL_USE_WIDTHS',1,b'\xFF\xFF\xFF\x0BL_USE_WINDOWS',2,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\x00\x23boxClone',0,b'\x00\x00\xF6\x23boxDestroy',0,b'\x00\x00\xF9\x23boxaDestroy',0,b'\x00\x00\x03\x23boxaGetBox',0,b'\x00\x00\x91\x23getLeptonicaVersion',0,b'\x00\x00\xFC\x23l_CIDataDestroy',0,b'\x00\x00\xE1\x23l_generateCIDataForPdf',0,b'\x00\x01\x08\x23lept_free',0,b'\x00\x00\x93\x23makePixelSumTab8',0,b'\x00\x00\x29\x23pixAnd',0,b'\x00\x00\x36\x23pixBackgroundNorm',0,b'\x00\x00\x2E\x23pixCleanBackgroundToWhite',0,b'\x00\x00\x20\x23pixClipRectangle',0,b'\x00\x00\xC9\x23pixColorFraction',0,b'\x00\x00\x6C\x23pixColorMagnitude',0,b'\x00\x00\x1D\x23pixConvertRGBToLuminance',0,b'\x00\x00\x63\x23pixConvertTo8',0,b'\x00\x00\x9B\x23pixCorrelationBinary',0,b'\x00\x00\xB5\x23pixCountPixels',0,b'\x00\x00\x7B\x23pixDeserializeFromMemory',0,b'\x00\x00\x63\x23pixDeskew',0,b'\x00\x00\xFF\x23pixDestroy',0,b'\x00\x00\x1D\x23pixEndianByteSwapNew',0,b'\x00\x00\xA0\x23pixEqual',0,b'\x00\x00\x7F\x23pixExtractBarcodes',0,b'\x00\x00\x08\x23pixFindPageForeground',0,b'\x00\x00\xB0\x23pixFindSkew',0,b'\x00\x00\x42\x23pixGammaTRC',0,b'\x00\x00\xC2\x23pixGenerateCIData',0,b'\x00\x00\xA5\x23pixGetAverageMaskedRGB',0,b'\x00\x00\x49\x23pixGlobalNormRGB',0,b'\x00\x00\x25\x23pixInvert',0,b'\x00\x00\x17\x23pixLocateBarcodes',0,b'\x00\x00\x67\x23pixMaskOverColorPixels',0,b'\x00\x00\xBA\x23pixNumSignificantGrayColors',0,b'\x00\x00\xD2\x23pixOtsuAdaptiveThreshold',0,b'\x00\x00\x51\x23pixOtsuThreshOnBackgroundNorm',0,b'\x00\x00\x83\x23pixProcessBarcodes',0,b'\x00\x00\x78\x23pixRead',0,b'\x00\x00\x8A\x23pixReadBarcodes',0,b'\x00\x00\x63\x23pixRemoveColormap',0,b'\x00\x00\x67\x23pixRemoveColormapGeneral',0,b'\x00\x00\x95\x23pixRenderBoxa',0,b'\x00\x00\x25\x23pixRotate180',0,b'\x00\x00\x63\x23pixRotateOrth',0,b'\x00\x00\x5E\x23pixScale',0,b'\x00\x00\xDC\x23pixSerializeToMemory',0,b'\x00\x00\xE7\x23pixWriteImpliedFormat',0,b'\x00\x00\xF0\x23pixWriteMemPng',0,b'\x00\x01\x02\x23pixaDestroy',0,b'\x00\x00\x12\x23pixaGetBox',0,b'\x00\x00\x73\x23pixaGetPix',0,b'\x00\x01\x05\x23sarrayDestroy',0,b'\x00\x00\xED\x23setMsgSeverity',0), - _struct_unions = ((b'\x00\x00\x01\x0B\x00\x00\x00\x02Box',b'\x00\x00\x05\x11x',b'\x00\x00\x05\x11y',b'\x00\x00\x05\x11w',b'\x00\x00\x05\x11h',b'\x00\x01\x25\x11refcount'),(b'\x00\x00\x01\x0C\x00\x00\x00\x02Boxa',b'\x00\x00\x05\x11n',b'\x00\x00\x05\x11nalloc',b'\x00\x01\x25\x11refcount',b'\x00\x00\x23\x11box'),(b'\x00\x00\x01\x0E\x00\x00\x00\x02L_Compressed_Data',b'\x00\x00\x05\x11type',b'\x00\x01\x22\x11datacomp',b'\x00\x00\x7D\x11nbytescomp',b'\x00\x01\x15\x11data85',b'\x00\x00\x7D\x11nbytes85',b'\x00\x01\x15\x11cmapdata85',b'\x00\x01\x15\x11cmapdatahex',b'\x00\x00\x05\x11ncolors',b'\x00\x00\x05\x11w',b'\x00\x00\x05\x11h',b'\x00\x00\x05\x11bps',b'\x00\x00\x05\x11spp',b'\x00\x00\x05\x11minisblack',b'\x00\x00\x05\x11predictor',b'\x00\x00\x7D\x11nbytes',b'\x00\x00\x05\x11res'),(b'\x00\x00\x01\x0F\x00\x00\x00\x02Pix',b'\x00\x01\x25\x11w',b'\x00\x01\x25\x11h',b'\x00\x01\x25\x11d',b'\x00\x01\x25\x11spp',b'\x00\x01\x25\x11wpl',b'\x00\x01\x25\x11refcount',b'\x00\x00\x05\x11xres',b'\x00\x00\x05\x11yres',b'\x00\x00\x05\x11informat',b'\x00\x00\x05\x11special',b'\x00\x01\x15\x11text',b'\x00\x01\x21\x11colormap',b'\x00\x01\x24\x11data'),(b'\x00\x00\x01\x11\x00\x00\x00\x02PixColormap',b'\x00\x01\x09\x11array',b'\x00\x00\x05\x11depth',b'\x00\x00\x05\x11nalloc',b'\x00\x00\x05\x11n'),(b'\x00\x00\x01\x10\x00\x00\x00\x02Pixa',b'\x00\x00\x05\x11n',b'\x00\x00\x05\x11nalloc',b'\x00\x01\x25\x11refcount',b'\x00\x00\x1A\x11pix',b'\x00\x00\x04\x11boxa'),(b'\x00\x00\x01\x13\x00\x00\x00\x02Sarray',b'\x00\x00\x05\x11nalloc',b'\x00\x00\x05\x11n',b'\x00\x00\x05\x11refcount',b'\x00\x01\x14\x11array')), - _enums = (b'\x00\x00\x01\x18\x00\x00\x00\x16$1\x00L_DEFAULT_ENCODE,L_JPEG_ENCODE,L_G4_ENCODE,L_FLATE_ENCODE,L_JP2K_ENCODE',b'\x00\x00\x01\x19\x00\x00\x00\x16$2\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\x01\x1A\x00\x00\x00\x16$3\x00L_NOCOPY,L_INSERT,L_COPY,L_CLONE,L_COPY_CLONE',b'\x00\x00\x01\x1B\x00\x00\x00\x16$4\x00L_USE_WIDTHS,L_USE_WINDOWS',b'\x00\x00\x01\x1C\x00\x00\x00\x16$5\x00L_BF_UNKNOWN,L_BF_ANY,L_BF_CODE128,L_BF_EAN8,L_BF_EAN13,L_BF_CODE2OF5,L_BF_CODEI2OF5,L_BF_CODE39,L_BF_CODE93,L_BF_CODABAR,L_BF_UPCA',b'\x00\x00\x01\x1D\x00\x00\x00\x16$6\x00L_SEVERITY_EXTERNAL,L_SEVERITY_ALL,L_SEVERITY_DEBUG,L_SEVERITY_INFO,L_SEVERITY_WARNING,L_SEVERITY_ERROR,L_SEVERITY_NONE'), - _typenames = (b'\x00\x00\x01\x0BBOX',b'\x00\x00\x01\x0CBOXA',b'\x00\x00\x01\x0EL_COMP_DATA',b'\x00\x00\x01\x0FPIX',b'\x00\x00\x01\x10PIXA',b'\x00\x00\x01\x11PIXCMAP',b'\x00\x00\x01\x13SARRAY',b'\x00\x00\x00\x32l_float32',b'\x00\x00\x01\x17l_float64',b'\x00\x00\x01\x1Fl_int16',b'\x00\x00\x00\x05l_int32',b'\x00\x00\x01\x1El_int64',b'\x00\x00\x01\x20l_int8',b'\x00\x00\x00\x05l_ok',b'\x00\x00\x01\x27l_uint16',b'\x00\x00\x01\x25l_uint32',b'\x00\x00\x01\x26l_uint64',b'\x00\x00\x01\x23l_uint8'), + _types = b'\x00\x00\x01\x0D\x00\x01\x17\x03\x00\x00\x00\x0F\x00\x00\x01\x0D\x00\x01\x18\x03\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x01\x0D\x00\x01\x1B\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\x01\x22\x03\x00\x00\x00\x0F\x00\x00\x01\x0D\x00\x01\x1C\x03\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x04\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x09\x03\x00\x00\x1A\x11\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x01\x11\x00\x00\x01\x03\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x0D\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x09\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\x09\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x0D\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x09\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\x0D\x01\x00\x00\x05\x03\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x09\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\x5B\x11\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x0D\x01\x00\x00\x0D\x01\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x13\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x10\x11\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x01\x31\x03\x00\x00\x1C\x01\x00\x00\x00\x0F\x00\x00\x13\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x01\x1E\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x01\x1E\x03\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x01\x1E\x0D\x00\x00\x13\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x93\x11\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x01\x21\x0D\x00\x00\x00\x0F\x00\x00\x5B\x0D\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x04\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x32\x03\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x5B\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\xAA\x11\x00\x00\xAA\x11\x00\x00\xAA\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\xAA\x11\x00\x00\xAA\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x5B\x11\x00\x00\x5B\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x0D\x01\x00\x00\x07\x01\x00\x00\x5B\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x01\x19\x03\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\xAA\x11\x00\x00\xAA\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x0D\x01\x00\x00\x1A\x11\x00\x00\x1A\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x01\x30\x03\x00\x00\x89\x03\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x10\x11\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\xD3\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x10\x11\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x01\x2E\x03\x00\x00\xEB\x11\x00\x00\x09\x11\x00\x00\x0D\x01\x00\x00\x00\x0F\x00\x01\x34\x0D\x00\x00\x23\x11\x00\x00\x00\x0F\x00\x01\x34\x0D\x00\x00\x04\x03\x00\x00\x00\x0F\x00\x01\x34\x0D\x00\x00\xD3\x11\x00\x00\x00\x0F\x00\x01\x34\x0D\x00\x00\x1A\x11\x00\x00\x00\x0F\x00\x01\x34\x0D\x00\x00\x13\x03\x00\x00\x00\x0F\x00\x01\x34\x0D\x00\x00\x93\x11\x00\x00\x00\x0F\x00\x01\x34\x0D\x00\x01\x34\x03\x00\x00\x00\x0F\x00\x00\x00\x09\x00\x00\x01\x09\x00\x01\x1A\x03\x00\x00\x02\x09\x00\x00\x03\x09\x00\x00\x05\x09\x00\x00\x04\x09\x00\x01\x1F\x03\x00\x00\x06\x09\x00\x01\x21\x03\x00\x01\x22\x03\x00\x00\x02\x01\x00\x00\x0E\x01\x00\x00\x00\x0B\x00\x00\x01\x0B\x00\x00\x02\x0B\x00\x00\x03\x0B\x00\x00\x04\x0B\x00\x00\x05\x0B\x00\x00\x0B\x01\x00\x00\x05\x01\x00\x00\x03\x01\x00\x01\x1D\x03\x00\x01\x2F\x03\x00\x00\x04\x01\x00\x01\x31\x03\x00\x00\x08\x01\x00\x00\x0C\x01\x00\x00\x06\x01\x00\x00\x00\x01', + _globals = (b'\xFF\xFF\xFF\x0BL_BF_ANY',1,b'\xFF\xFF\xFF\x0BL_BF_CODABAR',9,b'\xFF\xFF\xFF\x0BL_BF_CODE128',2,b'\xFF\xFF\xFF\x0BL_BF_CODE2OF5',5,b'\xFF\xFF\xFF\x0BL_BF_CODE39',7,b'\xFF\xFF\xFF\x0BL_BF_CODE93',8,b'\xFF\xFF\xFF\x0BL_BF_CODEI2OF5',6,b'\xFF\xFF\xFF\x0BL_BF_EAN13',4,b'\xFF\xFF\xFF\x0BL_BF_EAN8',3,b'\xFF\xFF\xFF\x0BL_BF_UNKNOWN',0,b'\xFF\xFF\xFF\x0BL_BF_UPCA',10,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_DEFAULT_ENCODE',0,b'\xFF\xFF\xFF\x0BL_FLATE_ENCODE',3,b'\xFF\xFF\xFF\x0BL_G4_ENCODE',2,b'\xFF\xFF\xFF\x0BL_INSERT',0,b'\xFF\xFF\xFF\x0BL_JP2K_ENCODE',4,b'\xFF\xFF\xFF\x0BL_JPEG_ENCODE',1,b'\xFF\xFF\xFF\x0BL_NOCOPY',0,b'\xFF\xFF\xFF\x0BL_SEVERITY_ALL',1,b'\xFF\xFF\xFF\x0BL_SEVERITY_DEBUG',2,b'\xFF\xFF\xFF\x0BL_SEVERITY_ERROR',5,b'\xFF\xFF\xFF\x0BL_SEVERITY_EXTERNAL',0,b'\xFF\xFF\xFF\x0BL_SEVERITY_INFO',3,b'\xFF\xFF\xFF\x0BL_SEVERITY_NONE',6,b'\xFF\xFF\xFF\x0BL_SEVERITY_WARNING',4,b'\xFF\xFF\xFF\x0BL_USE_WIDTHS',1,b'\xFF\xFF\xFF\x0BL_USE_WINDOWS',2,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\x00\x23boxClone',0,b'\x00\x01\x02\x23boxDestroy',0,b'\x00\x01\x05\x23boxaDestroy',0,b'\x00\x00\x03\x23boxaGetBox',0,b'\x00\x00\x9D\x23getLeptonicaVersion',0,b'\x00\x01\x08\x23l_CIDataDestroy',0,b'\x00\x00\xED\x23l_generateCIDataForPdf',0,b'\x00\x01\x14\x23lept_free',0,b'\x00\x00\x9F\x23makePixelSumTab8',0,b'\x00\x00\x29\x23pixAnd',0,b'\x00\x00\x36\x23pixBackgroundNorm',0,b'\x00\x00\x2E\x23pixCleanBackgroundToWhite',0,b'\x00\x00\x20\x23pixClipRectangle',0,b'\x00\x00\xD5\x23pixColorFraction',0,b'\x00\x00\x78\x23pixColorMagnitude',0,b'\x00\x00\x1D\x23pixConvertRGBToLuminance',0,b'\x00\x00\x6F\x23pixConvertTo8',0,b'\x00\x00\xA7\x23pixCorrelationBinary',0,b'\x00\x00\xC1\x23pixCountPixels',0,b'\x00\x00\x87\x23pixDeserializeFromMemory',0,b'\x00\x00\x6F\x23pixDeskew',0,b'\x00\x01\x0B\x23pixDestroy',0,b'\x00\x00\x1D\x23pixEndianByteSwapNew',0,b'\x00\x00\xAC\x23pixEqual',0,b'\x00\x00\x8B\x23pixExtractBarcodes',0,b'\x00\x00\x08\x23pixFindPageForeground',0,b'\x00\x00\xBC\x23pixFindSkew',0,b'\x00\x00\x42\x23pixGammaTRC',0,b'\x00\x00\xCE\x23pixGenerateCIData',0,b'\x00\x00\xB1\x23pixGetAverageMaskedRGB',0,b'\x00\x00\x49\x23pixGlobalNormRGB',0,b'\x00\x00\x25\x23pixInvert',0,b'\x00\x00\x17\x23pixLocateBarcodes',0,b'\x00\x00\x73\x23pixMaskOverColorPixels',0,b'\x00\x00\x51\x23pixMaskedThreshOnBackgroundNorm',0,b'\x00\x00\xC6\x23pixNumSignificantGrayColors',0,b'\x00\x00\xDE\x23pixOtsuAdaptiveThreshold',0,b'\x00\x00\x5D\x23pixOtsuThreshOnBackgroundNorm',0,b'\x00\x00\x8F\x23pixProcessBarcodes',0,b'\x00\x00\x84\x23pixRead',0,b'\x00\x00\x96\x23pixReadBarcodes',0,b'\x00\x00\x6F\x23pixRemoveColormap',0,b'\x00\x00\x73\x23pixRemoveColormapGeneral',0,b'\x00\x00\xA1\x23pixRenderBoxa',0,b'\x00\x00\x25\x23pixRotate180',0,b'\x00\x00\x6F\x23pixRotateOrth',0,b'\x00\x00\x6A\x23pixScale',0,b'\x00\x00\xE8\x23pixSerializeToMemory',0,b'\x00\x00\xF3\x23pixWriteImpliedFormat',0,b'\x00\x00\xFC\x23pixWriteMemPng',0,b'\x00\x01\x0E\x23pixaDestroy',0,b'\x00\x00\x12\x23pixaGetBox',0,b'\x00\x00\x7F\x23pixaGetPix',0,b'\x00\x01\x11\x23sarrayDestroy',0,b'\x00\x00\xF9\x23setMsgSeverity',0), + _struct_unions = ((b'\x00\x00\x01\x17\x00\x00\x00\x02Box',b'\x00\x00\x05\x11x',b'\x00\x00\x05\x11y',b'\x00\x00\x05\x11w',b'\x00\x00\x05\x11h',b'\x00\x01\x31\x11refcount'),(b'\x00\x00\x01\x18\x00\x00\x00\x02Boxa',b'\x00\x00\x05\x11n',b'\x00\x00\x05\x11nalloc',b'\x00\x01\x31\x11refcount',b'\x00\x00\x23\x11box'),(b'\x00\x00\x01\x1A\x00\x00\x00\x02L_Compressed_Data',b'\x00\x00\x05\x11type',b'\x00\x01\x2E\x11datacomp',b'\x00\x00\x89\x11nbytescomp',b'\x00\x01\x21\x11data85',b'\x00\x00\x89\x11nbytes85',b'\x00\x01\x21\x11cmapdata85',b'\x00\x01\x21\x11cmapdatahex',b'\x00\x00\x05\x11ncolors',b'\x00\x00\x05\x11w',b'\x00\x00\x05\x11h',b'\x00\x00\x05\x11bps',b'\x00\x00\x05\x11spp',b'\x00\x00\x05\x11minisblack',b'\x00\x00\x05\x11predictor',b'\x00\x00\x89\x11nbytes',b'\x00\x00\x05\x11res'),(b'\x00\x00\x01\x1B\x00\x00\x00\x02Pix',b'\x00\x01\x31\x11w',b'\x00\x01\x31\x11h',b'\x00\x01\x31\x11d',b'\x00\x01\x31\x11spp',b'\x00\x01\x31\x11wpl',b'\x00\x01\x31\x11refcount',b'\x00\x00\x05\x11xres',b'\x00\x00\x05\x11yres',b'\x00\x00\x05\x11informat',b'\x00\x00\x05\x11special',b'\x00\x01\x21\x11text',b'\x00\x01\x2D\x11colormap',b'\x00\x01\x30\x11data'),(b'\x00\x00\x01\x1D\x00\x00\x00\x02PixColormap',b'\x00\x01\x15\x11array',b'\x00\x00\x05\x11depth',b'\x00\x00\x05\x11nalloc',b'\x00\x00\x05\x11n'),(b'\x00\x00\x01\x1C\x00\x00\x00\x02Pixa',b'\x00\x00\x05\x11n',b'\x00\x00\x05\x11nalloc',b'\x00\x01\x31\x11refcount',b'\x00\x00\x1A\x11pix',b'\x00\x00\x04\x11boxa'),(b'\x00\x00\x01\x1F\x00\x00\x00\x02Sarray',b'\x00\x00\x05\x11nalloc',b'\x00\x00\x05\x11n',b'\x00\x00\x05\x11refcount',b'\x00\x01\x20\x11array')), + _enums = (b'\x00\x00\x01\x24\x00\x00\x00\x16$1\x00L_DEFAULT_ENCODE,L_JPEG_ENCODE,L_G4_ENCODE,L_FLATE_ENCODE,L_JP2K_ENCODE',b'\x00\x00\x01\x25\x00\x00\x00\x16$2\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\x01\x26\x00\x00\x00\x16$3\x00L_NOCOPY,L_INSERT,L_COPY,L_CLONE,L_COPY_CLONE',b'\x00\x00\x01\x27\x00\x00\x00\x16$4\x00L_USE_WIDTHS,L_USE_WINDOWS',b'\x00\x00\x01\x28\x00\x00\x00\x16$5\x00L_BF_UNKNOWN,L_BF_ANY,L_BF_CODE128,L_BF_EAN8,L_BF_EAN13,L_BF_CODE2OF5,L_BF_CODEI2OF5,L_BF_CODE39,L_BF_CODE93,L_BF_CODABAR,L_BF_UPCA',b'\x00\x00\x01\x29\x00\x00\x00\x16$6\x00L_SEVERITY_EXTERNAL,L_SEVERITY_ALL,L_SEVERITY_DEBUG,L_SEVERITY_INFO,L_SEVERITY_WARNING,L_SEVERITY_ERROR,L_SEVERITY_NONE'), + _typenames = (b'\x00\x00\x01\x17BOX',b'\x00\x00\x01\x18BOXA',b'\x00\x00\x01\x1AL_COMP_DATA',b'\x00\x00\x01\x1BPIX',b'\x00\x00\x01\x1CPIXA',b'\x00\x00\x01\x1DPIXCMAP',b'\x00\x00\x01\x1FSARRAY',b'\x00\x00\x00\x32l_float32',b'\x00\x00\x01\x23l_float64',b'\x00\x00\x01\x2Bl_int16',b'\x00\x00\x00\x05l_int32',b'\x00\x00\x01\x2Al_int64',b'\x00\x00\x01\x2Cl_int8',b'\x00\x00\x00\x05l_ok',b'\x00\x00\x01\x33l_uint16',b'\x00\x00\x01\x31l_uint32',b'\x00\x00\x01\x32l_uint64',b'\x00\x00\x01\x2Fl_uint8'), ) diff --git a/src/ocrmypdf/lib/compile_leptonica.py b/src/ocrmypdf/lib/compile_leptonica.py index 6a3c088c..ff1e63b0 100644 --- a/src/ocrmypdf/lib/compile_leptonica.py +++ b/src/ocrmypdf/lib/compile_leptonica.py @@ -251,6 +251,18 @@ pixOtsuThreshOnBackgroundNorm(PIX *pixs, l_float32 scorefract, l_int32 *pthresh); +PIX * +pixMaskedThreshOnBackgroundNorm(PIX *pixs, + PIX *pixim, + l_int32 sx, + l_int32 sy, + l_int32 thresh, + l_int32 mincount, + l_int32 smoothx, + l_int32 smoothy, + l_float32 scorefract, + l_int32 *pthresh); + PIX * pixCleanBackgroundToWhite(PIX *pixs, PIX *pixim, From c64bc9329ed8d58390a5de2049646db932f92141 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Tue, 6 Nov 2018 20:12:09 -0800 Subject: [PATCH 53/68] leptonica: reduce boilerplate for wrapper classes (except PIX) --- src/ocrmypdf/leptonica.py | 134 ++++++++++++++++---------------------- 1 file changed, 55 insertions(+), 79 deletions(-) diff --git a/src/ocrmypdf/leptonica.py b/src/ocrmypdf/leptonica.py index e7b46904..252dcb80 100644 --- a/src/ocrmypdf/leptonica.py +++ b/src/ocrmypdf/leptonica.py @@ -28,6 +28,7 @@ import warnings from tempfile import TemporaryFile from ctypes.util import find_library from functools import lru_cache +from collections.abc import Sequence from .lib._leptonica import ffi from .helpers import fspath @@ -119,6 +120,21 @@ class LeptonicaIOError(LeptonicaError): pass +class LeptonicaObject: + cdata_destroy = lambda cdata: None + LEPTONICA_TYPENAME = '' + + def __init__(self, cdata): + if not cdata: + raise ValueError('NULL cdata object') + self._cdata = ffi.gc(cdata, self._destroy) + + @classmethod + def _destroy(cls, cdata): + pp = ffi.new('{} **'.format(cls.LEPTONICA_TYPENAME), cdata) + cls.cdata_destroy(pp) + + class Pix: """ Wrapper around leptonica's PIX object. @@ -440,7 +456,7 @@ class Pix: cropped_pix = lept.pixClipRectangle( self._pix, - cropbox._box, + cropbox._cdata, ffi.NULL) return Pix(cropped_pix) @@ -522,7 +538,7 @@ class Pix: with _LeptonicaErrorTrap(): pix = Pix(lept.pixConvertTo8(self._pix, 0)) pixa_candidates = PixArray(lept.pixExtractBarcodes(pix._pix, 0)) - sarray = StringArray(lept.pixReadBarcodes(pixa_candidates._pixa, + sarray = StringArray(lept.pixReadBarcodes(pixa_candidates._cdata, lept.L_BF_ANY, lept.L_USE_WIDTHS, ffi.NULL, @@ -544,9 +560,11 @@ class Pix: # print('pix destroy ' + repr(pix)) -class CompressedData: - def __init__(self, compdata): - self._compdata = ffi.gc(compdata, CompressedData._destroy) +class CompressedData(LeptonicaObject): + """Wrapper for L_COMP_DATA - abstract compressed image data""" + + LEPTONICA_TYPENAME = 'L_COMP_DATA' + cdata_destroy = lept.l_CIDataDestroy @classmethod def open(cls, path, jpeg_quality=75): @@ -561,152 +579,110 @@ class CompressedData: return CompressedData(p_compdata[0]) def __len__(self): - return self._compdata.nbytescomp + return self._cdata.nbytescomp def read(self): - buf = ffi.buffer(self._compdata.datacomp, self._compdata.nbytescomp) + buf = ffi.buffer(self._cdata.datacomp, self._cdata.nbytescomp) return bytes(buf) def __getattr__(self, name): - if hasattr(self._compdata, name): - return getattr(self._compdata, name) + if hasattr(self._cdata, name): + return getattr(self._cdata, name) raise AttributeError(name) def get_palette_pdf_string(self): "Returns palette pre-formatted for use in PDF" - buflen = len('< ') + len(' rrggbb') * self._compdata.ncolors + len('>') - buf = ffi.buffer(self._compdata.cmapdatahex, buflen) + buflen = len('< ') + len(' rrggbb') * self._cdata.ncolors + len('>') + buf = ffi.buffer(self._cdata.cmapdatahex, buflen) return bytes(buf) - @staticmethod - def _destroy(compdata): - pp = ffi.new('L_COMP_DATA **', compdata) - lept.l_CIDataDestroy(pp) - -class PixArray: +class PixArray(LeptonicaObject, Sequence): """Wrapper around PIXA (array of PIX)""" - def __init__(self, pixa): - if not pixa: - raise ValueError('NULL pixa') - self._pixa = ffi.gc(pixa, PixArray._destroy) + LEPTONICA_TYPENAME = 'PIXA' + cdata_destroy = lept.pixaDestroy def __len__(self): - return self._pixa[0].n + return self._cdata[0].n def __getitem__(self, n): with _LeptonicaErrorTrap(): - return Pix(lept.pixaGetPix(self._pixa, n, lept.L_CLONE)) + return Pix(lept.pixaGetPix(self._cdata, n, lept.L_CLONE)) def get_box(self, n): with _LeptonicaErrorTrap(): - return Box(lept.pixaGetBox(self._pixa, n, lept.L_CLONE)) - - @staticmethod - def _destroy(pixa): - pp = ffi.new('PIXA **', pixa) - lept.pixaDestroy(pp) + return Box(lept.pixaGetBox(self._cdata, n, lept.L_CLONE)) -class Box: +class Box(LeptonicaObject): """Wrapper around Leptonica's BOX objects (a pixel rectangle) - See class Pix for notes about reference counting. + Uses x, y, w, h coordinates. """ - def __init__(self, box): - if not box: - raise ValueError('NULL box') - self._box = ffi.gc(box, Box._destroy) - + LEPTONICA_TYPENAME = 'BOX' + cdata_destroy = lept.boxDestroy def __repr__(self): - if self._box: + if self._cdata: return ''.format( self.x, self.y, self.w, self.h) return '' @property def x(self): - return self._box.x + return self._cdata.x @property def y(self): - return self._box.y + return self._cdata.y @property def w(self): - return self._box.w + return self._cdata.w @property def h(self): - return self._box.h - - @staticmethod - def _destroy(box): - p_box = ffi.new('BOX **', box) - lept.boxDestroy(p_box) + return self._cdata.h -class BoxArray: +class BoxArray(LeptonicaObject, Sequence): """Wrapper around Leptonica's BOXA (Array of BOX) objects.""" - def __init__(self, boxa): - if not boxa: - raise ValueError('NULL boxa') - self._boxa = ffi.gc(boxa, BoxArray._boxa_destroy) + LEPTONICA_TYPENAME = 'BOXA' + cdata_destroy = lept.boxaDestroy def __repr__(self): - if not self._boxa: + if not self._cdata: return '' boxes = (repr(box) for box in self) return '' - def __iter__(self): - for n in range(len(self)): - yield self[n] - def __len__(self): - return self._boxa.n + return self._cdata.n def __getitem__(self, n): if not isinstance(n, int): raise TypeError('list indices must be integers') if 0 <= n < len(self): - return Box(lept.boxaGetBox(self._boxa, n, lept.L_CLONE)) + return Box(lept.boxaGetBox(self._cdata, n, lept.L_CLONE)) raise IndexError(n) - @staticmethod - def _boxa_destroy(boxa): - p_boxa = ffi.new('BOXA **', boxa) - lept.boxaDestroy(p_boxa) +class StringArray(LeptonicaObject, Sequence): -class StringArray: - - def __init__(self, sarray): - if not sarray: - raise ValueError('NULL sarray') - self._sarray = ffi.gc(sarray, StringArray._sarray_destroy) + LEPTONICA_TYPENAME = 'SARRAY' + cdata_destroy = lept.sarrayDestroy def __len__(self): - return self._sarray.n + return self._cdata.n def __getitem__(self, n): if 0 <= n < len(self): - return ffi.string(self._sarray.array[n]) + return ffi.string(self._cdata.array[n]) raise IndexError(n) - def __iter__(self): - for n in range(len(self)): - yield self[n] - - @staticmethod - def _sarray_destroy(sarray): - pp = ffi.new('SARRAY **', sarray) - lept.sarrayDestroy(pp) - @lru_cache(maxsize=1) def get_leptonica_version(): From 806daf42846de7cefc176f05bbb42472bc3cd4de Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Tue, 6 Nov 2018 20:33:40 -0800 Subject: [PATCH 54/68] leptonica: reduce boilerplate for PIX (2/2) --- src/ocrmypdf/leptonica.py | 121 +++++++++++++++++++++----------------- 1 file changed, 66 insertions(+), 55 deletions(-) diff --git a/src/ocrmypdf/leptonica.py b/src/ocrmypdf/leptonica.py index 252dcb80..80f52b27 100644 --- a/src/ocrmypdf/leptonica.py +++ b/src/ocrmypdf/leptonica.py @@ -121,6 +121,23 @@ class LeptonicaIOError(LeptonicaError): class LeptonicaObject: + """General wrapper for Leptonica objects + + When Leptonica returns an object, we bundled it in a wrapper class, which + manages its memory. The wrapper class assumes that it will be calling some + sort of lept.thingDestroy() function when the instance is deleted. Most + Leptonica objects are reference counted, and destroy decrements the + refcount. + + Most of the time, when Leptonica returns something, we wrap and it the job + is done. When wrapping objects that came from a Leptonica container, like + a PIXA returning PIX, the subclass must clone the object before passing it + here, to maintain the reference count. + + CFFI ensures that the destroy function is called at garbage collection time + so we do not need to mess with __del__. + """ + cdata_destroy = lambda cdata: None LEPTONICA_TYPENAME = '' @@ -131,11 +148,15 @@ class LeptonicaObject: @classmethod def _destroy(cls, cdata): + """Destroy some cdata""" + # Leptonica API uses double-pointers for its destroy APIs to prevent + # dangling pointers. This means we need to put our single pointer, + # cdata, in a temporary CDATA**. pp = ffi.new('{} **'.format(cls.LEPTONICA_TYPENAME), cdata) cls.cdata_destroy(pp) -class Pix: +class Pix(LeptonicaObject): """ Wrapper around leptonica's PIX object. @@ -154,20 +175,17 @@ class Pix: modified objects. This allows convenient chaining: >>> Pix.open('filename.jpg').scale((0.5, 0.5)).deskew().show() - """ - def __init__(self, pix): - if not pix: - raise ValueError('NULL pix') - self._pix = ffi.gc(pix, Pix._destroy) + LEPTONICA_TYPENAME = "PIX" + cdata_destroy = lept.pixDestroy def __repr__(self): - if self._pix: + if self._cdata: s = "" - return s.format(self._pix.w, self._pix.h, self._pix.d, - int(ffi.cast('intptr_t', self._pix)), - '(colormapped)' if self._pix.colormap else '') + return s.format(self._cdata.w, self._cdata.h, self._cdata.d, + int(ffi.cast('intptr_t', self._cdata)), + '(colormapped)' if self._cdata.colormap else '') else: return "" @@ -180,7 +198,7 @@ class Pix: data = ffi.new('l_uint8 **') size = ffi.new('size_t *') - err = lept.pixWriteMemPng(data, size, self._pix, 0) + err = lept.pixWriteMemPng(data, size, self._cdata, 0) if err != 0: raise LeptonicaIOError("pixWriteMemPng") @@ -191,7 +209,7 @@ class Pix: data = ffi.new('l_uint32 **') size = ffi.new('size_t *') - err = lept.pixSerializeToMemory(self._pix, data, size) + err = lept.pixSerializeToMemory(self._cdata, data, size) if err != 0: raise LeptonicaIOError("pixSerializeToMemory") @@ -216,30 +234,30 @@ class Pix: return NotImplemented same = ffi.new('l_int32 *', 0) with _LeptonicaErrorTrap(): - err = lept.pixEqual(self._pix, other._pix, same) + err = lept.pixEqual(self._cdata, other._cdata, same) if err: raise TypeError() return bool(same[0]) @property def width(self): - return self._pix.w + return self._cdata.w @property def height(self): - return self._pix.h + return self._cdata.h @property def depth(self): - return self._pix.d + return self._cdata.d @property def size(self): - return (self._pix.w, self._pix.h) + return (self._cdata.w, self._cdata.h) @property def info(self): - return {'dpi': (self._pix.xres, self._pix.yres)} + return {'dpi': (self._cdata.xres, self._cdata.yres)} @property def mode(self): @@ -248,7 +266,7 @@ class Pix: return '1' elif self.depth >= 16: return 'RGB' - elif not self._pix.colormap: + elif not self._cdata.colormap: return 'L' else: return 'P' @@ -280,7 +298,7 @@ class Pix: with _LeptonicaErrorTrap(): lept.pixWriteImpliedFormat( os.fsencode(filename), - self._pix, jpeg_quality, jpeg_progressive) + self._cdata, jpeg_quality, jpeg_progressive) def topil(self): "Returns a PIL.Image version of this Pix" @@ -296,17 +314,17 @@ class Pix: raw_mode = 'ABGR' elif self.mode == '1': raw_mode = '1;I' - pix = Pix(lept.pixEndianByteSwapNew(pix._pix)) + pix = Pix(lept.pixEndianByteSwapNew(pix._cdata)) else: raw_mode = self.mode - pix = Pix(lept.pixEndianByteSwapNew(pix._pix)) + pix = Pix(lept.pixEndianByteSwapNew(pix._cdata)) else: raw_mode = self.mode # no endian swap needed - size = (pix._pix.w, pix._pix.h) - bytecount = pix._pix.wpl * 4 * pix._pix.h - buf = ffi.buffer(pix._pix.data, bytecount) - stride = pix._pix.wpl * 4 + size = (pix._cdata.w, pix._cdata.h) + bytecount = pix._cdata.wpl * 4 * pix._cdata.h + buf = ffi.buffer(pix._cdata.data, bytecount) + stride = pix._cdata.wpl * 4 im = Image.frombytes(self.mode, size, buf, 'raw', raw_mode, stride) @@ -325,21 +343,21 @@ class Pix: for skew angle """ with _LeptonicaErrorTrap(): - return Pix(lept.pixDeskew(self._pix, reduction_factor)) + return Pix(lept.pixDeskew(self._cdata, reduction_factor)) def scale(self, scale_xy): "Returns the pix object rescaled according to the proportions given." with _LeptonicaErrorTrap(): - return Pix(lept.pixScale(self._pix, scale_xy[0], scale_xy[1])) + return Pix(lept.pixScale(self._cdata, scale_xy[0], scale_xy[1])) def rotate180(self): with _LeptonicaErrorTrap(): - return Pix(lept.pixRotate180(ffi.NULL, self._pix)) + return Pix(lept.pixRotate180(ffi.NULL, self._cdata)) def rotate_orth(self, quads): "Orthographic rotation, quads: 0-3, number of clockwise rotations" with _LeptonicaErrorTrap(): - return Pix(lept.pixRotateOrth(self._pix, quads)) + return Pix(lept.pixRotateOrth(self._cdata, quads)) def find_skew(self): """Returns a tuple (deskew angle in degrees, confidence value). @@ -349,7 +367,7 @@ class Pix: with _LeptonicaErrorTrap(): angle = ffi.new('float *', 0.0) confidence = ffi.new('float *', 0.0) - result = lept.pixFindSkew(self._pix, angle, confidence) + result = lept.pixFindSkew(self._cdata, angle, confidence) if result == 0: return (angle[0], confidence[0]) else: @@ -357,7 +375,7 @@ class Pix: def convert_rgb_to_luminance(self): with _LeptonicaErrorTrap(): - gray_pix = lept.pixConvertRGBToLuminance(self._pix) + gray_pix = lept.pixConvertRGBToLuminance(self._cdata) if gray_pix: return Pix(gray_pix) return None @@ -371,7 +389,7 @@ class Pix: """ with _LeptonicaErrorTrap(): return Pix(lept.pixRemoveColormapGeneral( - self._pix, removal_type, lept.L_COPY)) + self._cdata, removal_type, lept.L_COPY)) def otsu_adaptive_threshold( self, tile_size=(300, 300), kernel_size=(4, 4), scorefract=0.1): @@ -381,7 +399,7 @@ class Pix: p_pix = ffi.new('PIX **') result = lept.pixOtsuAdaptiveThreshold( - self._pix, + self._cdata, sx, sy, smoothx, smoothy, scorefract, @@ -401,10 +419,10 @@ class Pix: if mask is None: mask = ffi.NULL if isinstance(mask, Pix): - mask = mask._pix + mask = mask._cdata thresh_pix = lept.pixOtsuThreshOnBackgroundNorm( - self._pix, + self._cdata, mask, sx, sy, thresh, mincount, bgval, @@ -425,10 +443,10 @@ class Pix: if mask is None: mask = ffi.NULL if isinstance(mask, Pix): - mask = mask._pix + mask = mask._cdata new_pix = lept.pixMaskedThreshOnBackgroundNorm( - self._pix, + self._cdata, mask, sx, sy, thresh, mincount, @@ -445,7 +463,7 @@ class Pix: showmorph=0, display=0, pdfdir=ffi.NULL): with _LeptonicaErrorTrap(): cropbox = Box(lept.pixFindPageForeground( - self._pix, + self._cdata, threshold, mindist, erasedist, @@ -455,7 +473,7 @@ class Pix: pdfdir)) cropped_pix = lept.pixClipRectangle( - self._pix, + self._cdata, cropbox._cdata, ffi.NULL) @@ -465,7 +483,7 @@ class Pix: self, mask=None, grayscale=None, gamma=1.0, black=0, white=255): with _LeptonicaErrorTrap(): return Pix(lept.pixCleanBackgroundToWhite( - self._pix, + self._cdata, mask or ffi.NULL, grayscale or ffi.NULL, gamma, @@ -476,7 +494,7 @@ class Pix: with _LeptonicaErrorTrap(): return Pix(lept.pixGammaTRC( ffi.NULL, - self._pix, + self._cdata, gamma, minval, maxval @@ -489,7 +507,7 @@ class Pix: target_pix = self.remove_colormap(lept.REMOVE_CMAP_BASED_ON_SRC) with _LeptonicaErrorTrap(): return Pix(lept.pixBackgroundNorm( - target_pix._pix, + target_pix._cdata, mask or ffi.NULL, grayscale or ffi.NULL, tile_size[0], @@ -516,7 +534,7 @@ class Pix: raise LeptonicaError("Leptonica version is too old") correlation = ffi.new('float *', 0.0) - result = lept.pixCorrelationBinary(pix1._pix, pix2._pix, + result = lept.pixCorrelationBinary(pix1._cdata, pix2._cdata, correlation) if result != 0: raise LeptonicaError("Correlation failed") @@ -525,19 +543,19 @@ class Pix: def generate_pdf_ci_data(self, type_, quality): "Convert to PDF data, with transcoding" p_compdata = ffi.new('L_COMP_DATA **') - result = lept.pixGenerateCIData(self._pix, type_, quality, 0, + result = lept.pixGenerateCIData(self._cdata, type_, quality, 0, p_compdata) if result != 0: raise LeptonicaError("Generate PDF data failed") return CompressedData(p_compdata[0]) def invert(self): - return Pix(lept.pixInvert(ffi.NULL, self._pix)) + return Pix(lept.pixInvert(ffi.NULL, self._cdata)) def locate_barcodes(self): with _LeptonicaErrorTrap(): - pix = Pix(lept.pixConvertTo8(self._pix, 0)) - pixa_candidates = PixArray(lept.pixExtractBarcodes(pix._pix, 0)) + pix = Pix(lept.pixConvertTo8(self._cdata, 0)) + pixa_candidates = PixArray(lept.pixExtractBarcodes(pix._cdata, 0)) sarray = StringArray(lept.pixReadBarcodes(pixa_candidates._cdata, lept.L_BF_ANY, lept.L_USE_WIDTHS, @@ -553,13 +571,6 @@ class Pix: yield (decoded, (left, top, right, bottom)) - @staticmethod - def _destroy(pix): - p_pix = ffi.new('PIX **', pix) - lept.pixDestroy(p_pix) - # print('pix destroy ' + repr(pix)) - - class CompressedData(LeptonicaObject): """Wrapper for L_COMP_DATA - abstract compressed image data""" From 58b26f6715dde2f1cdf2f9960c6986144c85eeeb Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Wed, 7 Nov 2018 01:28:54 -0800 Subject: [PATCH 55/68] Leptonica: learn to despeckle 1bpp images --- src/ocrmypdf/leptonica.py | 63 ++++++++++++++++++++++++++- src/ocrmypdf/lib/_leptonica.py | 10 ++--- src/ocrmypdf/lib/compile_leptonica.py | 43 ++++++++++++++++++ 3 files changed, 110 insertions(+), 6 deletions(-) diff --git a/src/ocrmypdf/leptonica.py b/src/ocrmypdf/leptonica.py index 80f52b27..756ba924 100644 --- a/src/ocrmypdf/leptonica.py +++ b/src/ocrmypdf/leptonica.py @@ -143,7 +143,7 @@ class LeptonicaObject: def __init__(self, cdata): if not cdata: - raise ValueError('NULL cdata object') + raise ValueError('Tried to wrap a NULL ' + self.LEPTONICA_TYPENAME) self._cdata = ffi.gc(cdata, self._destroy) @classmethod @@ -570,6 +570,35 @@ class Pix(LeptonicaObject): right, bottom = box.x + box.w, box.y + box.h yield (decoded, (left, top, right, bottom)) + def despeckle(self, size): + if size == 2: + speckle2 = """ + oooo + oC o + o o + oooo + """ + sel1 = Sel.from_selstr(speckle2, 'speckle2') + sel2 = Sel.create_brick(2, 2, 0, 0, lept.SEL_HIT) + elif size == 3: + speckle3 = """ + ooooo + oC o + o o + o o + ooooo + """ + sel1 = Sel.from_selstr(speckle3, 'speckle3') + sel2 = Sel.create_brick(3, 3, 0, 0, lept.SEL_HIT) + else: + raise ValueError(size) + + pixhmt = Pix(lept.pixHMT(ffi.NULL, self._cdata, sel1._cdata)) + pixdilated = Pix(lept.pixDilate(ffi.NULL, pixhmt._cdata, sel2._cdata)) + + pixsub = Pix(lept.pixSubtract(ffi.NULL, self._cdata, pixdilated._cdata)) + return pixsub + class CompressedData(LeptonicaObject): """Wrapper for L_COMP_DATA - abstract compressed image data""" @@ -682,6 +711,7 @@ class BoxArray(LeptonicaObject, Sequence): class StringArray(LeptonicaObject, Sequence): + """Leptonica SARRAY/string array""" LEPTONICA_TYPENAME = 'SARRAY' cdata_destroy = lept.sarrayDestroy @@ -695,6 +725,37 @@ class StringArray(LeptonicaObject, Sequence): raise IndexError(n) +class Sel(LeptonicaObject): + """Leptonica 'sel'/selection element for hit-miss transform""" + + LEPTONICA_TYPENAME = 'SEL' + cdata_destroy = lept.selDestroy + + @classmethod + def from_selstr(cls, selstr, name): + lines = [line.strip() for line in selstr.split('\n') if line.strip()] + h = len(lines) + w = len(lines[0]) + lengths = set(len(line) for line in lines) + if len(lengths) != 1: + raise ValueError("All lines in selstr must be same length") + + repacked = ''.join(line.strip() for line in lines) + buf_selstr = ffi.from_buffer(repacked.encode('ascii')) + buf_name = ffi.from_buffer(name.encode('ascii')) + sel = lept.selCreateFromString(buf_selstr, h, w, buf_name) + return cls(sel) + + @classmethod + def create_brick(cls, h, w, cy, cx, type_): + sel = lept.selCreateBrick(h, w, cy, cx, type_) + return cls(sel) + + def __repr__(self): + selstr = ffi.gc(lept.selPrintToString(self._cdata), lept.lept_free) + return '' + + @lru_cache(maxsize=1) def get_leptonica_version(): """Get Leptonica version string. diff --git a/src/ocrmypdf/lib/_leptonica.py b/src/ocrmypdf/lib/_leptonica.py index c4bc4ccd..502fd705 100644 --- a/src/ocrmypdf/lib/_leptonica.py +++ b/src/ocrmypdf/lib/_leptonica.py @@ -3,9 +3,9 @@ import _cffi_backend ffi = _cffi_backend.FFI('ocrmypdf.lib._leptonica', _version = 0x2601, - _types = b'\x00\x00\x01\x0D\x00\x01\x17\x03\x00\x00\x00\x0F\x00\x00\x01\x0D\x00\x01\x18\x03\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x01\x0D\x00\x01\x1B\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\x01\x22\x03\x00\x00\x00\x0F\x00\x00\x01\x0D\x00\x01\x1C\x03\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x04\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x09\x03\x00\x00\x1A\x11\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x01\x11\x00\x00\x01\x03\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x0D\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x09\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\x09\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x0D\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x09\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\x0D\x01\x00\x00\x05\x03\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x09\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\x5B\x11\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x0D\x01\x00\x00\x0D\x01\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x13\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x10\x11\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x01\x31\x03\x00\x00\x1C\x01\x00\x00\x00\x0F\x00\x00\x13\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x01\x1E\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x01\x1E\x03\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x01\x1E\x0D\x00\x00\x13\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x93\x11\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x01\x21\x0D\x00\x00\x00\x0F\x00\x00\x5B\x0D\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x04\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x32\x03\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x5B\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\xAA\x11\x00\x00\xAA\x11\x00\x00\xAA\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\xAA\x11\x00\x00\xAA\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x5B\x11\x00\x00\x5B\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x0D\x01\x00\x00\x07\x01\x00\x00\x5B\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x01\x19\x03\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\xAA\x11\x00\x00\xAA\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x0D\x01\x00\x00\x1A\x11\x00\x00\x1A\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x01\x30\x03\x00\x00\x89\x03\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x10\x11\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\xD3\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x10\x11\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x01\x2E\x03\x00\x00\xEB\x11\x00\x00\x09\x11\x00\x00\x0D\x01\x00\x00\x00\x0F\x00\x01\x34\x0D\x00\x00\x23\x11\x00\x00\x00\x0F\x00\x01\x34\x0D\x00\x00\x04\x03\x00\x00\x00\x0F\x00\x01\x34\x0D\x00\x00\xD3\x11\x00\x00\x00\x0F\x00\x01\x34\x0D\x00\x00\x1A\x11\x00\x00\x00\x0F\x00\x01\x34\x0D\x00\x00\x13\x03\x00\x00\x00\x0F\x00\x01\x34\x0D\x00\x00\x93\x11\x00\x00\x00\x0F\x00\x01\x34\x0D\x00\x01\x34\x03\x00\x00\x00\x0F\x00\x00\x00\x09\x00\x00\x01\x09\x00\x01\x1A\x03\x00\x00\x02\x09\x00\x00\x03\x09\x00\x00\x05\x09\x00\x00\x04\x09\x00\x01\x1F\x03\x00\x00\x06\x09\x00\x01\x21\x03\x00\x01\x22\x03\x00\x00\x02\x01\x00\x00\x0E\x01\x00\x00\x00\x0B\x00\x00\x01\x0B\x00\x00\x02\x0B\x00\x00\x03\x0B\x00\x00\x04\x0B\x00\x00\x05\x0B\x00\x00\x0B\x01\x00\x00\x05\x01\x00\x00\x03\x01\x00\x01\x1D\x03\x00\x01\x2F\x03\x00\x00\x04\x01\x00\x01\x31\x03\x00\x00\x08\x01\x00\x00\x0C\x01\x00\x00\x06\x01\x00\x00\x00\x01', - _globals = (b'\xFF\xFF\xFF\x0BL_BF_ANY',1,b'\xFF\xFF\xFF\x0BL_BF_CODABAR',9,b'\xFF\xFF\xFF\x0BL_BF_CODE128',2,b'\xFF\xFF\xFF\x0BL_BF_CODE2OF5',5,b'\xFF\xFF\xFF\x0BL_BF_CODE39',7,b'\xFF\xFF\xFF\x0BL_BF_CODE93',8,b'\xFF\xFF\xFF\x0BL_BF_CODEI2OF5',6,b'\xFF\xFF\xFF\x0BL_BF_EAN13',4,b'\xFF\xFF\xFF\x0BL_BF_EAN8',3,b'\xFF\xFF\xFF\x0BL_BF_UNKNOWN',0,b'\xFF\xFF\xFF\x0BL_BF_UPCA',10,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_DEFAULT_ENCODE',0,b'\xFF\xFF\xFF\x0BL_FLATE_ENCODE',3,b'\xFF\xFF\xFF\x0BL_G4_ENCODE',2,b'\xFF\xFF\xFF\x0BL_INSERT',0,b'\xFF\xFF\xFF\x0BL_JP2K_ENCODE',4,b'\xFF\xFF\xFF\x0BL_JPEG_ENCODE',1,b'\xFF\xFF\xFF\x0BL_NOCOPY',0,b'\xFF\xFF\xFF\x0BL_SEVERITY_ALL',1,b'\xFF\xFF\xFF\x0BL_SEVERITY_DEBUG',2,b'\xFF\xFF\xFF\x0BL_SEVERITY_ERROR',5,b'\xFF\xFF\xFF\x0BL_SEVERITY_EXTERNAL',0,b'\xFF\xFF\xFF\x0BL_SEVERITY_INFO',3,b'\xFF\xFF\xFF\x0BL_SEVERITY_NONE',6,b'\xFF\xFF\xFF\x0BL_SEVERITY_WARNING',4,b'\xFF\xFF\xFF\x0BL_USE_WIDTHS',1,b'\xFF\xFF\xFF\x0BL_USE_WINDOWS',2,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\x00\x23boxClone',0,b'\x00\x01\x02\x23boxDestroy',0,b'\x00\x01\x05\x23boxaDestroy',0,b'\x00\x00\x03\x23boxaGetBox',0,b'\x00\x00\x9D\x23getLeptonicaVersion',0,b'\x00\x01\x08\x23l_CIDataDestroy',0,b'\x00\x00\xED\x23l_generateCIDataForPdf',0,b'\x00\x01\x14\x23lept_free',0,b'\x00\x00\x9F\x23makePixelSumTab8',0,b'\x00\x00\x29\x23pixAnd',0,b'\x00\x00\x36\x23pixBackgroundNorm',0,b'\x00\x00\x2E\x23pixCleanBackgroundToWhite',0,b'\x00\x00\x20\x23pixClipRectangle',0,b'\x00\x00\xD5\x23pixColorFraction',0,b'\x00\x00\x78\x23pixColorMagnitude',0,b'\x00\x00\x1D\x23pixConvertRGBToLuminance',0,b'\x00\x00\x6F\x23pixConvertTo8',0,b'\x00\x00\xA7\x23pixCorrelationBinary',0,b'\x00\x00\xC1\x23pixCountPixels',0,b'\x00\x00\x87\x23pixDeserializeFromMemory',0,b'\x00\x00\x6F\x23pixDeskew',0,b'\x00\x01\x0B\x23pixDestroy',0,b'\x00\x00\x1D\x23pixEndianByteSwapNew',0,b'\x00\x00\xAC\x23pixEqual',0,b'\x00\x00\x8B\x23pixExtractBarcodes',0,b'\x00\x00\x08\x23pixFindPageForeground',0,b'\x00\x00\xBC\x23pixFindSkew',0,b'\x00\x00\x42\x23pixGammaTRC',0,b'\x00\x00\xCE\x23pixGenerateCIData',0,b'\x00\x00\xB1\x23pixGetAverageMaskedRGB',0,b'\x00\x00\x49\x23pixGlobalNormRGB',0,b'\x00\x00\x25\x23pixInvert',0,b'\x00\x00\x17\x23pixLocateBarcodes',0,b'\x00\x00\x73\x23pixMaskOverColorPixels',0,b'\x00\x00\x51\x23pixMaskedThreshOnBackgroundNorm',0,b'\x00\x00\xC6\x23pixNumSignificantGrayColors',0,b'\x00\x00\xDE\x23pixOtsuAdaptiveThreshold',0,b'\x00\x00\x5D\x23pixOtsuThreshOnBackgroundNorm',0,b'\x00\x00\x8F\x23pixProcessBarcodes',0,b'\x00\x00\x84\x23pixRead',0,b'\x00\x00\x96\x23pixReadBarcodes',0,b'\x00\x00\x6F\x23pixRemoveColormap',0,b'\x00\x00\x73\x23pixRemoveColormapGeneral',0,b'\x00\x00\xA1\x23pixRenderBoxa',0,b'\x00\x00\x25\x23pixRotate180',0,b'\x00\x00\x6F\x23pixRotateOrth',0,b'\x00\x00\x6A\x23pixScale',0,b'\x00\x00\xE8\x23pixSerializeToMemory',0,b'\x00\x00\xF3\x23pixWriteImpliedFormat',0,b'\x00\x00\xFC\x23pixWriteMemPng',0,b'\x00\x01\x0E\x23pixaDestroy',0,b'\x00\x00\x12\x23pixaGetBox',0,b'\x00\x00\x7F\x23pixaGetPix',0,b'\x00\x01\x11\x23sarrayDestroy',0,b'\x00\x00\xF9\x23setMsgSeverity',0), - _struct_unions = ((b'\x00\x00\x01\x17\x00\x00\x00\x02Box',b'\x00\x00\x05\x11x',b'\x00\x00\x05\x11y',b'\x00\x00\x05\x11w',b'\x00\x00\x05\x11h',b'\x00\x01\x31\x11refcount'),(b'\x00\x00\x01\x18\x00\x00\x00\x02Boxa',b'\x00\x00\x05\x11n',b'\x00\x00\x05\x11nalloc',b'\x00\x01\x31\x11refcount',b'\x00\x00\x23\x11box'),(b'\x00\x00\x01\x1A\x00\x00\x00\x02L_Compressed_Data',b'\x00\x00\x05\x11type',b'\x00\x01\x2E\x11datacomp',b'\x00\x00\x89\x11nbytescomp',b'\x00\x01\x21\x11data85',b'\x00\x00\x89\x11nbytes85',b'\x00\x01\x21\x11cmapdata85',b'\x00\x01\x21\x11cmapdatahex',b'\x00\x00\x05\x11ncolors',b'\x00\x00\x05\x11w',b'\x00\x00\x05\x11h',b'\x00\x00\x05\x11bps',b'\x00\x00\x05\x11spp',b'\x00\x00\x05\x11minisblack',b'\x00\x00\x05\x11predictor',b'\x00\x00\x89\x11nbytes',b'\x00\x00\x05\x11res'),(b'\x00\x00\x01\x1B\x00\x00\x00\x02Pix',b'\x00\x01\x31\x11w',b'\x00\x01\x31\x11h',b'\x00\x01\x31\x11d',b'\x00\x01\x31\x11spp',b'\x00\x01\x31\x11wpl',b'\x00\x01\x31\x11refcount',b'\x00\x00\x05\x11xres',b'\x00\x00\x05\x11yres',b'\x00\x00\x05\x11informat',b'\x00\x00\x05\x11special',b'\x00\x01\x21\x11text',b'\x00\x01\x2D\x11colormap',b'\x00\x01\x30\x11data'),(b'\x00\x00\x01\x1D\x00\x00\x00\x02PixColormap',b'\x00\x01\x15\x11array',b'\x00\x00\x05\x11depth',b'\x00\x00\x05\x11nalloc',b'\x00\x00\x05\x11n'),(b'\x00\x00\x01\x1C\x00\x00\x00\x02Pixa',b'\x00\x00\x05\x11n',b'\x00\x00\x05\x11nalloc',b'\x00\x01\x31\x11refcount',b'\x00\x00\x1A\x11pix',b'\x00\x00\x04\x11boxa'),(b'\x00\x00\x01\x1F\x00\x00\x00\x02Sarray',b'\x00\x00\x05\x11nalloc',b'\x00\x00\x05\x11n',b'\x00\x00\x05\x11refcount',b'\x00\x01\x20\x11array')), - _enums = (b'\x00\x00\x01\x24\x00\x00\x00\x16$1\x00L_DEFAULT_ENCODE,L_JPEG_ENCODE,L_G4_ENCODE,L_FLATE_ENCODE,L_JP2K_ENCODE',b'\x00\x00\x01\x25\x00\x00\x00\x16$2\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\x01\x26\x00\x00\x00\x16$3\x00L_NOCOPY,L_INSERT,L_COPY,L_CLONE,L_COPY_CLONE',b'\x00\x00\x01\x27\x00\x00\x00\x16$4\x00L_USE_WIDTHS,L_USE_WINDOWS',b'\x00\x00\x01\x28\x00\x00\x00\x16$5\x00L_BF_UNKNOWN,L_BF_ANY,L_BF_CODE128,L_BF_EAN8,L_BF_EAN13,L_BF_CODE2OF5,L_BF_CODEI2OF5,L_BF_CODE39,L_BF_CODE93,L_BF_CODABAR,L_BF_UPCA',b'\x00\x00\x01\x29\x00\x00\x00\x16$6\x00L_SEVERITY_EXTERNAL,L_SEVERITY_ALL,L_SEVERITY_DEBUG,L_SEVERITY_INFO,L_SEVERITY_WARNING,L_SEVERITY_ERROR,L_SEVERITY_NONE'), - _typenames = (b'\x00\x00\x01\x17BOX',b'\x00\x00\x01\x18BOXA',b'\x00\x00\x01\x1AL_COMP_DATA',b'\x00\x00\x01\x1BPIX',b'\x00\x00\x01\x1CPIXA',b'\x00\x00\x01\x1DPIXCMAP',b'\x00\x00\x01\x1FSARRAY',b'\x00\x00\x00\x32l_float32',b'\x00\x00\x01\x23l_float64',b'\x00\x00\x01\x2Bl_int16',b'\x00\x00\x00\x05l_int32',b'\x00\x00\x01\x2Al_int64',b'\x00\x00\x01\x2Cl_int8',b'\x00\x00\x00\x05l_ok',b'\x00\x00\x01\x33l_uint16',b'\x00\x00\x01\x31l_uint32',b'\x00\x00\x01\x32l_uint64',b'\x00\x00\x01\x2Fl_uint8'), + _types = b'\x00\x00\x01\x0D\x00\x01\x2F\x03\x00\x00\x00\x0F\x00\x00\x01\x0D\x00\x01\x30\x03\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x01\x0D\x00\x01\x33\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\x01\x3B\x03\x00\x00\x00\x0F\x00\x00\x01\x0D\x00\x01\x34\x03\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x04\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x09\x03\x00\x00\x1A\x11\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x01\x11\x00\x00\x01\x03\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x0D\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x09\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\x09\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x01\x38\x03\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x0D\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x09\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\x0D\x01\x00\x00\x05\x03\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x09\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\x60\x11\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x0D\x01\x00\x00\x0D\x01\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x13\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x10\x11\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x01\x4C\x03\x00\x00\x1C\x01\x00\x00\x00\x0F\x00\x00\x13\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x01\x36\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x01\x36\x03\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x01\x36\x0D\x00\x00\x13\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x98\x11\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x45\x0D\x00\x00\x10\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x10\x11\x00\x00\x00\x0F\x00\x00\x45\x0D\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\x01\x3A\x0D\x00\x00\x45\x11\x00\x00\x00\x0F\x00\x01\x3A\x0D\x00\x00\x00\x0F\x00\x00\x60\x0D\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x04\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x32\x03\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x60\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\xBF\x11\x00\x00\xBF\x11\x00\x00\xBF\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\xBF\x11\x00\x00\xBF\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x60\x11\x00\x00\x60\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x0D\x01\x00\x00\x07\x01\x00\x00\x60\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x01\x31\x03\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\xBF\x11\x00\x00\xBF\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x0D\x01\x00\x00\x1A\x11\x00\x00\x1A\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x01\x4B\x03\x00\x00\x8E\x03\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x10\x11\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\xE8\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x10\x11\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x01\x49\x03\x00\x01\x00\x11\x00\x00\x09\x11\x00\x00\x0D\x01\x00\x00\x00\x0F\x00\x01\x4F\x0D\x00\x00\x23\x11\x00\x00\x00\x0F\x00\x01\x4F\x0D\x00\x00\x04\x03\x00\x00\x00\x0F\x00\x01\x4F\x0D\x00\x00\xE8\x11\x00\x00\x00\x0F\x00\x01\x4F\x0D\x00\x00\x1A\x11\x00\x00\x00\x0F\x00\x01\x4F\x0D\x00\x00\x13\x03\x00\x00\x00\x0F\x00\x01\x4F\x0D\x00\x00\x98\x11\x00\x00\x00\x0F\x00\x01\x4F\x0D\x00\x00\x45\x03\x00\x00\x00\x0F\x00\x01\x4F\x0D\x00\x01\x4F\x03\x00\x00\x00\x0F\x00\x00\x00\x09\x00\x00\x01\x09\x00\x01\x32\x03\x00\x00\x02\x09\x00\x00\x03\x09\x00\x00\x05\x09\x00\x00\x04\x09\x00\x01\x37\x03\x00\x00\x06\x09\x00\x00\x07\x09\x00\x01\x3A\x03\x00\x01\x3B\x03\x00\x00\x02\x01\x00\x00\x0E\x01\x00\x00\x00\x0B\x00\x00\x01\x0B\x00\x00\x02\x0B\x00\x00\x03\x0B\x00\x00\x04\x0B\x00\x00\x05\x0B\x00\x00\x06\x0B\x00\x00\x60\x03\x00\x00\x0B\x01\x00\x00\x05\x01\x00\x00\x03\x01\x00\x01\x35\x03\x00\x01\x4A\x03\x00\x00\x04\x01\x00\x01\x4C\x03\x00\x00\x08\x01\x00\x00\x0C\x01\x00\x00\x06\x01\x00\x00\x00\x01', + _globals = (b'\xFF\xFF\xFF\x0BL_BF_ANY',1,b'\xFF\xFF\xFF\x0BL_BF_CODABAR',9,b'\xFF\xFF\xFF\x0BL_BF_CODE128',2,b'\xFF\xFF\xFF\x0BL_BF_CODE2OF5',5,b'\xFF\xFF\xFF\x0BL_BF_CODE39',7,b'\xFF\xFF\xFF\x0BL_BF_CODE93',8,b'\xFF\xFF\xFF\x0BL_BF_CODEI2OF5',6,b'\xFF\xFF\xFF\x0BL_BF_EAN13',4,b'\xFF\xFF\xFF\x0BL_BF_EAN8',3,b'\xFF\xFF\xFF\x0BL_BF_UNKNOWN',0,b'\xFF\xFF\xFF\x0BL_BF_UPCA',10,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_DEFAULT_ENCODE',0,b'\xFF\xFF\xFF\x0BL_FLATE_ENCODE',3,b'\xFF\xFF\xFF\x0BL_G4_ENCODE',2,b'\xFF\xFF\xFF\x0BL_INSERT',0,b'\xFF\xFF\xFF\x0BL_JP2K_ENCODE',4,b'\xFF\xFF\xFF\x0BL_JPEG_ENCODE',1,b'\xFF\xFF\xFF\x0BL_NOCOPY',0,b'\xFF\xFF\xFF\x0BL_SEVERITY_ALL',1,b'\xFF\xFF\xFF\x0BL_SEVERITY_DEBUG',2,b'\xFF\xFF\xFF\x0BL_SEVERITY_ERROR',5,b'\xFF\xFF\xFF\x0BL_SEVERITY_EXTERNAL',0,b'\xFF\xFF\xFF\x0BL_SEVERITY_INFO',3,b'\xFF\xFF\xFF\x0BL_SEVERITY_NONE',6,b'\xFF\xFF\xFF\x0BL_SEVERITY_WARNING',4,b'\xFF\xFF\xFF\x0BL_USE_WIDTHS',1,b'\xFF\xFF\xFF\x0BL_USE_WINDOWS',2,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'\xFF\xFF\xFF\x0BSEL_DONT_CARE',0,b'\xFF\xFF\xFF\x0BSEL_HIT',1,b'\xFF\xFF\xFF\x0BSEL_MISS',2,b'\x00\x00\x00\x23boxClone',0,b'\x00\x01\x17\x23boxDestroy',0,b'\x00\x01\x1A\x23boxaDestroy',0,b'\x00\x00\x03\x23boxaGetBox',0,b'\x00\x00\xB2\x23getLeptonicaVersion',0,b'\x00\x01\x1D\x23l_CIDataDestroy',0,b'\x00\x01\x02\x23l_generateCIDataForPdf',0,b'\x00\x01\x2C\x23lept_free',0,b'\x00\x00\xB4\x23makePixelSumTab8',0,b'\x00\x00\x29\x23pixAnd',0,b'\x00\x00\x36\x23pixBackgroundNorm',0,b'\x00\x00\x2E\x23pixCleanBackgroundToWhite',0,b'\x00\x00\x20\x23pixClipRectangle',0,b'\x00\x00\xEA\x23pixColorFraction',0,b'\x00\x00\x7D\x23pixColorMagnitude',0,b'\x00\x00\x1D\x23pixConvertRGBToLuminance',0,b'\x00\x00\x74\x23pixConvertTo8',0,b'\x00\x00\xBC\x23pixCorrelationBinary',0,b'\x00\x00\xD6\x23pixCountPixels',0,b'\x00\x00\x8C\x23pixDeserializeFromMemory',0,b'\x00\x00\x74\x23pixDeskew',0,b'\x00\x01\x20\x23pixDestroy',0,b'\x00\x00\x42\x23pixDilate',0,b'\x00\x00\x1D\x23pixEndianByteSwapNew',0,b'\x00\x00\xC1\x23pixEqual',0,b'\x00\x00\x42\x23pixErode',0,b'\x00\x00\x90\x23pixExtractBarcodes',0,b'\x00\x00\x08\x23pixFindPageForeground',0,b'\x00\x00\xD1\x23pixFindSkew',0,b'\x00\x00\x47\x23pixGammaTRC',0,b'\x00\x00\xE3\x23pixGenerateCIData',0,b'\x00\x00\xC6\x23pixGetAverageMaskedRGB',0,b'\x00\x00\x4E\x23pixGlobalNormRGB',0,b'\x00\x00\x42\x23pixHMT',0,b'\x00\x00\x25\x23pixInvert',0,b'\x00\x00\x17\x23pixLocateBarcodes',0,b'\x00\x00\x78\x23pixMaskOverColorPixels',0,b'\x00\x00\x56\x23pixMaskedThreshOnBackgroundNorm',0,b'\x00\x00\xDB\x23pixNumSignificantGrayColors',0,b'\x00\x00\xF3\x23pixOtsuAdaptiveThreshold',0,b'\x00\x00\x62\x23pixOtsuThreshOnBackgroundNorm',0,b'\x00\x00\x94\x23pixProcessBarcodes',0,b'\x00\x00\x89\x23pixRead',0,b'\x00\x00\x9B\x23pixReadBarcodes',0,b'\x00\x00\x74\x23pixRemoveColormap',0,b'\x00\x00\x78\x23pixRemoveColormapGeneral',0,b'\x00\x00\xB6\x23pixRenderBoxa',0,b'\x00\x00\x25\x23pixRotate180',0,b'\x00\x00\x74\x23pixRotateOrth',0,b'\x00\x00\x6F\x23pixScale',0,b'\x00\x00\xFD\x23pixSerializeToMemory',0,b'\x00\x00\x29\x23pixSubtract',0,b'\x00\x01\x08\x23pixWriteImpliedFormat',0,b'\x00\x01\x11\x23pixWriteMemPng',0,b'\x00\x01\x23\x23pixaDestroy',0,b'\x00\x00\x12\x23pixaGetBox',0,b'\x00\x00\x84\x23pixaGetPix',0,b'\x00\x01\x26\x23sarrayDestroy',0,b'\x00\x00\xA8\x23selCreateBrick',0,b'\x00\x00\xA2\x23selCreateFromString',0,b'\x00\x01\x29\x23selDestroy',0,b'\x00\x00\xAF\x23selPrintToString',0,b'\x00\x01\x0E\x23setMsgSeverity',0), + _struct_unions = ((b'\x00\x00\x01\x2F\x00\x00\x00\x02Box',b'\x00\x00\x05\x11x',b'\x00\x00\x05\x11y',b'\x00\x00\x05\x11w',b'\x00\x00\x05\x11h',b'\x00\x01\x4C\x11refcount'),(b'\x00\x00\x01\x30\x00\x00\x00\x02Boxa',b'\x00\x00\x05\x11n',b'\x00\x00\x05\x11nalloc',b'\x00\x01\x4C\x11refcount',b'\x00\x00\x23\x11box'),(b'\x00\x00\x01\x32\x00\x00\x00\x02L_Compressed_Data',b'\x00\x00\x05\x11type',b'\x00\x01\x49\x11datacomp',b'\x00\x00\x8E\x11nbytescomp',b'\x00\x01\x3A\x11data85',b'\x00\x00\x8E\x11nbytes85',b'\x00\x01\x3A\x11cmapdata85',b'\x00\x01\x3A\x11cmapdatahex',b'\x00\x00\x05\x11ncolors',b'\x00\x00\x05\x11w',b'\x00\x00\x05\x11h',b'\x00\x00\x05\x11bps',b'\x00\x00\x05\x11spp',b'\x00\x00\x05\x11minisblack',b'\x00\x00\x05\x11predictor',b'\x00\x00\x8E\x11nbytes',b'\x00\x00\x05\x11res'),(b'\x00\x00\x01\x33\x00\x00\x00\x02Pix',b'\x00\x01\x4C\x11w',b'\x00\x01\x4C\x11h',b'\x00\x01\x4C\x11d',b'\x00\x01\x4C\x11spp',b'\x00\x01\x4C\x11wpl',b'\x00\x01\x4C\x11refcount',b'\x00\x00\x05\x11xres',b'\x00\x00\x05\x11yres',b'\x00\x00\x05\x11informat',b'\x00\x00\x05\x11special',b'\x00\x01\x3A\x11text',b'\x00\x01\x48\x11colormap',b'\x00\x01\x4B\x11data'),(b'\x00\x00\x01\x35\x00\x00\x00\x02PixColormap',b'\x00\x01\x2D\x11array',b'\x00\x00\x05\x11depth',b'\x00\x00\x05\x11nalloc',b'\x00\x00\x05\x11n'),(b'\x00\x00\x01\x34\x00\x00\x00\x02Pixa',b'\x00\x00\x05\x11n',b'\x00\x00\x05\x11nalloc',b'\x00\x01\x4C\x11refcount',b'\x00\x00\x1A\x11pix',b'\x00\x00\x04\x11boxa'),(b'\x00\x00\x01\x37\x00\x00\x00\x02Sarray',b'\x00\x00\x05\x11nalloc',b'\x00\x00\x05\x11n',b'\x00\x00\x05\x11refcount',b'\x00\x01\x39\x11array'),(b'\x00\x00\x01\x38\x00\x00\x00\x02Sel',b'\x00\x00\x05\x11sy',b'\x00\x00\x05\x11sx',b'\x00\x00\x05\x11cy',b'\x00\x00\x05\x11cx',b'\x00\x01\x44\x11data',b'\x00\x01\x3A\x11name')), + _enums = (b'\x00\x00\x01\x3D\x00\x00\x00\x16$1\x00L_DEFAULT_ENCODE,L_JPEG_ENCODE,L_G4_ENCODE,L_FLATE_ENCODE,L_JP2K_ENCODE',b'\x00\x00\x01\x3E\x00\x00\x00\x16$2\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\x01\x3F\x00\x00\x00\x16$3\x00L_NOCOPY,L_INSERT,L_COPY,L_CLONE,L_COPY_CLONE',b'\x00\x00\x01\x40\x00\x00\x00\x16$4\x00L_USE_WIDTHS,L_USE_WINDOWS',b'\x00\x00\x01\x41\x00\x00\x00\x16$5\x00L_BF_UNKNOWN,L_BF_ANY,L_BF_CODE128,L_BF_EAN8,L_BF_EAN13,L_BF_CODE2OF5,L_BF_CODEI2OF5,L_BF_CODE39,L_BF_CODE93,L_BF_CODABAR,L_BF_UPCA',b'\x00\x00\x01\x42\x00\x00\x00\x16$6\x00L_SEVERITY_EXTERNAL,L_SEVERITY_ALL,L_SEVERITY_DEBUG,L_SEVERITY_INFO,L_SEVERITY_WARNING,L_SEVERITY_ERROR,L_SEVERITY_NONE',b'\x00\x00\x01\x43\x00\x00\x00\x16$7\x00SEL_DONT_CARE,SEL_HIT,SEL_MISS'), + _typenames = (b'\x00\x00\x01\x2FBOX',b'\x00\x00\x01\x30BOXA',b'\x00\x00\x01\x32L_COMP_DATA',b'\x00\x00\x01\x33PIX',b'\x00\x00\x01\x34PIXA',b'\x00\x00\x01\x35PIXCMAP',b'\x00\x00\x01\x37SARRAY',b'\x00\x00\x01\x38SEL',b'\x00\x00\x00\x32l_float32',b'\x00\x00\x01\x3Cl_float64',b'\x00\x00\x01\x46l_int16',b'\x00\x00\x00\x05l_int32',b'\x00\x00\x01\x45l_int64',b'\x00\x00\x01\x47l_int8',b'\x00\x00\x00\x05l_ok',b'\x00\x00\x01\x4El_uint16',b'\x00\x00\x01\x4Cl_uint32',b'\x00\x00\x01\x4Dl_uint64',b'\x00\x00\x01\x4Al_uint8'), ) diff --git a/src/ocrmypdf/lib/compile_leptonica.py b/src/ocrmypdf/lib/compile_leptonica.py index ff1e63b0..87712db2 100644 --- a/src/ocrmypdf/lib/compile_leptonica.py +++ b/src/ocrmypdf/lib/compile_leptonica.py @@ -135,6 +135,18 @@ struct L_Compressed_Data }; typedef struct L_Compressed_Data L_COMP_DATA; +/*! Selection */ +struct Sel +{ + l_int32 sy; /*!< sel height */ + l_int32 sx; /*!< sel width */ + l_int32 cy; /*!< y location of sel origin */ + l_int32 cx; /*!< x location of sel origin */ + l_int32 **data; /*!< {0,1,2}; data[i][j] in [row][col] order */ + char *name; /*!< used to find sel by name */ +}; +typedef struct Sel SEL; + 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 */ @@ -184,6 +196,12 @@ enum { L_SEVERITY_NONE = 6 /* Highest severity: print no messages */ }; +enum { + SEL_DONT_CARE = 0, + SEL_HIT = 1, + SEL_MISS = 2 +}; + """) ffibuilder.cdef(""" @@ -407,12 +425,34 @@ l_generateCIDataForPdf(const char *fname, l_int32 quality, L_COMP_DATA **pcid); + BOX * boxClone ( BOX *box ); BOX * boxaGetBox ( BOXA *boxa, l_int32 index, l_int32 accessflag ); +SEL * +selCreateFromString ( const char *text, l_int32 h, l_int32 w, const char *name ); + +SEL * +selCreateBrick ( l_int32 h, l_int32 w, l_int32 cy, l_int32 cx, l_int32 type ); + +char * +selPrintToString(SEL *sel); + +PIX * +pixDilate ( PIX *pixd, PIX *pixs, SEL *sel ); + +PIX * +pixErode ( PIX *pixd, PIX *pixs, SEL *sel ); + +PIX * +pixHMT ( PIX *pixd, PIX *pixs, SEL *sel ); + +PIX * +pixSubtract ( PIX *pixd, PIX *pixs1, PIX *pixs2 ); + void boxDestroy(BOX **pbox); @@ -434,8 +474,11 @@ sarrayDestroy(SARRAY **psa); void lept_free(void *ptr); +void selDestroy ( SEL **psel ); + l_int32 setMsgSeverity(l_int32 newsev); + """) From 5ed05e08b1f71e663316a4b2c8b1c84967c22701 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Fri, 9 Nov 2018 01:40:01 -0800 Subject: [PATCH 56/68] Fix "no languages" test and misuse of os.environ --- src/ocrmypdf/exec/tesseract.py | 31 +++++++++++++++++++++---------- tests/test_tess4.py | 22 +++++++++++++++++++--- 2 files changed, 40 insertions(+), 13 deletions(-) diff --git a/src/ocrmypdf/exec/tesseract.py b/src/ocrmypdf/exec/tesseract.py index 1dce012c..fde6b066 100644 --- a/src/ocrmypdf/exec/tesseract.py +++ b/src/ocrmypdf/exec/tesseract.py @@ -21,7 +21,7 @@ import shutil from functools import lru_cache from collections import namedtuple from textwrap import dedent -from subprocess import CalledProcessError, TimeoutExpired, check_output, STDOUT +from subprocess import CalledProcessError, TimeoutExpired, check_output, STDOUT, run, PIPE from contextlib import suppress from ..exceptions import MissingDependencyError, TesseractConfigError @@ -92,22 +92,33 @@ def psm(): @lru_cache(maxsize=1) def languages(): + def lang_error(output): + msg = dedent("""Tesseract failed to report available languages. + Output from Tesseract: + ----------- + """) + msg += output + print(msg, file=sys.stderr) + args_tess = [ 'tesseract', '--list-langs' ] try: - langs = check_output( - args_tess, universal_newlines=True, stderr=STDOUT) + proc = run( + args_tess, universal_newlines=True, stdout=PIPE, stderr=STDOUT, + check=True + ) + output = proc.stdout except CalledProcessError as e: - msg = dedent("""Tesseract failed to report available languages. - Output from Tesseract: - ----------- - """) - msg += e.output - print(msg, file=sys.stderr) + lang_error(e.output) raise MissingDependencyError from e - return set(lang.strip() for lang in langs.splitlines()[1:]) + + header, *rest = output.splitlines() + if not header.startswith('List of available languages'): + lang_error(output) + raise MissingDependencyError + return set(lang.strip() for lang in rest) def tess_base_args(langs, engine_mode): diff --git a/tests/test_tess4.py b/tests/test_tess4.py index 859b3cbb..0af3b7e6 100644 --- a/tests/test_tess4.py +++ b/tests/test_tess4.py @@ -16,8 +16,9 @@ # along with OCRmyPDF. If not, see . import pytest -from ocrmypdf.exceptions import ExitCode +from ocrmypdf.exceptions import ExitCode, MissingDependencyError from ocrmypdf.exec import tesseract +from ocrmypdf.helpers import fspath from ocrmypdf import pdfinfo import sys import os @@ -43,6 +44,7 @@ def _ensure_tess4(): tess4 = Path(os.environ['OCRMYPDF_TESS4']) assert tess4.is_file() env['PATH'] = tess4.parent + ':' + env['PATH'] + env['OCRMYPDF_TESS4'] = os.environ['OCRMYPDF_TESS4'] return env raise EnvironmentError("Can't find Tesseract 4") @@ -56,9 +58,12 @@ def ensure_tess4(): @contextmanager def modified_os_environ(env): old_env = os.environ.copy() - os.environ = env + os.environ.update(env) yield - os.environ = old_env + for key in env: + del os.environ[key] + if key in old_env: + os.environ[key] = old_env[key] def tess4_available(): @@ -149,3 +154,14 @@ def test_content_preservation(ensure_tess4, resources, outpdf): info = pdfinfo.PdfInfo(outpdf) page = info[0] assert len(page.images) > 1, "masks were rasterized" + + +def test_no_languages(ensure_tess4, tmpdir): + env = ensure_tess4 + (tmpdir / 'tessdata').mkdir() + env['TESSDATA_PREFIX'] = fspath(tmpdir) + + with modified_os_environ(env): + with pytest.raises(MissingDependencyError): + tesseract.languages.cache_clear() + tesseract.languages() From eed04243904909e7be61f038d4c17f757fbab7ba Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Sat, 10 Nov 2018 00:56:04 -0800 Subject: [PATCH 57/68] Update requirements --- requirements/dev.txt | 2 +- requirements/main.txt | 10 +++++----- requirements/test.txt | 2 +- setup.py | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/requirements/dev.txt b/requirements/dev.txt index e74bb3a7..4faf987d 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -1,4 +1,4 @@ check-manifest >= 0.35 twine >= 1.8.1 -coverage >= 4.4 +coverage >= 4.5 GitPython == 2.1.3 diff --git a/requirements/main.txt b/requirements/main.txt index 8f1fd896..6509cfd3 100644 --- a/requirements/main.txt +++ b/requirements/main.txt @@ -2,11 +2,11 @@ # setup.py lists a separate set of requirements that are looser to simplify # installation cffi == 1.11.5 -img2pdf == 0.3.0 -pdfminer == 20170720 +img2pdf == 0.3.1 +pdfminer == 20181108 pikepdf == 0.3.7 Pillow >= 5.0.0, != 5.1.0 ; sys_platform == "darwin" -pycparser == 2.18 +pycparser == 2.19 python-xmp-toolkit == 2.0.1 -reportlab == 3.4.0 -ruffus == 2.7.0 +reportlab == 3.5.9 +ruffus == 2.8.0 diff --git a/requirements/test.txt b/requirements/test.txt index c1d250b5..6476c892 100644 --- a/requirements/test.txt +++ b/requirements/test.txt @@ -1,4 +1,4 @@ -pytest >= 3.2 +pytest >= 3.10 pytest-helpers-namespace pytest-xdist pytest-cov diff --git a/setup.py b/setup.py index c2336467..a87a7590 100644 --- a/setup.py +++ b/setup.py @@ -250,8 +250,8 @@ setup( ], install_requires=[ 'cffi >= 1.9.1', # must be a setup and install requirement - 'img2pdf >= 0.2.4, < 0.4', # pure Python, so track HEAD closely - 'pdfminer.six == 20170720', + 'img2pdf >= 0.3.0, < 0.4', # pure Python, so track HEAD closely + 'pdfminer.six == 20181108', 'pikepdf >= 0.3.7, < 0.4', 'Pillow >= 4.0.0, != 5.1.0 ; sys_platform == "darwin"', # Pillow < 4 has BytesIO/TIFF bug w/img2pdf 0.2.3 From a2170ef8d6b4eb1ee6ce550cb380c7df0ea30e6c Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Sat, 10 Nov 2018 00:56:22 -0800 Subject: [PATCH 58/68] test: test version check code --- tests/test_main.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/tests/test_main.py b/tests/test_main.py index 2492648c..b74c03aa 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -28,7 +28,7 @@ import PIL import pytest from ocrmypdf.pdfinfo import PdfInfo, Colorspace, Encoding -from ocrmypdf.exceptions import ExitCode +from ocrmypdf.exceptions import ExitCode, MissingDependencyError from ocrmypdf.exec import ghostscript, qpdf, tesseract from ocrmypdf.pdfa import file_claims_pdfa from ocrmypdf.leptonica import Pix @@ -945,3 +945,16 @@ def test_livecycle(resources, no_outpdf): ) assert p.returncode == ExitCode.input_file, err + + +def test_version_check(): + from ocrmypdf.exec import get_version + + with pytest.raises(MissingDependencyError): + get_version('NOT_FOUND_UNLIKELY_ON_PATH') + + with pytest.raises(MissingDependencyError): + get_version('sh', version_arg='-c') + + with pytest.raises(MissingDependencyError): + get_version('echo') From 0e88b3c38a397f4b86c4d80138169d3a6c6d7108 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Sat, 10 Nov 2018 01:09:19 -0800 Subject: [PATCH 59/68] Update v7.3.0 release notes --- docs/release_notes.rst | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/docs/release_notes.rst b/docs/release_notes.rst index ba5c6bf6..ad77b7fb 100644 --- a/docs/release_notes.rst +++ b/docs/release_notes.rst @@ -14,6 +14,26 @@ Note that it is licensed under GPLv3, so scripts that ``import ocrmypdf`` and ar replace: `#$1 `_ +v7.3.0 +------ + +- Added a new feature ``--redo-ocr`` to detect existing OCR in a file, remove it, and redo the OCR. This may be particularly helpful for anyone who wants to take advantage of OCR quality improvements in Tesseract 4.0. Note that OCR added by OCRmyPDF before version 3.0 cannot be detected since it was not properly marked as invisible text in the earliest versions. OCR that constructs a font from visible text, such as Adobe Acrobat's ClearScan. + +- OCRmyPDF's content detection is generally more sophisticated. It learns more about the contents of each PDF and makes better recommendations: + + - OCRmyPDF can now detect when a PDF contains text that cannot be mapped to Unicode (meaning it is readable to human eyes but copy-pastes as gibberish). In these cases it recommends ``--force-ocr`` to make the text searchable. + + - PDFs containing vector objects are now rendered at more appropriate resolution for OCR. + + - We now exit with an error for PDFs that contain Adobe LiveCycle Designer's dynamic XFA forms. Currently the open source community does not have tools to work with these files. + + - OCRmyPDF now warns when a PDF that contains Adobe AcroForms, since such files probably do not need OCR. It can work with these files. + +- Added a new feature ``--mask-barcodes`` to detect and suppress barcodes in files. We have observed that barcodes can interfere with OCR. + +- Fixed an issue where an error message was not reported when the installed Ghostscript was very old. + + v7.2.1 ------ From eacd26a68bd8cde34885d898515b02b4d84b4d53 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Sun, 28 Oct 2018 16:19:37 -0700 Subject: [PATCH 60/68] Mention v6.2.5 release --- docs/release_notes.rst | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/release_notes.rst b/docs/release_notes.rst index ad77b7fb..7778e920 100644 --- a/docs/release_notes.rst +++ b/docs/release_notes.rst @@ -172,6 +172,15 @@ v7.0.0 + It may be necessary to separately ``pip install pycparser`` to avoid `another Python 3.7 issue `_. +v6.2.5 +------ + +- Disable a failing test due to Tesseract 4.0rc1 behavior change. Previously, Tesseract would exit with an error message if its configuration was invalid, and OCRmyPDF would intercept this message. Now Tesseract issues a warning, which OCRmyPDF v6.2.5 may relay or ignore. (In v7.x, OCRmyPDF will respond to the warning.) + +- This release branch no longer supports using the optional PyMuPDF installation, since it was removed in v7.x. + +- This release branch no longer supports macOS. macOS users should upgrade to v7.x. + v6.2.4 ------ From e3fce112ed546464525c0bcd3b8c6ff3449cd742 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Sat, 10 Nov 2018 01:31:51 -0800 Subject: [PATCH 61/68] main.txt: wrong pdfminer --- docs/release_notes.rst | 1 + requirements/main.txt | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/release_notes.rst b/docs/release_notes.rst index 7778e920..b6be039f 100644 --- a/docs/release_notes.rst +++ b/docs/release_notes.rst @@ -33,6 +33,7 @@ v7.3.0 - Fixed an issue where an error message was not reported when the installed Ghostscript was very old. +- New dependency: pdfminer.six 20181108. v7.2.1 ------ diff --git a/requirements/main.txt b/requirements/main.txt index 6509cfd3..8f4a227e 100644 --- a/requirements/main.txt +++ b/requirements/main.txt @@ -3,7 +3,7 @@ # installation cffi == 1.11.5 img2pdf == 0.3.1 -pdfminer == 20181108 +pdfminer.six == 20181108 pikepdf == 0.3.7 Pillow >= 5.0.0, != 5.1.0 ; sys_platform == "darwin" pycparser == 2.19 From 16a6fd2ea980ade070dad86f7c317395c0a17fe5 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Sat, 10 Nov 2018 01:34:33 -0800 Subject: [PATCH 62/68] Update docs for --redo-ocr and --mask-barcodes --- docs/advanced.rst | 4 +++- docs/cookbook.rst | 31 +++++++++++-------------------- docs/introduction.rst | 8 ++++---- 3 files changed, 18 insertions(+), 25 deletions(-) diff --git a/docs/advanced.rst b/docs/advanced.rst index bc84c5a0..a0f7b7d8 100644 --- a/docs/advanced.rst +++ b/docs/advanced.rst @@ -13,7 +13,9 @@ If a page in a PDF seems to have text, by default OCRmyPDF will exit without mod If ``--skip-text`` is issued, then no OCR will be performed on pages that already have text. The page will be copied to the output. This may be useful for documents that contain both "born digital" and scanned content, or to use OCRmyPDF to normalize and convert to PDF/A regardless of their contents. -If ``--force-ocr`` is issued, then all pages will be rasterized to images, discarding any hidden OCR text, and rasterizing any printable text. This is useful for redoing OCR, for fixing OCR text with a damaged character map (text is selectable but not searchable), and destroying redacted information. +If ``--redo-ocr`` is issued, then a detailed text analysis is performed. Text is categorized as either visible or invisible. Invisible text (OCR) is stripped out. Then an image of each page is created with visible text masked out. The page image is sent for OCR, and any additional text is inserted as OCR. If a file contains a mix of text and bitmap images that contain text, OCRmyPDF will locate the additional text in images without disrupting the existing text. + +If ``--force-ocr`` is issued, then all pages will be rasterized to images, discarding any hidden OCR text, and rasterizing any printable text. This is useful for redoing OCR, for fixing OCR text with a damaged character map (text is selectable but not searchable), and destroying redacted information. Any forms and vector graphics will be rasterized as well. Time and image size limits diff --git a/docs/cookbook.rst b/docs/cookbook.rst index db2c2bf5..996062d9 100644 --- a/docs/cookbook.rst +++ b/docs/cookbook.rst @@ -57,7 +57,6 @@ You can increase (decrease) the parameter ``--rotate-pages-threshold`` to make p If the page is "just a little off horizontal", like a crooked picture, then you want ``--deskew``. ``--rotate-pages`` is for when the cardinal angle is wrong. - OCR languages other than English """""""""""""""""""""""""""""""" @@ -70,7 +69,6 @@ By default OCRmyPDF assumes the document is English. Language packs must be installed for all languages specified. See :ref:`Installing additional language packs `. - Produce PDF and text file containing OCR text """"""""""""""""""""""""""""""""""""""""""""" @@ -116,7 +114,6 @@ If you have multiple images, you must use ``img2pdf`` to convert the images to P ImageMagick ``convert`` can also convert a group of images to PDF, but in the author's experience it takes a long time, transcodes unnecessarily and gives poor results. - Image processing ---------------- @@ -132,6 +129,8 @@ OCRmyPDF perform some image processing on each page of a PDF, if desired. The s * ``--clean-final`` uses unpaper to clean up pages before OCR and inserts the page into the final output. You will want to review each page to ensure that unpaper did not remove something important. +* ``-mask-barcodes`` will "cover up" any barcodes detected in the image of a page. Barcodes are known to confuse Tesseract OCR and interfere with the recognition of text on the same baseline as a barcode. The output file will contain the unaltered image of the barcode. + .. note:: In many cases image processing will rasterize PDF pages as images, potentially losing quality. @@ -140,7 +139,6 @@ OCRmyPDF perform some image processing on each page of a PDF, if desired. The s ``--clean-final`` and ``-remove-background`` may leave undesirable visual artifacts in some images where their algorithms have shortcomings. Files should be visually reviewed after using these options. - OCR and correct document skew (crooked scan) """""""""""""""""""""""""""""""""""""""""""" @@ -167,28 +165,22 @@ 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 -"""""""" +Redo existing 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. +To redo OCR on a file OCRed with other OCR software or a previous version of OCRmyPDF and/or Tesseract, you may use the ``--redo-ocr`` argument. (Normally, OCRmyPDF will exit with an error if asked to modify a file with OCR.) + +This may be helpful for users who want to take advantage of accuracy improvements in Tesseract 4.0 for files they previously OCRed with an earlier version of Tesseract and OCRmyPDF. .. code-block:: bash - ocrmypdf --force-ocr input.pdf output.pdf + ocrmypdf --redo-ocr input.pdf output.pdf -Note that the method above will force rasterization of all pages, potentially reducing quality or losing vector content. +This method will replace OCR without rasterizing, reducing quality or removing vector content. If a file contains a mix of pure digital text and OCR, digital text will be ignored and OCR will be replaced. As such this mode is incompatible with image processing options, since they alter the appearance of the file. -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. +In some cases, existing OCR cannot be detected or replaced. Files produced by OCRmyPDF v2.2 or earlier, for example, are internally represented as having visible text with an opaque image drawn on top. This situation cannot be detected. +If ``--redo-ocr`` does not work, you can use ``--force-ocr``, which will force rasterization of all pages, potentially reducing quality or losing vector content. Improving OCR quality --------------------- @@ -199,7 +191,6 @@ Rotating pages and deskewing helps to ensure that the page orientation is correc OCR quality will suffer if the resolution of input images is not correct (since the range of pixel sizes that will be checked for possible fonts will also be incorrect). - PDF optimization ---------------- diff --git a/docs/introduction.rst b/docs/introduction.rst index 7038d3fc..c4b4f52d 100644 --- a/docs/introduction.rst +++ b/docs/introduction.rst @@ -64,7 +64,7 @@ In the case of a PDF that is nothing other than a container of images (no rotati OCRmyPDF uses several strategies depending on input options and the input PDF itself, but generally speaking it rasterizes a page for OCR and then grafts the OCR back onto the original. As such it can handle complex PDFs and still preserve their contents as much as possible. -OCRmyPDF also supports a many, many edge cases that have cropped over several years of development. We support PDF features like images inside of Form XObjects, and pages with UserUnit scaling. We support rare image formats like non-monochrome 1-bit images. Thanks to pikepdf and QPDF, we auto-repair PDFs that are damaged. (Not that you need to know what any of these are! You should be able to throw any PDF at it.) +OCRmyPDF also supports a many, many edge cases that have cropped over several years of development. We support PDF features like images inside of Form XObjects, and pages with UserUnit scaling. We support rare image formats like non-monochrome 1-bit images. We warn about files you may not to OCR. Thanks to pikepdf and QPDF, we auto-repair PDFs that are damaged. (Not that you need to know what any of these are! You should be able to throw any PDF at it.) Limitations @@ -76,20 +76,20 @@ OCRmyPDF is limited by the Tesseract OCR engine. As such it experiences these l * It is not capable of recognizing handwriting. * It may find gibberish and report this as OCR output. * If a document contains languages outside of those given in the ``-l LANG`` arguments, results may be poor. -* It is not always good at analyzing the natural reading order of documents. For example, it may fail to recognize that a document contains two columns and join text across the columns. +* It is not always good at analyzing the natural reading order of documents. For example, it may fail to recognize that a document contains two columns, and may try to join text across columns. * Poor quality scans may produce poor quality OCR. Garbage in, garbage out. * It does not expose information about what font family text belongs to. OCRmyPDF is also limited by the PDF specification: * PDF encodes the position of text glyphs but does not encode document structure. There is no markup that divides a document in sections, paragraphs, sentences, or even words (since blank spaces are not represented). As such all elements of document structure including the spaces between words must be derived heuristically. Some PDF viewers do a better job of this than others. -* Because some popular open source PDF viewers have a particularly hard time with spaces betweem words, OCRmyPDF appends a space to each text element as a workaround. While this mixes document structure with graphical information that ideally should be left to the PDF viewer to interpret, it improves compatibility with some viewers and does not cause problems for better ones. +* Because some popular open source PDF viewers have a particularly hard time with spaces betweem words, OCRmyPDF appends a space to each text element as a workaround (when using ``--pdf-renderer hocr``). While this mixes document structure with graphical information that ideally should be left to the PDF viewer to interpret, it improves compatibility with some viewers and does not cause problems for better ones. Ghostscript also imposes some limitations: * PDFs containing JBIG2-encoded content will be converted to CCITT Group4 encoding, which has lower compression ratios, if Ghostscript PDF/A is enabled. * PDFs containing JPEG 2000-encoded content will be converted to JPEG encoding, which may introduce compression artifacts, if Ghostscript PDF/A is enabled. -* Ghostscript may transcode grayscale and color images, either lossy to lossless or lossless to lossy, based on an internal algorithm. This behavior can be suppressed by setting ``--pdfa-image-compression`` to ``jpeg`` or ``lossless`` to set all images to one type or the other. Ghostscript has no option to maintain the input image's format. +* Ghostscript may transcode grayscale and color images, either lossy to lossless or lossless to lossy, based on an internal algorithm. This behavior can be suppressed by setting ``--pdfa-image-compression`` to ``jpeg`` or ``lossless`` to set all images to one type or the other. Ghostscript has no option to maintain the input image's format. (Ghostscript 9.25+ can copy JPEG images without transcoding them; earlier versions will transcode.) Regarding OCRmyPDF itself: From e55a4115e11b67a1f7c1995a198adb01f00fb5df Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Sat, 10 Nov 2018 01:44:05 -0800 Subject: [PATCH 63/68] Travis: pytest 3.10.0 internal error? --- requirements/test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/test.txt b/requirements/test.txt index 6476c892..f28432a3 100644 --- a/requirements/test.txt +++ b/requirements/test.txt @@ -1,4 +1,4 @@ -pytest >= 3.10 +pytest == 3.9.3 pytest-helpers-namespace pytest-xdist pytest-cov From 755b5d87e39dc4f47c10ed1bddc5039ba72d2537 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Sat, 10 Nov 2018 01:50:51 -0800 Subject: [PATCH 64/68] Add missing chardet, implied by pdfminer.six? --- requirements/main.txt | 1 + setup.py | 1 + 2 files changed, 2 insertions(+) diff --git a/requirements/main.txt b/requirements/main.txt index 8f4a227e..bfee425e 100644 --- a/requirements/main.txt +++ b/requirements/main.txt @@ -4,6 +4,7 @@ cffi == 1.11.5 img2pdf == 0.3.1 pdfminer.six == 20181108 +chardet == 3.0.4 pikepdf == 0.3.7 Pillow >= 5.0.0, != 5.1.0 ; sys_platform == "darwin" pycparser == 2.19 diff --git a/setup.py b/setup.py index a87a7590..2af9de35 100644 --- a/setup.py +++ b/setup.py @@ -249,6 +249,7 @@ setup( 'src/ocrmypdf/lib/compile_leptonica.py:ffibuilder' ], install_requires=[ + 'chardet >= 3.0.0', 'cffi >= 1.9.1', # must be a setup and install requirement 'img2pdf >= 0.3.0, < 0.4', # pure Python, so track HEAD closely 'pdfminer.six == 20181108', From cc7f2a3f02873d9e7e635c1aac4b4959bb781c9f Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Sat, 10 Nov 2018 02:11:23 -0800 Subject: [PATCH 65/68] Fix Python 3.5 pathlib regressions --- src/ocrmypdf/exec/qpdf.py | 3 ++- src/ocrmypdf/exec/tesseract.py | 4 ++-- tests/test_lept.py | 4 +++- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/ocrmypdf/exec/qpdf.py b/src/ocrmypdf/exec/qpdf.py index 33bef631..df8dca34 100644 --- a/src/ocrmypdf/exec/qpdf.py +++ b/src/ocrmypdf/exec/qpdf.py @@ -21,6 +21,7 @@ from functools import lru_cache from ..exceptions import InputFileError, SubprocessOutputError, \ EncryptedPdfError from . import get_version +from ..helpers import fspath @lru_cache(maxsize=1) @@ -32,7 +33,7 @@ def check(input_file, log=None): args_qpdf = [ 'qpdf', '--check', - input_file + fspath(input_file) ] if log is None: diff --git a/src/ocrmypdf/exec/tesseract.py b/src/ocrmypdf/exec/tesseract.py index fde6b066..13ff838b 100644 --- a/src/ocrmypdf/exec/tesseract.py +++ b/src/ocrmypdf/exec/tesseract.py @@ -25,7 +25,7 @@ from subprocess import CalledProcessError, TimeoutExpired, check_output, STDOUT, from contextlib import suppress from ..exceptions import MissingDependencyError, TesseractConfigError -from ..helpers import page_number +from ..helpers import page_number, fspath from . import get_version OrientationConfidence = namedtuple( @@ -135,7 +135,7 @@ def tess_base_args(langs, engine_mode): def get_orientation(input_file, engine_mode, timeout: float, log): args_tesseract = tess_base_args(['osd'], engine_mode) + [ psm(), '0', - input_file, + fspath(input_file), 'stdout' ] diff --git a/tests/test_lept.py b/tests/test_lept.py index 04280b83..85e2482e 100644 --- a/tests/test_lept.py +++ b/tests/test_lept.py @@ -25,6 +25,7 @@ import pytest from PIL import Image, ImageChops import ocrmypdf.leptonica as lept +from ocrmypdf.helpers import fspath def test_colormap_backgroundnorm(resources): @@ -86,4 +87,5 @@ def test_leptonica_compile(tmpdir): # Compile the library but build it somewhere that won't interfere with # existing compiled library. Also compile in API mode so that we test # the interfaces, even though we use it ABI mode. - ffibuilder.compile(tmpdir=tmpdir, target=(tmpdir / 'lepttest.*')) + ffibuilder.compile(tmpdir=fspath(tmpdir), + target=fspath(tmpdir / 'lepttest.*')) From 0f5c484b626632aa68259eda16ff2c1b87a42104 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Sat, 10 Nov 2018 13:52:43 -0800 Subject: [PATCH 66/68] Travis: only need to specify chardet because we use pip install --no-deps --- requirements/main.txt | 2 +- setup.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/requirements/main.txt b/requirements/main.txt index bfee425e..3b9884b1 100644 --- a/requirements/main.txt +++ b/requirements/main.txt @@ -1,10 +1,10 @@ # requirements.txt can be used to replicate the developer's build environment # setup.py lists a separate set of requirements that are looser to simplify # installation +chardet == 3.0.4 cffi == 1.11.5 img2pdf == 0.3.1 pdfminer.six == 20181108 -chardet == 3.0.4 pikepdf == 0.3.7 Pillow >= 5.0.0, != 5.1.0 ; sys_platform == "darwin" pycparser == 2.19 diff --git a/setup.py b/setup.py index 2af9de35..a87a7590 100644 --- a/setup.py +++ b/setup.py @@ -249,7 +249,6 @@ setup( 'src/ocrmypdf/lib/compile_leptonica.py:ffibuilder' ], install_requires=[ - 'chardet >= 3.0.0', 'cffi >= 1.9.1', # must be a setup and install requirement 'img2pdf >= 0.3.0, < 0.4', # pure Python, so track HEAD closely 'pdfminer.six == 20181108', From 701ef1df3f4d126840596a9b1718fb4ef7640c6b Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Sat, 10 Nov 2018 15:34:37 -0800 Subject: [PATCH 67/68] Add threshold function to work around Tesseract's poor thresholding of bright backgrounds --- src/ocrmypdf/__main__.py | 4 +++ src/ocrmypdf/_pipeline.py | 21 +++++++----- src/ocrmypdf/leptonica.py | 49 +++++++++++++++------------ src/ocrmypdf/lib/_leptonica.py | 10 +++--- src/ocrmypdf/lib/compile_leptonica.py | 1 + 5 files changed, 50 insertions(+), 35 deletions(-) diff --git a/src/ocrmypdf/__main__.py b/src/ocrmypdf/__main__.py index 511b0cf1..2caf45a4 100755 --- a/src/ocrmypdf/__main__.py +++ b/src/ocrmypdf/__main__.py @@ -253,6 +253,10 @@ preprocessing.add_argument( help="Mask out any barcodes that appear in the PDF so they are not " "considered during OCR. Barcodes can introduce false characters into " "OCR.") +preprocessing.add_argument( + '--threshold', action='store_true', + help="Threshold image to 1bpp before sending it to Tesseract for OCR. Can " + "improve OCR quality compared to Tesseract's thresholder.") ocrsettings = parser.add_argument_group( "OCR options", diff --git a/src/ocrmypdf/_pipeline.py b/src/ocrmypdf/_pipeline.py index 13fa51ce..4e0ff44f 100644 --- a/src/ocrmypdf/_pipeline.py +++ b/src/ocrmypdf/_pipeline.py @@ -631,17 +631,20 @@ def select_ocr_image( log.debug('blanking %r', pixcoords) draw.rectangle(pixcoords, fill=white) #draw.rectangle(pixcoords, outline=pink) - - if options.mask_barcodes: - pix = leptonica.Pix.open(image) - barcodes = pix.locate_barcodes() - for barcode in barcodes: - decoded, rect = barcode - log.info('masking barcode %s %r', decoded, rect) - draw.rectangle(rect, fill=white) - del draw + if options.mask_barcodes or options.threshold: + pix = leptonica.Pix.frompil(im) + if options.threshold: + pix = pix.masked_threshold_on_background_norm() + if options.mask_barcodes: + barcodes = pix.locate_barcodes() + for barcode in barcodes: + decoded, rect = barcode + log.info('masking barcode %s %r', decoded, rect) + draw.rectangle(rect, fill=white) + im = pix.topil() + # Pillow requires integer DPI dpi = round(xres), round(yres) im.save(output_file, dpi=dpi) diff --git a/src/ocrmypdf/leptonica.py b/src/ocrmypdf/leptonica.py index 756ba924..644acd70 100644 --- a/src/ocrmypdf/leptonica.py +++ b/src/ocrmypdf/leptonica.py @@ -20,15 +20,16 @@ # # Python FFI wrapper for Leptonica library -import argparse -import sys -import os -import logging -import warnings -from tempfile import TemporaryFile +from collections.abc import Sequence from ctypes.util import find_library from functools import lru_cache -from collections.abc import Sequence +from io import BytesIO +from tempfile import TemporaryFile +import argparse +import logging +import os +import sys +import warnings from .lib._leptonica import ffi from .helpers import fspath @@ -300,8 +301,19 @@ class Pix(LeptonicaObject): os.fsencode(filename), self._cdata, jpeg_quality, jpeg_progressive) + @classmethod + def frompil(self, pillow_image): + """Create a copy of a PIL.Image from this Pix""" + bio = BytesIO() + pillow_image.save(bio, format='png', compress_level=1) + py_buffer = bio.getbuffer() + c_buffer = ffi.from_buffer(py_buffer) + with _LeptonicaErrorTrap(): + pix = Pix(lept.pixReadMem(c_buffer, len(c_buffer))) + return pix + def topil(self): - "Returns a PIL.Image version of this Pix" + """Returns a PIL.Image version of this Pix""" from PIL import Image # Leptonica manages data in words, so it implicitly does an endian @@ -416,8 +428,7 @@ class Pix(LeptonicaObject): with _LeptonicaErrorTrap(): sx, sy = tile_size smoothx, smoothy = kernel_size - if mask is None: - mask = ffi.NULL + mask = ffi.NULL if isinstance(mask, Pix): mask = mask._cdata @@ -429,9 +440,7 @@ class Pix(LeptonicaObject): smoothx, smoothy, scorefract, ffi.NULL - ) - if thresh_pix == ffi.NULL: - return None + ) return Pix(thresh_pix) def masked_threshold_on_background_norm( @@ -440,23 +449,21 @@ class Pix(LeptonicaObject): with _LeptonicaErrorTrap(): sx, sy = tile_size smoothx, smoothy = kernel_size - if mask is None: - mask = ffi.NULL + mask = ffi.NULL if isinstance(mask, Pix): mask = mask._cdata - new_pix = lept.pixMaskedThreshOnBackgroundNorm( - self._cdata, + pix = Pix(lept.pixConvertTo8(self._cdata, 0)) + thresh_pix = lept.pixMaskedThreshOnBackgroundNorm( + pix._cdata, mask, sx, sy, thresh, mincount, smoothx, smoothy, scorefract, ffi.NULL - ) - if new_pix == ffi.NULL: - return None - return Pix(new_pix) + ) + return Pix(thresh_pix) def crop_to_foreground( self, threshold=128, mindist=70, erasedist=30, pagenum=0, diff --git a/src/ocrmypdf/lib/_leptonica.py b/src/ocrmypdf/lib/_leptonica.py index 502fd705..17a2f757 100644 --- a/src/ocrmypdf/lib/_leptonica.py +++ b/src/ocrmypdf/lib/_leptonica.py @@ -3,9 +3,9 @@ import _cffi_backend ffi = _cffi_backend.FFI('ocrmypdf.lib._leptonica', _version = 0x2601, - _types = b'\x00\x00\x01\x0D\x00\x01\x2F\x03\x00\x00\x00\x0F\x00\x00\x01\x0D\x00\x01\x30\x03\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x01\x0D\x00\x01\x33\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\x01\x3B\x03\x00\x00\x00\x0F\x00\x00\x01\x0D\x00\x01\x34\x03\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x04\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x09\x03\x00\x00\x1A\x11\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x01\x11\x00\x00\x01\x03\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x0D\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x09\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\x09\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x01\x38\x03\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x0D\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x09\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\x0D\x01\x00\x00\x05\x03\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x09\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\x60\x11\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x0D\x01\x00\x00\x0D\x01\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x13\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x10\x11\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x01\x4C\x03\x00\x00\x1C\x01\x00\x00\x00\x0F\x00\x00\x13\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x01\x36\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x01\x36\x03\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x01\x36\x0D\x00\x00\x13\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x98\x11\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x45\x0D\x00\x00\x10\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x10\x11\x00\x00\x00\x0F\x00\x00\x45\x0D\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\x01\x3A\x0D\x00\x00\x45\x11\x00\x00\x00\x0F\x00\x01\x3A\x0D\x00\x00\x00\x0F\x00\x00\x60\x0D\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x04\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x32\x03\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x60\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\xBF\x11\x00\x00\xBF\x11\x00\x00\xBF\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\xBF\x11\x00\x00\xBF\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x60\x11\x00\x00\x60\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x0D\x01\x00\x00\x07\x01\x00\x00\x60\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x01\x31\x03\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\xBF\x11\x00\x00\xBF\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x0D\x01\x00\x00\x1A\x11\x00\x00\x1A\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x01\x4B\x03\x00\x00\x8E\x03\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x10\x11\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\xE8\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x10\x11\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x01\x49\x03\x00\x01\x00\x11\x00\x00\x09\x11\x00\x00\x0D\x01\x00\x00\x00\x0F\x00\x01\x4F\x0D\x00\x00\x23\x11\x00\x00\x00\x0F\x00\x01\x4F\x0D\x00\x00\x04\x03\x00\x00\x00\x0F\x00\x01\x4F\x0D\x00\x00\xE8\x11\x00\x00\x00\x0F\x00\x01\x4F\x0D\x00\x00\x1A\x11\x00\x00\x00\x0F\x00\x01\x4F\x0D\x00\x00\x13\x03\x00\x00\x00\x0F\x00\x01\x4F\x0D\x00\x00\x98\x11\x00\x00\x00\x0F\x00\x01\x4F\x0D\x00\x00\x45\x03\x00\x00\x00\x0F\x00\x01\x4F\x0D\x00\x01\x4F\x03\x00\x00\x00\x0F\x00\x00\x00\x09\x00\x00\x01\x09\x00\x01\x32\x03\x00\x00\x02\x09\x00\x00\x03\x09\x00\x00\x05\x09\x00\x00\x04\x09\x00\x01\x37\x03\x00\x00\x06\x09\x00\x00\x07\x09\x00\x01\x3A\x03\x00\x01\x3B\x03\x00\x00\x02\x01\x00\x00\x0E\x01\x00\x00\x00\x0B\x00\x00\x01\x0B\x00\x00\x02\x0B\x00\x00\x03\x0B\x00\x00\x04\x0B\x00\x00\x05\x0B\x00\x00\x06\x0B\x00\x00\x60\x03\x00\x00\x0B\x01\x00\x00\x05\x01\x00\x00\x03\x01\x00\x01\x35\x03\x00\x01\x4A\x03\x00\x00\x04\x01\x00\x01\x4C\x03\x00\x00\x08\x01\x00\x00\x0C\x01\x00\x00\x06\x01\x00\x00\x00\x01', - _globals = (b'\xFF\xFF\xFF\x0BL_BF_ANY',1,b'\xFF\xFF\xFF\x0BL_BF_CODABAR',9,b'\xFF\xFF\xFF\x0BL_BF_CODE128',2,b'\xFF\xFF\xFF\x0BL_BF_CODE2OF5',5,b'\xFF\xFF\xFF\x0BL_BF_CODE39',7,b'\xFF\xFF\xFF\x0BL_BF_CODE93',8,b'\xFF\xFF\xFF\x0BL_BF_CODEI2OF5',6,b'\xFF\xFF\xFF\x0BL_BF_EAN13',4,b'\xFF\xFF\xFF\x0BL_BF_EAN8',3,b'\xFF\xFF\xFF\x0BL_BF_UNKNOWN',0,b'\xFF\xFF\xFF\x0BL_BF_UPCA',10,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_DEFAULT_ENCODE',0,b'\xFF\xFF\xFF\x0BL_FLATE_ENCODE',3,b'\xFF\xFF\xFF\x0BL_G4_ENCODE',2,b'\xFF\xFF\xFF\x0BL_INSERT',0,b'\xFF\xFF\xFF\x0BL_JP2K_ENCODE',4,b'\xFF\xFF\xFF\x0BL_JPEG_ENCODE',1,b'\xFF\xFF\xFF\x0BL_NOCOPY',0,b'\xFF\xFF\xFF\x0BL_SEVERITY_ALL',1,b'\xFF\xFF\xFF\x0BL_SEVERITY_DEBUG',2,b'\xFF\xFF\xFF\x0BL_SEVERITY_ERROR',5,b'\xFF\xFF\xFF\x0BL_SEVERITY_EXTERNAL',0,b'\xFF\xFF\xFF\x0BL_SEVERITY_INFO',3,b'\xFF\xFF\xFF\x0BL_SEVERITY_NONE',6,b'\xFF\xFF\xFF\x0BL_SEVERITY_WARNING',4,b'\xFF\xFF\xFF\x0BL_USE_WIDTHS',1,b'\xFF\xFF\xFF\x0BL_USE_WINDOWS',2,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'\xFF\xFF\xFF\x0BSEL_DONT_CARE',0,b'\xFF\xFF\xFF\x0BSEL_HIT',1,b'\xFF\xFF\xFF\x0BSEL_MISS',2,b'\x00\x00\x00\x23boxClone',0,b'\x00\x01\x17\x23boxDestroy',0,b'\x00\x01\x1A\x23boxaDestroy',0,b'\x00\x00\x03\x23boxaGetBox',0,b'\x00\x00\xB2\x23getLeptonicaVersion',0,b'\x00\x01\x1D\x23l_CIDataDestroy',0,b'\x00\x01\x02\x23l_generateCIDataForPdf',0,b'\x00\x01\x2C\x23lept_free',0,b'\x00\x00\xB4\x23makePixelSumTab8',0,b'\x00\x00\x29\x23pixAnd',0,b'\x00\x00\x36\x23pixBackgroundNorm',0,b'\x00\x00\x2E\x23pixCleanBackgroundToWhite',0,b'\x00\x00\x20\x23pixClipRectangle',0,b'\x00\x00\xEA\x23pixColorFraction',0,b'\x00\x00\x7D\x23pixColorMagnitude',0,b'\x00\x00\x1D\x23pixConvertRGBToLuminance',0,b'\x00\x00\x74\x23pixConvertTo8',0,b'\x00\x00\xBC\x23pixCorrelationBinary',0,b'\x00\x00\xD6\x23pixCountPixels',0,b'\x00\x00\x8C\x23pixDeserializeFromMemory',0,b'\x00\x00\x74\x23pixDeskew',0,b'\x00\x01\x20\x23pixDestroy',0,b'\x00\x00\x42\x23pixDilate',0,b'\x00\x00\x1D\x23pixEndianByteSwapNew',0,b'\x00\x00\xC1\x23pixEqual',0,b'\x00\x00\x42\x23pixErode',0,b'\x00\x00\x90\x23pixExtractBarcodes',0,b'\x00\x00\x08\x23pixFindPageForeground',0,b'\x00\x00\xD1\x23pixFindSkew',0,b'\x00\x00\x47\x23pixGammaTRC',0,b'\x00\x00\xE3\x23pixGenerateCIData',0,b'\x00\x00\xC6\x23pixGetAverageMaskedRGB',0,b'\x00\x00\x4E\x23pixGlobalNormRGB',0,b'\x00\x00\x42\x23pixHMT',0,b'\x00\x00\x25\x23pixInvert',0,b'\x00\x00\x17\x23pixLocateBarcodes',0,b'\x00\x00\x78\x23pixMaskOverColorPixels',0,b'\x00\x00\x56\x23pixMaskedThreshOnBackgroundNorm',0,b'\x00\x00\xDB\x23pixNumSignificantGrayColors',0,b'\x00\x00\xF3\x23pixOtsuAdaptiveThreshold',0,b'\x00\x00\x62\x23pixOtsuThreshOnBackgroundNorm',0,b'\x00\x00\x94\x23pixProcessBarcodes',0,b'\x00\x00\x89\x23pixRead',0,b'\x00\x00\x9B\x23pixReadBarcodes',0,b'\x00\x00\x74\x23pixRemoveColormap',0,b'\x00\x00\x78\x23pixRemoveColormapGeneral',0,b'\x00\x00\xB6\x23pixRenderBoxa',0,b'\x00\x00\x25\x23pixRotate180',0,b'\x00\x00\x74\x23pixRotateOrth',0,b'\x00\x00\x6F\x23pixScale',0,b'\x00\x00\xFD\x23pixSerializeToMemory',0,b'\x00\x00\x29\x23pixSubtract',0,b'\x00\x01\x08\x23pixWriteImpliedFormat',0,b'\x00\x01\x11\x23pixWriteMemPng',0,b'\x00\x01\x23\x23pixaDestroy',0,b'\x00\x00\x12\x23pixaGetBox',0,b'\x00\x00\x84\x23pixaGetPix',0,b'\x00\x01\x26\x23sarrayDestroy',0,b'\x00\x00\xA8\x23selCreateBrick',0,b'\x00\x00\xA2\x23selCreateFromString',0,b'\x00\x01\x29\x23selDestroy',0,b'\x00\x00\xAF\x23selPrintToString',0,b'\x00\x01\x0E\x23setMsgSeverity',0), - _struct_unions = ((b'\x00\x00\x01\x2F\x00\x00\x00\x02Box',b'\x00\x00\x05\x11x',b'\x00\x00\x05\x11y',b'\x00\x00\x05\x11w',b'\x00\x00\x05\x11h',b'\x00\x01\x4C\x11refcount'),(b'\x00\x00\x01\x30\x00\x00\x00\x02Boxa',b'\x00\x00\x05\x11n',b'\x00\x00\x05\x11nalloc',b'\x00\x01\x4C\x11refcount',b'\x00\x00\x23\x11box'),(b'\x00\x00\x01\x32\x00\x00\x00\x02L_Compressed_Data',b'\x00\x00\x05\x11type',b'\x00\x01\x49\x11datacomp',b'\x00\x00\x8E\x11nbytescomp',b'\x00\x01\x3A\x11data85',b'\x00\x00\x8E\x11nbytes85',b'\x00\x01\x3A\x11cmapdata85',b'\x00\x01\x3A\x11cmapdatahex',b'\x00\x00\x05\x11ncolors',b'\x00\x00\x05\x11w',b'\x00\x00\x05\x11h',b'\x00\x00\x05\x11bps',b'\x00\x00\x05\x11spp',b'\x00\x00\x05\x11minisblack',b'\x00\x00\x05\x11predictor',b'\x00\x00\x8E\x11nbytes',b'\x00\x00\x05\x11res'),(b'\x00\x00\x01\x33\x00\x00\x00\x02Pix',b'\x00\x01\x4C\x11w',b'\x00\x01\x4C\x11h',b'\x00\x01\x4C\x11d',b'\x00\x01\x4C\x11spp',b'\x00\x01\x4C\x11wpl',b'\x00\x01\x4C\x11refcount',b'\x00\x00\x05\x11xres',b'\x00\x00\x05\x11yres',b'\x00\x00\x05\x11informat',b'\x00\x00\x05\x11special',b'\x00\x01\x3A\x11text',b'\x00\x01\x48\x11colormap',b'\x00\x01\x4B\x11data'),(b'\x00\x00\x01\x35\x00\x00\x00\x02PixColormap',b'\x00\x01\x2D\x11array',b'\x00\x00\x05\x11depth',b'\x00\x00\x05\x11nalloc',b'\x00\x00\x05\x11n'),(b'\x00\x00\x01\x34\x00\x00\x00\x02Pixa',b'\x00\x00\x05\x11n',b'\x00\x00\x05\x11nalloc',b'\x00\x01\x4C\x11refcount',b'\x00\x00\x1A\x11pix',b'\x00\x00\x04\x11boxa'),(b'\x00\x00\x01\x37\x00\x00\x00\x02Sarray',b'\x00\x00\x05\x11nalloc',b'\x00\x00\x05\x11n',b'\x00\x00\x05\x11refcount',b'\x00\x01\x39\x11array'),(b'\x00\x00\x01\x38\x00\x00\x00\x02Sel',b'\x00\x00\x05\x11sy',b'\x00\x00\x05\x11sx',b'\x00\x00\x05\x11cy',b'\x00\x00\x05\x11cx',b'\x00\x01\x44\x11data',b'\x00\x01\x3A\x11name')), - _enums = (b'\x00\x00\x01\x3D\x00\x00\x00\x16$1\x00L_DEFAULT_ENCODE,L_JPEG_ENCODE,L_G4_ENCODE,L_FLATE_ENCODE,L_JP2K_ENCODE',b'\x00\x00\x01\x3E\x00\x00\x00\x16$2\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\x01\x3F\x00\x00\x00\x16$3\x00L_NOCOPY,L_INSERT,L_COPY,L_CLONE,L_COPY_CLONE',b'\x00\x00\x01\x40\x00\x00\x00\x16$4\x00L_USE_WIDTHS,L_USE_WINDOWS',b'\x00\x00\x01\x41\x00\x00\x00\x16$5\x00L_BF_UNKNOWN,L_BF_ANY,L_BF_CODE128,L_BF_EAN8,L_BF_EAN13,L_BF_CODE2OF5,L_BF_CODEI2OF5,L_BF_CODE39,L_BF_CODE93,L_BF_CODABAR,L_BF_UPCA',b'\x00\x00\x01\x42\x00\x00\x00\x16$6\x00L_SEVERITY_EXTERNAL,L_SEVERITY_ALL,L_SEVERITY_DEBUG,L_SEVERITY_INFO,L_SEVERITY_WARNING,L_SEVERITY_ERROR,L_SEVERITY_NONE',b'\x00\x00\x01\x43\x00\x00\x00\x16$7\x00SEL_DONT_CARE,SEL_HIT,SEL_MISS'), - _typenames = (b'\x00\x00\x01\x2FBOX',b'\x00\x00\x01\x30BOXA',b'\x00\x00\x01\x32L_COMP_DATA',b'\x00\x00\x01\x33PIX',b'\x00\x00\x01\x34PIXA',b'\x00\x00\x01\x35PIXCMAP',b'\x00\x00\x01\x37SARRAY',b'\x00\x00\x01\x38SEL',b'\x00\x00\x00\x32l_float32',b'\x00\x00\x01\x3Cl_float64',b'\x00\x00\x01\x46l_int16',b'\x00\x00\x00\x05l_int32',b'\x00\x00\x01\x45l_int64',b'\x00\x00\x01\x47l_int8',b'\x00\x00\x00\x05l_ok',b'\x00\x00\x01\x4El_uint16',b'\x00\x00\x01\x4Cl_uint32',b'\x00\x00\x01\x4Dl_uint64',b'\x00\x00\x01\x4Al_uint8'), + _types = b'\x00\x00\x01\x0D\x00\x01\x33\x03\x00\x00\x00\x0F\x00\x00\x01\x0D\x00\x01\x34\x03\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x01\x0D\x00\x01\x37\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\x01\x3F\x03\x00\x00\x00\x0F\x00\x00\x01\x0D\x00\x01\x38\x03\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x04\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x09\x03\x00\x00\x1A\x11\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x01\x11\x00\x00\x01\x03\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x0D\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x09\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\x09\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x01\x3C\x03\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x0D\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x09\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\x0D\x01\x00\x00\x05\x03\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x09\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\x60\x11\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x0D\x01\x00\x00\x0D\x01\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x13\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x00\x10\x11\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x01\x4E\x03\x00\x00\x1C\x01\x00\x00\x00\x0F\x00\x00\x09\x0D\x00\x01\x50\x03\x00\x00\x1C\x01\x00\x00\x00\x0F\x00\x00\x13\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x01\x3A\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x01\x3A\x03\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x01\x3A\x0D\x00\x00\x13\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x9C\x11\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x45\x0D\x00\x00\x10\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x10\x11\x00\x00\x00\x0F\x00\x00\x45\x0D\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\x01\x3E\x0D\x00\x00\x45\x11\x00\x00\x00\x0F\x00\x01\x3E\x0D\x00\x00\x00\x0F\x00\x00\x60\x0D\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x04\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x32\x03\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x60\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\xC3\x11\x00\x00\xC3\x11\x00\x00\xC3\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\xC3\x11\x00\x00\xC3\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x60\x11\x00\x00\x60\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x0D\x01\x00\x00\x07\x01\x00\x00\x60\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x01\x35\x03\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\xC3\x11\x00\x00\xC3\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x0D\x01\x00\x00\x1A\x11\x00\x00\x1A\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x09\x11\x00\x01\x4F\x03\x00\x00\x8E\x03\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x10\x11\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\xEC\x11\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x10\x11\x00\x00\x09\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x05\x0D\x00\x01\x4D\x03\x00\x01\x04\x11\x00\x00\x09\x11\x00\x00\x0D\x01\x00\x00\x00\x0F\x00\x01\x53\x0D\x00\x00\x23\x11\x00\x00\x00\x0F\x00\x01\x53\x0D\x00\x00\x04\x03\x00\x00\x00\x0F\x00\x01\x53\x0D\x00\x00\xEC\x11\x00\x00\x00\x0F\x00\x01\x53\x0D\x00\x00\x1A\x11\x00\x00\x00\x0F\x00\x01\x53\x0D\x00\x00\x13\x03\x00\x00\x00\x0F\x00\x01\x53\x0D\x00\x00\x9C\x11\x00\x00\x00\x0F\x00\x01\x53\x0D\x00\x00\x45\x03\x00\x00\x00\x0F\x00\x01\x53\x0D\x00\x01\x53\x03\x00\x00\x00\x0F\x00\x00\x00\x09\x00\x00\x01\x09\x00\x01\x36\x03\x00\x00\x02\x09\x00\x00\x03\x09\x00\x00\x05\x09\x00\x00\x04\x09\x00\x01\x3B\x03\x00\x00\x06\x09\x00\x00\x07\x09\x00\x01\x3E\x03\x00\x01\x3F\x03\x00\x00\x02\x01\x00\x00\x0E\x01\x00\x00\x00\x0B\x00\x00\x01\x0B\x00\x00\x02\x0B\x00\x00\x03\x0B\x00\x00\x04\x0B\x00\x00\x05\x0B\x00\x00\x06\x0B\x00\x00\x60\x03\x00\x00\x0B\x01\x00\x00\x05\x01\x00\x00\x03\x01\x00\x01\x39\x03\x00\x01\x4E\x03\x00\x00\x04\x01\x00\x01\x50\x03\x00\x00\x08\x01\x00\x00\x0C\x01\x00\x00\x06\x01\x00\x00\x00\x01', + _globals = (b'\xFF\xFF\xFF\x0BL_BF_ANY',1,b'\xFF\xFF\xFF\x0BL_BF_CODABAR',9,b'\xFF\xFF\xFF\x0BL_BF_CODE128',2,b'\xFF\xFF\xFF\x0BL_BF_CODE2OF5',5,b'\xFF\xFF\xFF\x0BL_BF_CODE39',7,b'\xFF\xFF\xFF\x0BL_BF_CODE93',8,b'\xFF\xFF\xFF\x0BL_BF_CODEI2OF5',6,b'\xFF\xFF\xFF\x0BL_BF_EAN13',4,b'\xFF\xFF\xFF\x0BL_BF_EAN8',3,b'\xFF\xFF\xFF\x0BL_BF_UNKNOWN',0,b'\xFF\xFF\xFF\x0BL_BF_UPCA',10,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_DEFAULT_ENCODE',0,b'\xFF\xFF\xFF\x0BL_FLATE_ENCODE',3,b'\xFF\xFF\xFF\x0BL_G4_ENCODE',2,b'\xFF\xFF\xFF\x0BL_INSERT',0,b'\xFF\xFF\xFF\x0BL_JP2K_ENCODE',4,b'\xFF\xFF\xFF\x0BL_JPEG_ENCODE',1,b'\xFF\xFF\xFF\x0BL_NOCOPY',0,b'\xFF\xFF\xFF\x0BL_SEVERITY_ALL',1,b'\xFF\xFF\xFF\x0BL_SEVERITY_DEBUG',2,b'\xFF\xFF\xFF\x0BL_SEVERITY_ERROR',5,b'\xFF\xFF\xFF\x0BL_SEVERITY_EXTERNAL',0,b'\xFF\xFF\xFF\x0BL_SEVERITY_INFO',3,b'\xFF\xFF\xFF\x0BL_SEVERITY_NONE',6,b'\xFF\xFF\xFF\x0BL_SEVERITY_WARNING',4,b'\xFF\xFF\xFF\x0BL_USE_WIDTHS',1,b'\xFF\xFF\xFF\x0BL_USE_WINDOWS',2,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'\xFF\xFF\xFF\x0BSEL_DONT_CARE',0,b'\xFF\xFF\xFF\x0BSEL_HIT',1,b'\xFF\xFF\xFF\x0BSEL_MISS',2,b'\x00\x00\x00\x23boxClone',0,b'\x00\x01\x1B\x23boxDestroy',0,b'\x00\x01\x1E\x23boxaDestroy',0,b'\x00\x00\x03\x23boxaGetBox',0,b'\x00\x00\xB6\x23getLeptonicaVersion',0,b'\x00\x01\x21\x23l_CIDataDestroy',0,b'\x00\x01\x06\x23l_generateCIDataForPdf',0,b'\x00\x01\x30\x23lept_free',0,b'\x00\x00\xB8\x23makePixelSumTab8',0,b'\x00\x00\x29\x23pixAnd',0,b'\x00\x00\x36\x23pixBackgroundNorm',0,b'\x00\x00\x2E\x23pixCleanBackgroundToWhite',0,b'\x00\x00\x20\x23pixClipRectangle',0,b'\x00\x00\xEE\x23pixColorFraction',0,b'\x00\x00\x7D\x23pixColorMagnitude',0,b'\x00\x00\x1D\x23pixConvertRGBToLuminance',0,b'\x00\x00\x74\x23pixConvertTo8',0,b'\x00\x00\xC0\x23pixCorrelationBinary',0,b'\x00\x00\xDA\x23pixCountPixels',0,b'\x00\x00\x90\x23pixDeserializeFromMemory',0,b'\x00\x00\x74\x23pixDeskew',0,b'\x00\x01\x24\x23pixDestroy',0,b'\x00\x00\x42\x23pixDilate',0,b'\x00\x00\x1D\x23pixEndianByteSwapNew',0,b'\x00\x00\xC5\x23pixEqual',0,b'\x00\x00\x42\x23pixErode',0,b'\x00\x00\x94\x23pixExtractBarcodes',0,b'\x00\x00\x08\x23pixFindPageForeground',0,b'\x00\x00\xD5\x23pixFindSkew',0,b'\x00\x00\x47\x23pixGammaTRC',0,b'\x00\x00\xE7\x23pixGenerateCIData',0,b'\x00\x00\xCA\x23pixGetAverageMaskedRGB',0,b'\x00\x00\x4E\x23pixGlobalNormRGB',0,b'\x00\x00\x42\x23pixHMT',0,b'\x00\x00\x25\x23pixInvert',0,b'\x00\x00\x17\x23pixLocateBarcodes',0,b'\x00\x00\x78\x23pixMaskOverColorPixels',0,b'\x00\x00\x56\x23pixMaskedThreshOnBackgroundNorm',0,b'\x00\x00\xDF\x23pixNumSignificantGrayColors',0,b'\x00\x00\xF7\x23pixOtsuAdaptiveThreshold',0,b'\x00\x00\x62\x23pixOtsuThreshOnBackgroundNorm',0,b'\x00\x00\x98\x23pixProcessBarcodes',0,b'\x00\x00\x89\x23pixRead',0,b'\x00\x00\x9F\x23pixReadBarcodes',0,b'\x00\x00\x8C\x23pixReadMem',0,b'\x00\x00\x74\x23pixRemoveColormap',0,b'\x00\x00\x78\x23pixRemoveColormapGeneral',0,b'\x00\x00\xBA\x23pixRenderBoxa',0,b'\x00\x00\x25\x23pixRotate180',0,b'\x00\x00\x74\x23pixRotateOrth',0,b'\x00\x00\x6F\x23pixScale',0,b'\x00\x01\x01\x23pixSerializeToMemory',0,b'\x00\x00\x29\x23pixSubtract',0,b'\x00\x01\x0C\x23pixWriteImpliedFormat',0,b'\x00\x01\x15\x23pixWriteMemPng',0,b'\x00\x01\x27\x23pixaDestroy',0,b'\x00\x00\x12\x23pixaGetBox',0,b'\x00\x00\x84\x23pixaGetPix',0,b'\x00\x01\x2A\x23sarrayDestroy',0,b'\x00\x00\xAC\x23selCreateBrick',0,b'\x00\x00\xA6\x23selCreateFromString',0,b'\x00\x01\x2D\x23selDestroy',0,b'\x00\x00\xB3\x23selPrintToString',0,b'\x00\x01\x12\x23setMsgSeverity',0), + _struct_unions = ((b'\x00\x00\x01\x33\x00\x00\x00\x02Box',b'\x00\x00\x05\x11x',b'\x00\x00\x05\x11y',b'\x00\x00\x05\x11w',b'\x00\x00\x05\x11h',b'\x00\x01\x50\x11refcount'),(b'\x00\x00\x01\x34\x00\x00\x00\x02Boxa',b'\x00\x00\x05\x11n',b'\x00\x00\x05\x11nalloc',b'\x00\x01\x50\x11refcount',b'\x00\x00\x23\x11box'),(b'\x00\x00\x01\x36\x00\x00\x00\x02L_Compressed_Data',b'\x00\x00\x05\x11type',b'\x00\x01\x4D\x11datacomp',b'\x00\x00\x8E\x11nbytescomp',b'\x00\x01\x3E\x11data85',b'\x00\x00\x8E\x11nbytes85',b'\x00\x01\x3E\x11cmapdata85',b'\x00\x01\x3E\x11cmapdatahex',b'\x00\x00\x05\x11ncolors',b'\x00\x00\x05\x11w',b'\x00\x00\x05\x11h',b'\x00\x00\x05\x11bps',b'\x00\x00\x05\x11spp',b'\x00\x00\x05\x11minisblack',b'\x00\x00\x05\x11predictor',b'\x00\x00\x8E\x11nbytes',b'\x00\x00\x05\x11res'),(b'\x00\x00\x01\x37\x00\x00\x00\x02Pix',b'\x00\x01\x50\x11w',b'\x00\x01\x50\x11h',b'\x00\x01\x50\x11d',b'\x00\x01\x50\x11spp',b'\x00\x01\x50\x11wpl',b'\x00\x01\x50\x11refcount',b'\x00\x00\x05\x11xres',b'\x00\x00\x05\x11yres',b'\x00\x00\x05\x11informat',b'\x00\x00\x05\x11special',b'\x00\x01\x3E\x11text',b'\x00\x01\x4C\x11colormap',b'\x00\x01\x4F\x11data'),(b'\x00\x00\x01\x39\x00\x00\x00\x02PixColormap',b'\x00\x01\x31\x11array',b'\x00\x00\x05\x11depth',b'\x00\x00\x05\x11nalloc',b'\x00\x00\x05\x11n'),(b'\x00\x00\x01\x38\x00\x00\x00\x02Pixa',b'\x00\x00\x05\x11n',b'\x00\x00\x05\x11nalloc',b'\x00\x01\x50\x11refcount',b'\x00\x00\x1A\x11pix',b'\x00\x00\x04\x11boxa'),(b'\x00\x00\x01\x3B\x00\x00\x00\x02Sarray',b'\x00\x00\x05\x11nalloc',b'\x00\x00\x05\x11n',b'\x00\x00\x05\x11refcount',b'\x00\x01\x3D\x11array'),(b'\x00\x00\x01\x3C\x00\x00\x00\x02Sel',b'\x00\x00\x05\x11sy',b'\x00\x00\x05\x11sx',b'\x00\x00\x05\x11cy',b'\x00\x00\x05\x11cx',b'\x00\x01\x48\x11data',b'\x00\x01\x3E\x11name')), + _enums = (b'\x00\x00\x01\x41\x00\x00\x00\x16$1\x00L_DEFAULT_ENCODE,L_JPEG_ENCODE,L_G4_ENCODE,L_FLATE_ENCODE,L_JP2K_ENCODE',b'\x00\x00\x01\x42\x00\x00\x00\x16$2\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\x01\x43\x00\x00\x00\x16$3\x00L_NOCOPY,L_INSERT,L_COPY,L_CLONE,L_COPY_CLONE',b'\x00\x00\x01\x44\x00\x00\x00\x16$4\x00L_USE_WIDTHS,L_USE_WINDOWS',b'\x00\x00\x01\x45\x00\x00\x00\x16$5\x00L_BF_UNKNOWN,L_BF_ANY,L_BF_CODE128,L_BF_EAN8,L_BF_EAN13,L_BF_CODE2OF5,L_BF_CODEI2OF5,L_BF_CODE39,L_BF_CODE93,L_BF_CODABAR,L_BF_UPCA',b'\x00\x00\x01\x46\x00\x00\x00\x16$6\x00L_SEVERITY_EXTERNAL,L_SEVERITY_ALL,L_SEVERITY_DEBUG,L_SEVERITY_INFO,L_SEVERITY_WARNING,L_SEVERITY_ERROR,L_SEVERITY_NONE',b'\x00\x00\x01\x47\x00\x00\x00\x16$7\x00SEL_DONT_CARE,SEL_HIT,SEL_MISS'), + _typenames = (b'\x00\x00\x01\x33BOX',b'\x00\x00\x01\x34BOXA',b'\x00\x00\x01\x36L_COMP_DATA',b'\x00\x00\x01\x37PIX',b'\x00\x00\x01\x38PIXA',b'\x00\x00\x01\x39PIXCMAP',b'\x00\x00\x01\x3BSARRAY',b'\x00\x00\x01\x3CSEL',b'\x00\x00\x00\x32l_float32',b'\x00\x00\x01\x40l_float64',b'\x00\x00\x01\x4Al_int16',b'\x00\x00\x00\x05l_int32',b'\x00\x00\x01\x49l_int64',b'\x00\x00\x01\x4Bl_int8',b'\x00\x00\x00\x05l_ok',b'\x00\x00\x01\x52l_uint16',b'\x00\x00\x01\x50l_uint32',b'\x00\x00\x01\x51l_uint64',b'\x00\x00\x01\x4El_uint8'), ) diff --git a/src/ocrmypdf/lib/compile_leptonica.py b/src/ocrmypdf/lib/compile_leptonica.py index 87712db2..4418e7cf 100644 --- a/src/ocrmypdf/lib/compile_leptonica.py +++ b/src/ocrmypdf/lib/compile_leptonica.py @@ -206,6 +206,7 @@ enum { ffibuilder.cdef(""" PIX * pixRead ( const char *filename ); +PIX * pixReadMem ( const l_uint8 *data, size_t size ); PIX * pixScale ( PIX *pixs, l_float32 scalex, l_float32 scaley ); l_int32 pixFindSkew ( PIX *pixs, l_float32 *pangle, l_float32 *pconf ); l_int32 pixWriteImpliedFormat ( const char *filename, PIX *pix, l_int32 quality, l_int32 progressive ); From 700abbb8a51790bb1fba62c6c13fbc8f933aa92a Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Sat, 10 Nov 2018 15:48:41 -0800 Subject: [PATCH 68/68] Documentation for OCR quality features --- .gitignore | 1 + docs/release_notes.rst | 8 +++++++- src/ocrmypdf/__main__.py | 4 ++-- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 610fe588..e545c422 100644 --- a/.gitignore +++ b/.gitignore @@ -32,6 +32,7 @@ htmlcov/ *.profile /*.pdf /*.qdf +/*.png /scratch.py IDEAS log/ diff --git a/docs/release_notes.rst b/docs/release_notes.rst index b6be039f..d36cf4d4 100644 --- a/docs/release_notes.rst +++ b/docs/release_notes.rst @@ -29,7 +29,13 @@ v7.3.0 - OCRmyPDF now warns when a PDF that contains Adobe AcroForms, since such files probably do not need OCR. It can work with these files. -- Added a new feature ``--mask-barcodes`` to detect and suppress barcodes in files. We have observed that barcodes can interfere with OCR. +- Added three new **experimental** features. The name, syntax and behavior of these arguments is subject to change. They may also be incompatible with some other features. + + - ``--remove-vectors`` which strips out vector graphics. This can improve OCR quality since OCR will not search artwork for readable text; however, it currently removes "text as curves" as well. + + - ``--mask-barcodes`` to detect and suppress barcodes in files. We have observed that barcodes can interfere with OCR. + + - ``--threshold`` which uses a more sophisticated thresholding algorithm than is currently in use in Tesseract OCR. This works around a `known issue in Tesseract `_ with text on bright backgrounds. - Fixed an issue where an error message was not reported when the installed Ghostscript was very old. diff --git a/src/ocrmypdf/__main__.py b/src/ocrmypdf/__main__.py index 2caf45a4..52ae8629 100755 --- a/src/ocrmypdf/__main__.py +++ b/src/ocrmypdf/__main__.py @@ -250,12 +250,12 @@ preprocessing.add_argument( "will not be included in OCR. This can eliminate false characters.") preprocessing.add_argument( '--mask-barcodes', action='store_true', - help="Mask out any barcodes that appear in the PDF so they are not " + help="EXPERIMENTAL. Mask out any barcodes that appear in the PDF so they are not " "considered during OCR. Barcodes can introduce false characters into " "OCR.") preprocessing.add_argument( '--threshold', action='store_true', - help="Threshold image to 1bpp before sending it to Tesseract for OCR. Can " + help="EXPERIMENTAL. Threshold image to 1bpp before sending it to Tesseract for OCR. Can " "improve OCR quality compared to Tesseract's thresholder.") ocrsettings = parser.add_argument_group(