Skip to content

Measure text with real font metrics, supplied by the caller - #23

Merged
tannevaled merged 2 commits into
mainfrom
font-metrics
Sep 5, 2026
Merged

Measure text with real font metrics, supplied by the caller#23
tannevaled merged 2 commits into
mainfrom
font-metrics

Conversation

@tannevaled

Copy link
Copy Markdown
Contributor

Text measured with real font metrics, with the fonts supplied by the caller.

xfa keeps its zero dependencies. A template names typefaces and carries none;
the PDF it travelled in carries them; this package reads neither. So the font
set is an argument:

  • Face is two methods — an advance in thousandths of an em, and the
    face's vertical metrics.
  • Typeface is one family's four faces.
  • FontSet is an ordered map of families with pdf.js's FontFinder.find
    ported over it
    (fonts.js:90-155), pass for pass, including the ordered
    alternation its strip pattern really is: bolditalic before bold, it
    last, and a scanner rather than a chain of replacements.
  • PlaceWithFonts(form, set) is the new door. Place passes none.

No font file is read here. tools/fontdump, outside the module, wires
go-opentype/opentype to Face in about thirty lines.

text.go grows the branch it had been written around: addString with a face
is text.js:175-206. compute is untouched — it is the same function in both
regimes.

Measured, and the font set is named

Fifteen families from /System/Library/Fonts/Supplemental: Arial, Times New
Roman and Courier New in four faces each, plus Verdana, Tahoma, Georgia, Arial
Narrow, Arial Black and Wingdings. Arial stands in for Helvetica, Myriad Pro,
HelveticaNeueLT Std and Helvetica LT Std. 211 683 of the corpus's 214 011
typeface references reach a family (98.9%).

The unit is a LINE, because pdfium measures with its own files and no
implementation reproduces another's advances in points.

over 7 358 measured leaves paired with pdfium v0.18.0 with fonts
same line count 25.9% 82.5%
one line too many 26.0% 6.9%
two or more too many 38.4% 0.1%
before after
body leaves off the paper 1000 10
forms with one 34 4
agree on sheet+y 162 200 164 604
forms perfect on sheet+y 403 415
agree on x 173 806 173 806
corpus sheets 3 088 3 088

The ten left are the nine pdfium also puts off the paper and g-1055's one
unpaired leaf.

29 forms improve and one is worse: us-irs__f6252, by one leaf, where a
12 pt heading in HelveticaNeueLT Std wraps for pdfium and not for our Arial
stand-in — and where the old height agreed by accident. Named in the comment.

The control caught a defect before any number was believed

PlaceWithFonts(form, nil) must reproduce v0.18.0 exactly. Seven forms
differed, on letterSpacing="-0.002em": this package reads a length strictly
and a strict reading stopped four forms' whole tables. pdf.js reads every
length with getMeasurement, which drops a unit it does not know; that
function is now ported properly and a FONT's two lengths take it. All 560 forms
are byte-identical to v0.18.0 with no fonts.

Gate: 100% of statements with XFACORPUS unset, go vet, gofmt -l empty,
-race, thirteen cross-compile targets, CGO_ENABLED=0, GOWORK=off, Go
1.27.1 via GOTOOLCHAIN. The corpus suite passes with the environment set.

Refs #7.

🤖 Generated with Claude Code

tannevaled and others added 2 commits September 5, 2026 18:18
A template names typefaces and carries none; the PDF it travelled in
carries them. So the font set is an argument and not a dependency: Face
is two methods, Typeface is a family's four faces, and FontSet is an
ordered map with FontFinder.find over it (fonts.js:90-155), pass for
pass, including the ordered alternation its strip pattern really is.

text.go grows the branch it had been written around. addString with a
face is text.js:175-206 — advances in thousandths of an em scaled by the
written size, a line height from the face's own vertical metrics and a
first line that is that height less the gap. compute is untouched: it is
the same function in both regimes, which is why real metrics reach the
layout without moving the line breaking or the arithmetic above it.

