diff --git a/src/ocrmypdf/_graft.py b/src/ocrmypdf/_graft.py index f1b69903..a59513ab 100644 --- a/src/ocrmypdf/_graft.py +++ b/src/ocrmypdf/_graft.py @@ -11,8 +11,19 @@ from contextlib import suppress from pathlib import Path from typing import Optional -import pikepdf -from pikepdf.objects import Dictionary, Name +from pikepdf import ( + Dictionary, + Name, + Object, + Operator, + Page, + Pdf, + PdfError, + PdfMatrix, + Stream, + parse_content_stream, + unparse_content_stream, +) log = logging.getLogger(__name__) MAX_REPLACE_PAGES = 100 @@ -47,51 +58,28 @@ def strip_invisible_text(pdf, page): render_mode = 0 text_objects = [] - rich_page = pikepdf.Page(page) + rich_page = Page(page) rich_page.contents_coalesce() - for operands, operator in pikepdf.parse_content_stream(page, ''): + for operands, operator in parse_content_stream(page, ''): if not in_text_obj: - if operator == pikepdf.Operator('BT'): + if operator == Operator('BT'): in_text_obj = True render_mode = 0 text_objects.append((operands, operator)) else: stream.append((operands, operator)) else: - if operator == pikepdf.Operator('Tr'): + if operator == Operator('Tr'): render_mode = operands[0] text_objects.append((operands, operator)) - if operator == pikepdf.Operator('ET'): + if operator == Operator('ET'): in_text_obj = False if render_mode != 3: stream.extend(text_objects) text_objects.clear() - def convert(op): - try: - return op.unparse() - except AttributeError: - return str(op).encode('ascii') - - if hasattr(pikepdf, 'unparse_content_stream'): - content_stream = pikepdf.unparse_content_stream(stream) - else: - lines = [] - - for operands, operator in stream: - 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) - page.Contents = pikepdf.Stream(pdf, content_stream) + content_stream = unparse_content_stream(stream) + page.Contents = Stream(pdf, content_stream) class OcrGrafter: @@ -99,14 +87,14 @@ class OcrGrafter: self.context = context self.path_base = context.origin - self.pdf_base = pikepdf.open(self.path_base) + self.pdf_base = Pdf.open(self.path_base) self.font, self.font_key = None, None self.pdfinfo = context.pdfinfo self.output_file = context.get_path('graft_layers.pdf') self.procset = self.pdf_base.make_indirect( - pikepdf.Object.parse(b'[ /PDF /Text /ImageB /ImageC /ImageI ]') + Object.parse(b'[ /PDF /Text /ImageB /ImageC /ImageI ]') ) self.emplacements = 1 @@ -130,7 +118,7 @@ class OcrGrafter: # We are updating the old page with a rasterized PDF of the new # page (without changing objgen, to preserve references) log.debug("Emplacement update") - with pikepdf.open(image) as pdf_image: + with Pdf.open(path_image) as pdf_image: self.emplacements += 1 foreign_image_page = pdf_image.pages[0] self.pdf_base.pages.append(foreign_image_page) @@ -203,7 +191,7 @@ class OcrGrafter: self.pdf_base.save(next_file) self.pdf_base.close() - self.pdf_base = pikepdf.open(next_file) + 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 @@ -219,7 +207,7 @@ class OcrGrafter: font, font_key = None, None possible_font_names = ('/f-0-0', '/F1') try: - with pikepdf.open(text) as pdf_text: + with Pdf.open(text) as pdf_text: try: pdf_text_fonts = pdf_text.pages[0].Resources.get('/Font', {}) except (AttributeError, IndexError, KeyError): @@ -233,7 +221,7 @@ class OcrGrafter: if pdf_text_font: font = self.pdf_base.copy_foreign(pdf_text_font) return font, font_key - except (FileNotFoundError, pikepdf.PdfError): + except (FileNotFoundError, PdfError): # PdfError occurs if a 0-length file is written e.g. due to OCR timeout return None, None @@ -242,9 +230,9 @@ class OcrGrafter: *, page_num: int, textpdf: Path, - font: pikepdf.Object, - font_key: pikepdf.Object, - procset: pikepdf.Object, + font: Object, + font_key: Object, + procset: Object, text_rotation: int, strip_old_text: bool, ): @@ -255,7 +243,7 @@ class OcrGrafter: return # This is a pointer indicating a specific page in the base file - with pikepdf.open(textpdf) as pdf_text: + with Pdf.open(textpdf) as pdf_text: pdf_text_contents = pdf_text.pages[0].Contents.read_bytes() base_page = self.pdf_base.pages.p(page_num) @@ -270,13 +258,13 @@ class OcrGrafter: mediabox = [float(base_page.MediaBox[v]) for v in range(4)] wp, hp = mediabox[2] - mediabox[0], mediabox[3] - mediabox[1] - translate = pikepdf.PdfMatrix().translated(-wt / 2, -ht / 2) - untranslate = pikepdf.PdfMatrix().translated(wp / 2, hp / 2) - corner = pikepdf.PdfMatrix().translated(mediabox[0], mediabox[1]) + translate = PdfMatrix().translated(-wt / 2, -ht / 2) + untranslate = PdfMatrix().translated(wp / 2, hp / 2) + corner = PdfMatrix().translated(mediabox[0], mediabox[1]) # -rotation because the input is a clockwise angle and this formula # uses CCW text_rotation = -text_rotation % 360 - rotate = pikepdf.PdfMatrix().rotated(text_rotation) + rotate = PdfMatrix().rotated(text_rotation) # Because of rounding of DPI, we might get a text layer that is not # identically sized to the target page. Scale to adjust. Normally this @@ -287,7 +275,7 @@ class OcrGrafter: scale_y = hp / ht # log.debug('%r', scale_x, scale_y) - scale = pikepdf.PdfMatrix().scaled(scale_x, scale_y) + scale = PdfMatrix().scaled(scale_x, scale_y) # Translate the text so it is centered at (0, 0), rotate it there, adjust # for a size different between initial and text PDF, then untranslate, and @@ -310,14 +298,14 @@ class OcrGrafter: pdf_draw_xobj = ( (b'q %s cm\n' % ctm.encode()) + (b'%s Do\n' % text_xobj_name) + b'\nQ\n' ) - new_text_layer = pikepdf.Stream(self.pdf_base, pdf_draw_xobj) + new_text_layer = Stream(self.pdf_base, pdf_draw_xobj) if strip_old_text: strip_invisible_text(self.pdf_base, base_page) - if hasattr(pikepdf.Page, 'contents_add'): + if hasattr(Page, 'contents_add'): # pikepdf >= 2.14 adds this method and deprecates the one below - pikepdf.Page(base_page).contents_add(new_text_layer, prepend=True) + Page(base_page).contents_add(new_text_layer, prepend=True) else: # pikepdf < 2.14 base_page.page_contents_add(