Conversation
Embedded subset parses cleanly but lacks a shown glyph; font-integrity currently passes it clean. Expectation flips with the audit change. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…114) Reported codes then prove which of page content, Form XObject and annotation appearance were traversed. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
#114) Shown codes in page content, Form XObjects and annotation appearance streams must resolve to a real glyph; missing, .notdef and empty-outline glyphs are reported per page and font. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…114) A code outside /Widths resolves with zero advance and emitted no item, so the missing glyph was invisible to the audit. The text sequence now lists every unresolved code. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… items (#114) drawText returned early for an empty sequence, before any hook ran. A new performTextGlyphsUnresolved hook fires first; the editor processor is unaffected. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…114) Adds the font-glyph-missing snapshot and regenerates the check catalog, corpus-coverage map and backlog. Golden corpus snapshots are unchanged. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ge (#114) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
| } | ||
| } | ||
|
|
||
| void performProcessTextSequence(const TextSequence& textSequence, ProcessOrder order) override |
There was a problem hiding this comment.
Invisible text is audited as shown glyphs. The processor classifies every text sequence whatever the text rendering mode, so invisible OCR layers (Tr 3) and clip-only text (Tr 7) count as shown. OCR'd PDFs (Tesseract/ABBYY) draw an embedded GlyphLessFont with Tr 3, and its glyphs are empty outlines. That makes classifyShownGlyph return EmptyOutline for every character, so font-integrity reports a finding on every page of a correct document. Skip sequences whose rendering mode is neither filled nor stroked (both hooks).
| { processor.processFormStream(formStream); }); | ||
| pageDefects = processor.defects(); | ||
| } | ||
| catch (const PDFException& exception) |
There was a problem hiding this comment.
Budget exhaustion is swallowed. PDFBudgetExceededException derives from PDFException, and the content processor rethrows it on purpose. This catch turns it into a GlyphCoverageIncomplete error on this page and on every later page, and the engine's recordBudgetFailure path never runs. The defects already recorded before the throw are also dropped. Rethrow budget exceptions here, as processContent does.
|
|
||
| void record(const PDFFont& font, PDFShownGlyphDefect defect, CID code) | ||
| { | ||
| ShownGlyphDefects& entry = m_defects[QString::fromLatin1(font.getFontId())]; |
There was a problem hiding this comment.
Defects are keyed by resource name, not by font. getFontId() is the resource key (e.g. F1), so different fonts that share a key across page and Form XObject resources are merged. subtype and composite then come from whichever font was recorded last, so code_kind can be wrong for some of the codes. Key by font object identity instead (e.g. the PDFFont pointer or object reference) and keep the resource name for display.
Shown-glyph coverage ignored the text rendering mode, so Tr 3 OCR layers drawn with glyphless fonts were reported as empty glyphs. Defects are now recorded per font object instead of per resource name, and budget exhaustion propagates to the engine instead of becoming per-page errors. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Closes #114
What changed
font-integritynow audits glyph coverage. Every character code (simple fonts) or CID (composite fonts) shown in page content, Form XObjects and annotation appearance streams is resolved through the font's encoding and cmap; one that maps to no glyph, to.notdef, or to an empty outline for a visible character is reported per page and font withevidence.missing_codes. A subset that parses but lacks a used glyph no longer passes clean.TextSequence::unresolvedCodesandTextSequenceItem::glyphIndexrecord what the realized font could not resolve (including zero-advance codes outside/Widths, which previously emitted no item).PDFPageContentProcessor::performTextGlyphsUnresolvedhook, called beforedrawText's empty-sequence early return. The default is a no-op, so the editor processor is unaffected.classifyShownGlyphinpdffontintegrity.{h,cpp};ShownGlyphCoverageProcessorand the extendedrunFontIntegrityCheckinpreflightengine.cpp.font-glyph-coveragemoved tolanded(closed byfont-integrity);font-integritylimitations rewritten.Proof
Source SHA under test:
ed926643. Fixture:loop-preflight/testdata/fixtures/font-glyph-missing.pdf(generatorloop-preflight/tools/generate_font_glyph_coverage_fixtures.py). It is committed first (8edf5fb, d7e34f2), before the change. Codes 0xE9, 0xEA and 0xEB are shown in page content, a Form XObject and a Widget appearance stream, so the reported codes prove each location.PdfTool, fixture at this PR)font-glyph-missingpass: true,errors: []pass: false, onefont-integrityerror: page 1, fontF2+0,missing_codes: [233, 234, 235]font-glyph-missing.json(others differ only in line endings locally)Local (Windows, MSVC Release):
UnitTestsPreflightEngine112/112,UnitTestsPreflightCorpus156/156,UnitTestsPreflightVerdict53/53,UnitTestsPreflightChecks17/17,UnitTestsEvidenceGraph15/15,UnitTestsFontEncoding8/8,UnitTestsContentProcessorLimits10/10.python scripts/generate-architecture-catalogs.py --check,check_source_integrity.py,check_preflight_truth_source.py,generate-adapters.py, andcheck-change.py --dry-runall pass.Skipped or unavailable locally: the full
check-change.pybuild/ctest/clang-tidy run (noclang-tidy-18, and only the targets above were built), Linux lanes, and the remaining mapped test targets. CI covers those.Remaining risk
glyphIndex == 0is reported as.notdef; an embedded font that uses GID 0 as a real glyph would be a false positive (none in the golden corpus).Anti-slop review
One new hook and two small fields rather than a second text pipeline; the audit reuses the renderer's own glyph resolution so it cannot disagree with what is drawn. The first implementation passed the fixture clean because of the empty-sequence early return and zero-width codes; both are fixed and covered by the fixture.
🤖 Generated with Claude Code
Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.