From b18e66e2ca0edc8730b0115a08bbafa4b18f7054 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Thu, 18 Oct 2018 01:21:51 -0700 Subject: [PATCH] pdfinfo: learn to detect vector graphic objects --- src/ocrmypdf/pdfinfo.py | 38 ++++++++++++++++++++++++++++---------- tests/test_pdfinfo.py | 6 ++++++ 2 files changed, 34 insertions(+), 10 deletions(-) diff --git a/src/ocrmypdf/pdfinfo.py b/src/ocrmypdf/pdfinfo.py index 6e43adbd..a2748c23 100644 --- a/src/ocrmypdf/pdfinfo.py +++ b/src/ocrmypdf/pdfinfo.py @@ -104,7 +104,7 @@ InlineSettings = namedtuple('InlineSettings', ['iimage', 'shorthand', 'stack_depth']) ContentsInfo = namedtuple('ContentsInfo', - ['xobject_settings', 'inline_images', 'found_text']) + ['xobject_settings', 'inline_images', 'found_text', 'found_vector']) def _normalize_stack(graphobjs): @@ -119,7 +119,7 @@ def _normalize_stack(graphobjs): def _interpret_contents(contentstream, initial_shorthand=UNIT_SQUARE): - """Interpret the PDF content stream + """Interpret the PDF content stream. The stack represents the state of the PDF graphics stack. We are only interested in the current transformation matrix (CTM) so we only track @@ -136,16 +136,16 @@ def _interpret_contents(contentstream, initial_shorthand=UNIT_SQUARE): page. PDF units suit our needs so we initialize ctm to the identity matrix. - """ stack = [] ctm = PdfMatrix(initial_shorthand) xobject_settings = [] inline_images = [] - found_text = False - text_operators = set(['Tj', 'TJ', '"', "'"]) - operator_whitelist = """q Q Do cm TJ Tj " ' BI ID EI""" + 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*""" for n, graphobj in enumerate(_normalize_stack( pikepdf.parse_content_stream(contentstream, operator_whitelist))): @@ -178,12 +178,14 @@ def _interpret_contents(contentstream, initial_shorthand=UNIT_SQUARE): inline_images.append(inline) elif operator in text_operators: found_text = True - + elif operator in vector_operators: + found_vector = True return ContentsInfo( xobject_settings=xobject_settings, inline_images=inline_images, - found_text=found_text) + found_text=found_text, + found_vector=found_vector) def _get_dpi(ctm_shorthand, image_size): @@ -250,6 +252,11 @@ 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') @@ -482,6 +489,8 @@ def _find_images(*, pdf, container, shorthand=None): contentsinfo = _interpret_contents(container, initial_shorthand) + if contentsinfo.found_vector: + yield VectorInfo() yield from _find_inline_images(contentsinfo) yield from _find_regular_images(container, contentsinfo) yield from _find_form_xobject_images(pdf, container, contentsinfo) @@ -585,6 +594,12 @@ def _pdf_get_pageinfo(pdf, pageno: int, infile, xmltext): pageinfo['images'] = [im for im in _find_images(pdf=pdf, container=page, shorthand=userunit_shorthand)] + + if any(isinstance(im, VectorInfo) for im in pageinfo['images']): + pageinfo['has_vector'] = True + + pageinfo['images'] = [im for im in pageinfo['images'] + if not isinstance(im, VectorInfo)] if pageinfo['images']: xres = Decimal(max(image.xres for image in pageinfo['images'])) yres = Decimal(max(image.yres for image in pageinfo['images'])) @@ -647,6 +662,10 @@ class PageInfo: def has_text(self): return self._pageinfo['has_text'] + @property + def has_vector(self): + return self._pageinfo['has_vector'] + @property def width_inches(self): return self._pageinfo['width_inches'] @@ -711,9 +730,8 @@ class PageInfo: class PdfInfo: - """Get summary information about a PDF + """Get summary information about a PDF""" - """ def __init__(self, infile, log=None): self._infile = infile self._pages, pdf = _pdf_get_all_pageinfo(infile, log=log) diff --git a/tests/test_pdfinfo.py b/tests/test_pdfinfo.py index ca4bcc43..c3ee0b6d 100644 --- a/tests/test_pdfinfo.py +++ b/tests/test_pdfinfo.py @@ -169,3 +169,9 @@ def test_regex(): assert rx.match(s) for s in must_not_match: assert not rx.match(s) + + +def test_vector(resources): + filename = resources / 'vector.pdf' + pdf = pdfinfo.PdfInfo(filename) + assert pdf[0].has_vector