Guard find_nonembedded_cid_fonts against non-dictionary resources
A malformed PDF can store a non-dictionary object under a page's /Font or /XObject resource. find_nonembedded_cid_fonts() iterated .values() on that object outside the per-entry try/except, so scanning such a page raised (TypeError/AttributeError depending on the pikepdf version) instead of producing output. This surfaced as a PDF/A conversion crash. Route both resource lookups through a small helper that returns an empty list when the resource is missing or not a dictionary, so a garbage entry is simply treated as having no fonts. Add a regression test covering a non-dictionary /Font and /XObject.
This commit is contained in:
+19
-4
@@ -148,6 +148,23 @@ def _cid_font_is_embedded(type0_font: Dictionary) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
def _dict_entries(resource):
|
||||
"""Return the values of a PDF resource sub-dictionary, tolerating garbage.
|
||||
|
||||
A ``/Font`` or ``/XObject`` resource is expected to be a dictionary, but a
|
||||
malformed PDF -- common in OCR workloads -- may store a non-dictionary
|
||||
object (an array, a name, an empty value) there. Calling ``.values()`` on a
|
||||
non-dictionary raises, so return an empty list in that case rather than
|
||||
letting the scan crash (issue #1713).
|
||||
"""
|
||||
if resource is None:
|
||||
return []
|
||||
try:
|
||||
return list(resource.values())
|
||||
except (AttributeError, TypeError):
|
||||
return []
|
||||
|
||||
|
||||
def find_nonembedded_cid_fonts(pdf: Pdf) -> set[str]:
|
||||
"""Find CID-keyed (Type0) fonts that lack embedded glyph data.
|
||||
|
||||
@@ -175,8 +192,7 @@ def find_nonembedded_cid_fonts(pdf: Pdf) -> set[str]:
|
||||
if resources is None or depth > 10:
|
||||
return
|
||||
fonts = resources.get(Name.Font, None)
|
||||
if fonts is not None:
|
||||
for font in fonts.values():
|
||||
for font in _dict_entries(fonts):
|
||||
try:
|
||||
if font.get(Name.Subtype) != Name.Type0:
|
||||
continue
|
||||
@@ -186,8 +202,7 @@ def find_nonembedded_cid_fonts(pdf: Pdf) -> set[str]:
|
||||
except (AttributeError, TypeError, KeyError):
|
||||
continue
|
||||
xobjects = resources.get(Name.XObject, None)
|
||||
if xobjects is not None:
|
||||
for xobj in xobjects.values():
|
||||
for xobj in _dict_entries(xobjects):
|
||||
if xobj.get(Name.Subtype) == Name.Form and Name.Resources in xobj:
|
||||
scan_resources(xobj[Name.Resources], depth + 1)
|
||||
|
||||
|
||||
@@ -94,6 +94,22 @@ class TestFindNonembeddedCidFonts:
|
||||
with pikepdf.open(path) as pdf:
|
||||
assert find_nonembedded_cid_fonts(pdf) == {'ZZZ+Hidden'}
|
||||
|
||||
def test_non_dictionary_font_and_xobject_resources_are_ignored(self, tmp_path):
|
||||
# A malformed PDF may carry a /Font or /XObject resource that is not a
|
||||
# dictionary (an array, a name, an empty value). Scanning must skip it
|
||||
# rather than raise when iterating its values (regression test for the
|
||||
# crash reported in issue #1713).
|
||||
path = tmp_path / 'malformed_resources.pdf'
|
||||
with pikepdf.new() as pdf:
|
||||
page = pdf.add_blank_page()
|
||||
page.Resources = pikepdf.Dictionary(
|
||||
Font=pikepdf.Array([]),
|
||||
XObject=pikepdf.Array([]),
|
||||
)
|
||||
pdf.save(path)
|
||||
with pikepdf.open(path) as pdf:
|
||||
assert find_nonembedded_cid_fonts(pdf) == set()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def nonembedded_cid_pdf(tmp_path):
|
||||
|
||||
Reference in New Issue
Block a user