Refine non-dict resource guard: prefer isinstance, cover FontDescriptor

Follow-on to the non-dictionary /Font and /XObject guard. Replace the
_dict_entries() helper with an isinstance(..., pikepdf.Dictionary) check
at each resource lookup, which pikepdf's metaclass supports directly and
which reads as exactly the invariant being enforced. Iterate via
as_dict().values() so the values are typed and mypy stays clean once the
Any from the untyped resources argument is narrowed away.

Also guard _cid_font_is_embedded against a non-dictionary /FontDescriptor:
`key in descriptor` raises ValueError on a non-dict, which the caller's
except (AttributeError, TypeError, KeyError) does not catch. Such a font
now counts as non-embedded and is reported, so PDF/A conversion is refused
rather than risking Ghostscript corrupting a pre-existing CID text layer.

Adds a regression test for the FontDescriptor case (issue #1713).
This commit is contained in:
James R. Barlow
2026-07-16 23:42:50 -07:00
parent ef903db360
commit 640b3062b2
2 changed files with 52 additions and 31 deletions
+30
View File
@@ -110,6 +110,36 @@ class TestFindNonembeddedCidFonts:
with pikepdf.open(path) as pdf:
assert find_nonembedded_cid_fonts(pdf) == set()
def test_non_dictionary_font_descriptor_is_reported(self, tmp_path):
# A Type0 font whose descendant carries a non-dictionary /FontDescriptor
# has no embedded glyph data, so it must be reported -- not crash. This
# is the same malformed-resource bug class as #1713, one level deeper:
# `key in descriptor` raises ValueError on a non-dictionary.
path = tmp_path / 'bad_descriptor.pdf'
with pikepdf.new() as pdf:
page = pdf.add_blank_page()
cidfont = pdf.make_indirect(
pikepdf.Dictionary(
Type=Name.Font,
Subtype=Name.CIDFontType2,
BaseFont=Name('/BOGUS+CID'),
FontDescriptor=Name.NotADictionary,
)
)
type0 = pdf.make_indirect(
pikepdf.Dictionary(
Type=Name.Font,
Subtype=Name.Type0,
BaseFont=Name('/BOGUS+CID'),
Encoding=Name.Identity_H,
DescendantFonts=pikepdf.Array([cidfont]),
)
)
page.Resources = pikepdf.Dictionary(Font=pikepdf.Dictionary(F0=type0))
pdf.save(path)
with pikepdf.open(path) as pdf:
assert find_nonembedded_cid_fonts(pdf) == {'BOGUS+CID'}
@pytest.fixture
def nonembedded_cid_pdf(tmp_path):