graft: improve typing and remove procset tracking

ProcSet is optional and deprecated in PDF 2.0, and does little anyway; so
we removed it.
This commit is contained in:
James R. Barlow
2023-11-04 02:32:39 -07:00
parent 916106733c
commit 949265bbd0
+37 -35
View File
@@ -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)