From 949265bbd0e4b0c3c606129e37039905c58b22c9 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Sat, 4 Nov 2023 02:32:39 -0700 Subject: [PATCH] graft: improve typing and remove procset tracking ProcSet is optional and deprecated in PDF 2.0, and does little anyway; so we removed it. --- src/ocrmypdf/_graft.py | 72 ++++++++++++++++++++++-------------------- 1 file changed, 37 insertions(+), 35 deletions(-) diff --git a/src/ocrmypdf/_graft.py b/src/ocrmypdf/_graft.py index 33098a8b..da61b2e2 100644 --- a/src/ocrmypdf/_graft.py +++ b/src/ocrmypdf/_graft.py @@ -12,8 +12,8 @@ from pathlib import Path from pikepdf import ( Dictionary, Name, - Object, Operator, + Page, Pdf, PdfError, PdfMatrix, @@ -22,17 +22,24 @@ from pikepdf import ( unparse_content_stream, ) +from ocrmypdf._jobcontext import PdfContext + log = logging.getLogger(__name__) MAX_REPLACE_PAGES = 100 -def _ensure_dictionary(obj, name): +def _ensure_dictionary(obj: Dictionary | Stream, name: Name): if name not in obj: obj[name] = Dictionary({}) return obj[name] -def _update_resources(*, obj, font, font_key, procset): +def _update_resources( + *, + obj: Dictionary | Stream, + font: Dictionary | None, + font_key: Name | None, +): """Update this obj's fonts with a reference to the Glyphless font. obj can be a page or Form XObject. @@ -42,13 +49,8 @@ def _update_resources(*, obj, font, font_key, procset): if font_key is not None and font_key not in fonts: fonts[font_key] = font - # Reassign /ProcSet to one that just lists everything - ProcSet is - # obsolete and doesn't matter but recommended for old viewer support - if procset: - resources['/ProcSet'] = procset - -def strip_invisible_text(pdf, page): +def strip_invisible_text(pdf: Pdf, page: Page): stream = [] in_text_obj = False render_mode = 0 @@ -79,20 +81,17 @@ def strip_invisible_text(pdf, page): class OcrGrafter: """Manages grafting text-only PDFs onto regular PDFs.""" - def __init__(self, context): + def __init__(self, context: PdfContext): self.context = context self.path_base = context.origin self.pdf_base = Pdf.open(self.path_base) - self.font, self.font_key = None, None + self.font: Dictionary | None = None + self.font_key: Name | None = None self.pdfinfo = context.pdfinfo self.output_file = context.get_path('graft_layers.pdf') - self.procset = self.pdf_base.make_indirect( - Object.parse(b'[ /PDF /Text /ImageB /ImageC /ImageI ]') - ) - self.emplacements = 1 self.interim_count = 0 @@ -119,7 +118,9 @@ class OcrGrafter: foreign_image_page = pdf_image.pages[0] self.pdf_base.pages.append(foreign_image_page) local_image_page = self.pdf_base.pages[-1] - self.pdf_base.pages[pageno].emplace(local_image_page) + self.pdf_base.pages[pageno].emplace( + local_image_page, retain=(Name.Parent,) + ) del self.pdf_base.pages[-1] emplaced_page = True @@ -135,6 +136,8 @@ class OcrGrafter: ) if textpdf and self.font: + if self.font_key is None: + raise ValueError("Font key is not set") # Graft the text layer onto this page, whether new or old, possibly # rotating the text layer by the amount is misaligned. strip_old = self.context.options.redo_ocr @@ -144,7 +147,6 @@ class OcrGrafter: font=self.font, font_key=self.font_key, text_rotation=text_misaligned, - procset=self.procset, strip_old_text=strip_old, ) @@ -159,7 +161,7 @@ class OcrGrafter: if self.emplacements % MAX_REPLACE_PAGES == 0: self.save_and_reload() - def save_and_reload(self): + def save_and_reload(self) -> None: """Save and reload the Pdf. This will keep a lid on our memory usage for very large files. Attach @@ -167,9 +169,7 @@ class OcrGrafter: back. """ page0 = self.pdf_base.pages[0] - _update_resources( - obj=page0, font=self.font, font_key=self.font_key, procset=self.procset - ) + _update_resources(obj=page0.obj, font=self.font, font_key=self.font_key) # We cannot read and write the same file, that will corrupt it # but we don't to keep more copies than we need to. Delete intermediates. @@ -188,7 +188,6 @@ class OcrGrafter: self.pdf_base.close() self.pdf_base = Pdf.open(next_file) - self.procset = self.pdf_base.pages[0].Resources.ProcSet self.font, self.font_key = None, None # Ensure we reacquire this information self.interim_count += 1 @@ -197,24 +196,32 @@ class OcrGrafter: self.pdf_base.close() return self.output_file - def _find_font(self, text): + def _find_font(self, text: Path) -> tuple[Dictionary | None, Name | None]: """Copy a font from the filename text into pdf_base.""" font, font_key = None, None possible_font_names = ('/f-0-0', '/F1') try: with Pdf.open(text) as pdf_text: try: - pdf_text_fonts = pdf_text.pages[0].Resources.get('/Font', {}) + pdf_text_fonts = pdf_text.pages[0].Resources.get( + Name.Font, Dictionary() + ) except (AttributeError, IndexError, KeyError): return None, None + if not isinstance(pdf_text_fonts, Dictionary): + log.warning("Page fonts are not stored in a dictionary") + return None, None pdf_text_font = None for f in possible_font_names: pdf_text_font = pdf_text_fonts.get(f, None) if pdf_text_font is not None: - font_key = f + font_key = Name(f) break if pdf_text_font: font = self.pdf_base.copy_foreign(pdf_text_font) + if not isinstance(font, Dictionary): + log.warning("Font is not a dictionary") + font, font_key = None, None return font, font_key except (FileNotFoundError, PdfError): # PdfError occurs if a 0-length file is written e.g. due to OCR timeout @@ -225,9 +232,8 @@ class OcrGrafter: *, page_num: int, textpdf: Path, - font: Object, - font_key: Object, - procset: Object, + font: Dictionary, + font_key: Name, text_rotation: int, strip_old_text: bool, ): @@ -278,7 +284,7 @@ class OcrGrafter: # finally move the lower left corner to match the mediabox ctm = translate @ rotate @ scale @ untranslate @ corner - base_resources = _ensure_dictionary(base_page, Name.Resources) + base_resources = _ensure_dictionary(base_page.obj, Name.Resources) base_xobjs = _ensure_dictionary(base_resources, Name.XObject) text_xobj_name = Name.random(prefix="OCR-") xobj = self.pdf_base.make_stream(pdf_text_contents) @@ -287,9 +293,7 @@ class OcrGrafter: xobj.Subtype = Name.Form xobj.FormType = 1 xobj.BBox = mediabox - _update_resources( - obj=xobj, font=font, font_key=font_key, procset=[Name.PDF] - ) + _update_resources(obj=xobj, font=font, font_key=font_key) pdf_draw_xobj = ( (b'q %s cm\n' % ctm.encode()) + (b'%s Do\n' % text_xobj_name) + b'\nQ\n' @@ -301,6 +305,4 @@ class OcrGrafter: base_page.contents_add(new_text_layer, prepend=True) - _update_resources( - obj=base_page, font=font, font_key=font_key, procset=procset - ) + _update_resources(obj=base_page.obj, font=font, font_key=font_key)