Repository navigation
Description
Context
DESIGN.md section 7, and #482. A save compacts each face that pyropdf embeds into a bare CID-keyed CFF program, whose charstrings are drawn from skrifa's unhinted outlines (Charstring in crates/pyropdf-engine/src/fonts/compact.rs). The program keeps none of the face's hints: no stem hints or hint masks in its charstrings, and none of the hint values of its Private DICT, such as the blue zones and the standard stem widths. A viewer that hints CFF text from the program, as FreeType's CFF driver does and as hayro 0.9 does when it hints (LaurenzV/hayro#1705), then draws the saved text differently from the face, whatever pyropdf's own renderer does: rendered hinted by hayro, canvas text in NotoSansJP-subset.otf differs in 873 of 36,000 pixels after the save (evidence/hinted-subsets, from #18). A program that subset_fonts subsets keeps the charstrings it keeps, hints included, so only compaction loses them. The owner asked for this issue on 2026-11-04 (#18).
Goal
A compacted CFF program keeps the hints of the glyphs it keeps and the hint values of the face, so that a viewer hints the saved text as it hints the face.
Scope
In: the CFF path of compact. Each kept glyph's charstring keeps its hstem, vstem, hstemhm, vstemhm, hintmask, and cntrmask, and the Private DICT of each font DICT keeps BlueValues, OtherBlues, FamilyBlues, FamilyOtherBlues, BlueScale, BlueShift, BlueFuzz, StdHW, StdVW, StemSnapH, StemSnapV, ForceBold, LanguageGroup, and ExpansionFactor (Adobe Technical Notes 5176 and 5177). Out: TrueType programs, which keep their instructions; the auto-hinter's reference glyphs, which the owner chose not to keep in subsets (#18).
Paths
crates/pyropdf-engine/src/fonts/compact.rs, crates/pyropdf-engine/src/fonts/cff.rs, crates/pyropdf-engine/src/fonts/tests.rs, tests/test_canvas_embedded.py, tests/test_font_subset_poppler.py, evidence/hinted-subsets.
Acceptance
- Rendered hinted by hayro, canvas text in a CID-keyed CFF face draws the same before and after a save, which
evidence/hinted-subsetsreports with no differing pixel, and rendered unhinted it still does. - A compacted program's charstrings carry the face's stem hints and masks for each kept glyph, and its Private DICTs the face's hint values; the
fontsfuzz target still runs clean. -
test_100_stamps_save_no_larger_than_krillastill holds, and the pull request reports how many bytes the hints add.
Notes
- skrifa reads a charstring through
skrifa::raw::ps::cs, whoseCommandSinkreceives the stems and the masks as well as the outline, so a writer can replay them instead of drawing the outline alone. - A hint mask selects stems by their order in the charstring, so each glyph's stems go with the masks that refer to them.
- The comparison needs hayro rendering with
RenderSettings::hinting, which pyropdf's own renderer leaves off since hayro-syntax: object numbers as u32 (I8) #18; the evidence harness renders with hayro directly.
Activity
yhay81 commentedon Nov 3, 2026
Claimed. Branch:
claude/611-cff-hintsCompare a CFF face's hinted renders through page.pixmap (#611)
Record the hinted renders without hayro's glyph cache (#611)
yhay81 commentedon Nov 4, 2026
Handoff: PR #638 on branch
claude/611-cff-hints.evidence/hinted-subsets). The hints add 180 to 314 bytes to the programs oftest_100_stamps_save_no_larger_than_krilla, which still holds.tools/check.pypasses on the head merged with main at 093c9b3. Thefontsfuzz target and the poppler tests run only in CI.Keep a CFF face's hints when a save compacts it (#611) (#638)