The stack under it is FontInfo and FontSelector folded together, with
the replacement that decides everything: when the typeface is not found
the WHOLE of xfaFont goes, size included, so a draw asking for
Wingdings at 14pt is measured at ten points. A rich text's spans push
their own font, inheriting each of the five values they leave falsy.

leafsize.go resolves a leaf's <font> the way layoutNode does — its own,
else the nearest above it in the template (html_utils.js:232-241) — and
reads <para lineHeight>, which nothing could read before.

Place is unchanged and passes no fonts, so v0.18.0's numbers stand;
PlaceWithFonts is the new door.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A control run before anything was measured: PlaceWithFonts(form, nil)
must reproduce v0.18.0 across the corpus, and it did not. Seven forms
differed, all on letterSpacing="-0.002em" — 24 draws of ca-cra t1206 and
t1207 — because this package reads a length strictly, following pdfium,
and a strict reading of a relative unit stopped the leaf, its row and
every sibling below it. Four forms lost their whole table.

pdf.js reads every length with getMeasurement (utils.js:78-102), which
drops a unit it does not know and keeps the number, and that function is
now ported properly: unanchored, so it finds a number wherever it first
appears, and with a default for a string holding none. styleMeasure is
it with a default of nought, so the CSS reader is the same code. A
FONT's two lengths take the lenient path; x, y, w and h stay strict.

With that, all 560 forms are byte-identical to v0.18.0 with no fonts.

The tests found one more: stripQuotes on a string of a single quote is
"" in JavaScript and a panic in Go.

100% of statements, with XFACORPUS unset.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@tannevaled
tannevaled merged commit fb608e0 into main Sep 5, 2026
1 check passed
@tannevaled
tannevaled deleted the font-metrics branch September 5, 2026 16:41
tannevaled added a commit that referenced this pull request Sep 5, 2026
…red (#24)

Two measurements this repository could not make, and one number it published
that a sharper instrument contradicts.

THE SHEET. Every judge here compares a box — its x, and the sheet it landed
on with its y — and none of them compares the sheet itself. v0.18.0 moved 378
leaves onto correctly oriented sheets and not one figure in corpus_test.go
changed by a unit, because a leaf at the same (sheet, x, y) on a 612x792 sheet
and on a 792x612 one counted as agreement.

TestSheetsAgainstPdfium asks both halves. The SIZE is the weak one: 528 of the
559 forms give every page area of a form the same medium, so a sheet opened on
the wrong page area is still the right size. Over the corpus the size agrees on
3 035 of 3 035 sheets, the largest disagreement in either dimension being
0.0000 pt — and v0.18.0's reported residue of "7 sheets whose size disagrees"
was 7 FORMS tying at the fourth decimal, 1008.0 against 1008.0001, not a
disagreement about paper at all.

Which PAGE AREA a sheet was opened on has no such blind spot, and it finds ten
sheets on three forms: us-uscis__g-1055 6, i-956g 3, i-956 1 — the three forms
#21 and #23 left open, counted for the first time. placer.sheetAreas records
it here and the probe reads it from GetPage(i)'s form node there, so it is
known even on the 91 sheets of g-1055 whose page area draws no furniture.
Neither side reads it off the geometry being judged.

THE LINE COUNT. doc.go's "same line count as pdfium 25.9% / 82.5%" was not a
line count: it was (our height less pdfium's) over OUR line height, bucketed.
The proxy flatters, because a leaf that disagrees on the number of lines AND on
how tall a line is can come to the same height. pdfium's probe now reports its
own line count per leaf, and the answer is 23.2% and 76.3%. 1 002 leaves the
proxy scored as agreeing take a different number of lines — fw9's
Page4.Col2.F among them, four lines of 9 pt here against pdfium's three of 12.

Where the counts do agree the whole residue is the FIRST line: 5 372 of 5 373.
Our height for every line after the first is pdfium's to a hundredth of a point
on 1 177 of 1 178. pdfium makes the first line the font size; pdf.js makes it
the face's line height less its gap, and this follows pdf.js — so that one is a
disagreement with pdfium and not a defect against the reference. Nothing in the
measurement is changed for it.

The layout does not move: all 560 forms are byte-identical before and after.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant