Repository navigation
Description
Context
DESIGN.md section 5: settings that change what a computation produces, such as fallback fonts, are part of its memo key. Section 14: pyropdf is compared with frozen pylopdf. #624 measures the exact boxes of words, layout, chars, and search on the fallback faces, and its Notes leave other extraction without them, so that it keeps its memo key. page.content() is that other extraction, although its docstring says that "glyph boxes are the exact boxes of the glyph outlines".
On every page of tests/assets/text/nonembedded-cjk.pdf, after a first rendering, frozen pylopdf's text runs measure the faces, and pyropdf's have no ink:
| Page 2 | Frozen pylopdf | pyropdf on the branch of #624 |
|---|---|---|
TextRun.bbox |
(21.52, 36.4, 138.56, 73.2) |
(20.0, 70.0, 140.0, 70.0), the baseline |
first Glyph.bbox |
(21.52, 36.52, 58.84, 73.2) |
None |
first three Glyph.glyph_id |
12066, 8774, 4007 |
1837, 1384, 2957 |
So page.content() gives no box for glyphs that page.pixmap() draws. evidence/phase1-parity does not compare page.content(), so its report does not show this.
Goal
The text runs of page.content() describe a font that a document does not embed as the fallback face that draws it, as frozen pylopdf does: its glyph boxes, the run's box, and its glyph IDs.
Scope
In: glyph boxes, run boxes, and glyph IDs of text runs; doc.fallback_fonts in the memo key of a content read with text runs; the docstring of page.content().
Out: the advances of a stretched glyph, which #656 tracks. Content reads with text_runs=False, which should keep their memo key.
Paths
crates/pyropdf-engine/src/extract/mod.rs, crates/pyropdf-engine/src/extract/model.rs, crates/pyropdf-python/src/items.rs, crates/pyropdf-python/src/document.rs, python/pyropdf/_page.py, and the tests of content items.
Acceptance
- On
tests/assets/text/nonembedded-cjk.pdf, the text runs ofpage.content()match frozen pylopdf with its CJK extras, read after a first rendering. - Editing
doc.fallback_fontschanges the runs that the next call returns, and a call with the same faces reuses the memoized result. - Without an installed face, the runs are as they are now, and a document whose fonts are all embedded reads no fallback face.
Notes
- Blocked by Measure the exact boxes of non-embedded CJK fonts on their fallback faces #624, which adds
HashandEqtoFallbackfor a memo key andFallback::watching. - Frozen pylopdf fills its faces at its first rendering, so a read before any rendering gives what pyropdf gives now. Compare after one, as
evidence/phase1-parity/dump.pydoes since Measure the exact boxes of non-embedded CJK fonts on their fallback faces #624. - A text run has the fields that Measure the exact boxes of non-embedded CJK fonts on their fallback faces #624 keeps from a pass without faces, such as advances, so a content read cannot simply run through the faces while Keep the PDF's advances when hayro stretches a substitute glyph #656 is open: a stretched advance would move too.
Activity
yhay81 commentedon Nov 4, 2026
Claimed. Branch:
claude/658-content-on-faces.yhay81 commentedon Nov 4, 2026
Handoff. Branch
claude/658-content-on-faces, PR #664.Changed. The text runs of
page.content()describe a CJK font that a document does not embed as the face that draws it: glyph IDs, glyph and run boxes, and the font's record, which holds the face's weight. Matrices, advances, text, and paints stay from the read without faces, because a face's stretch would move the advances (#656). A second interpretation through the faces runs only whenFallback::watchingreports a font that a face would draw, andclipapplies after it.doc.fallback_fontsjoins the memo key of content with text runs;text_runs=Falsekeeps its key.Checks.
python tools/check.py --lockedandcargo check --manifest-path fuzz/Cargo.toml --bins --lockedpass on the PR's head, before CI. Every field of every run on the 7 pages ofnonembedded-cjk.pdfmatches frozen pylopdf after a rendering, and also without faces; the IDs, boxes, and weight are pinned intests/test_fallback_fonts.py.Next action. The owner reviews and merges #664 once CI is green on its head.
Owner decision needed. #658 is not linked as a blocker of #636, so I have not treated it as covered by the merge authorization there. To put it in 0.2.2, either link it to #636 or say so on the PR.
Still open.
evidence/phase1-paritydoes not comparepage.content(), so its report would not show a regression in these runs. #656's branch is not pushed yet and will touch the same record code inmodel.rs.Measure the text runs of page.content() on the fallback faces (#658) (#…