Skip to content

Measure the text runs of page.content() on the fallback faces #658

Closed

Description

@yhay81

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 of page.content() match frozen pylopdf with its CJK extras, read after a first rendering.
  • Editing doc.fallback_fonts changes 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

Activity

  1. added this to the Phase 1: Reading milestone on Nov 4, 2026
  2. added
    area:enginecrates/pyropdf-engine: interpretation, rendering, writing, understanding
    on Nov 4, 2026
  3. added
    in-progressClaimed; the latest comment names the branch
    on Nov 4, 2026
  4. yhay81 commented on Nov 4, 2026

    @yhay81
    OwnerAuthor

    Claimed. Branch: claude/658-content-on-faces.

  5. yhay81 commented on Nov 4, 2026

    @yhay81
    OwnerAuthor

    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 when Fallback::watching reports a font that a face would draw, and clip applies after it. doc.fallback_fonts joins the memo key of content with text runs; text_runs=False keeps its key.

    Checks. python tools/check.py --locked and cargo check --manifest-path fuzz/Cargo.toml --bins --locked pass on the PR's head, before CI. Every field of every run on the 7 pages of nonembedded-cjk.pdf matches frozen pylopdf after a rendering, and also without faces; the IDs, boxes, and weight are pinned in tests/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-parity does not compare page.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 in model.rs.

  6. removed
    in-progressClaimed; the latest comment names the branch
    on Nov 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:enginecrates/pyropdf-engine: interpretation, rendering, writing, understanding

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions