Phase 5 of REVIEWS/DART_PDF_VS_MAKEPAD_PDF_GAP_ANALYSIS.md.
Step 5.1, real metrics (new pdf-graphics/src/sfnt.rs):
- Reads head, hhea, OS/2 and hmtx from embedded TrueType/OpenType programs,
which the review asks for by name. sTypoAscender/Descender are preferred
over hhea when non-zero, since subset fonts often zero them; sCapHeight
and sxHeight are only read from OS/2 version 2 and later, because reading
them from a v1 table returns whatever bytes follow it.
- Every read is bounds-checked. A table pointing outside the file, a
truncated directory or a zero unitsPerEm yields None rather than a panic
or a divide by zero. These are untrusted embedded programs.
- resolve_font() now reads FontFile/FontFile2/FontFile3 and falls back to
the descriptors declared Ascent/Descent only when no program is embedded.
- renderer.rs looks up a per-font ascent instead of leaving ascent_em
permanently None, which had left the fallback ratio always in effect.
Step 5.2, accumulated advances (new pdf-graphics/src/advance.rs):
- measure_advance() implements PDF 32000-1 9.4.4 properly:
(w0/1000 * Tfs + Tc + Tw) * Th/100, with Tw restricted to single-byte
code 32. Applying Tw to a two-byte code whose low byte is 32 is a classic
composite-font bug and is now covered by a test.
- glyph_advances() gives per-glyph widths so a caret or a partial selection
rectangle no longer assumes even spacing.
- GlyphAdvanceCache is keyed by font name and size as the review specifies,
and carries a generation so it rebuilds when page data changes rather
than every frame. Redefining a font drops its stale measurements.
Step 5.3, composite fonts:
- The old build_cid_widths truncated every CID to u8, so any glyph above
255 silently took the default width. It is replaced by CidWidths keyed by
the real CID, with the CMap resolved from a predefined name or an
embedded stream.
- ResolvedFont::decode_text returns None for a composite font with no
ToUnicode map instead of guessing Latin-1 and producing plausible
nonsense.
Tests: new tests/phase5_exit_criterion.rs asserts the exit criterion.
Selection across a proportional and a monospaced run verifies the second
run starts at the first ones true end; per-glyph advances sum to the run
advance and H measures wider than l, which an even-spacing estimate would
not. Copy returns correct Unicode through ToUnicode including a non-ASCII
scalar, and through WinAnsi for simple fonts. Search finds both hits across
two lines with rectangles inside their own runs and the right vertical
order. Cache reuse is asserted through hit and miss counts.
Validation:
TEST_TARGET=pdf ./tools/test-rust-clean.sh (203 tests)
TEST_TARGET=pdf-ui ./tools/test-rust-clean.sh (238 tests)
Both rustfmt and clippy -D warnings clean.