Skip to content

Read the letters of the languages that use them - #4

Merged
tannevaled merged 2 commits into
mainfrom
glyph-names
Aug 27, 2026
Merged

Read the letters of the languages that use them#4
tannevaled merged 2 commits into
mainfrom
glyph-names

Conversation

@tannevaled

Copy link
Copy Markdown
Contributor

The glyph names here covered the Latin an English document is set in and stopped. A document in Czech, Polish, Slovak, Hungarian, Turkish, Romanian, Latvian or Lithuanian is set in the block that follows, and every one of its accented letters was read as nothing at all.

That is worse than it sounds. A dropped character leaves no mark: Příliš comes back Píliš and still looks like a word.

Censused before deciding what to fix

Over the whole 118 833-file corpus: of 52.4 million glyph names, 483 382 could not be turned into text, across 4 789 files. Five shapes, and only three were safe to act on.

shape count verdict
the missing AGL block ~144 000 fixedrcaron, zdotaccent, Ccaron, nacute, aogonek, scedilla, gbreve, Uhungarumlaut, IJ
a.variant 7 272 fixed — the part before the first full stop is the name
f_i ligatures 3 024 fixed — a name is its parts, in order
uniXXXXXXXX 49 740 left alone, deliberately
g0, C7 279 253 unrecoverable — a subset font's own numbering

The one I did not fix is the interesting one. uni00000048 looks decodable. Read as the specification says it is U+0000 then U+0048 — but the values that appear are 0x15, 0x48, 0x44, 0x55: glyph numbers a producer dressed in a Unicode-looking name. Decoding them would turn text that is merely missing into text that is confidently wrong, which is the worse failure.

Measured, same census before and after

unreadable names          483 382 -> 440 865
files losing a character    4 789 ->   4 332
named as a ligature         3 024 ->       0
named with a variant        7 272 ->     885

And on the file that started it, through pdfops text:

- F unc tion D enition:          - Di eomor phism of Or ig inal D omain
+ F unc tion D efinition:        + Diff eomor phism of Or ig inal D omain

A code standing for two letters has to give back two, so Font.Text now asks the new TextOfGlyphName, and RuneOfGlyphName refuses what will not fit in one character.

⚠ The remaining spurious word breaks in that output (Or ig inal) are a different defect, in extract's word splitting, not in the glyph names.

100% statement coverage, go vet and gofmt clean, nine cross-compile targets.

The glyph names here covered the Latin an English document is set in and
stopped. A document in Czech, Polish, Slovak, Hungarian, Turkish, Romanian,
Latvian or Lithuanian is set in the block that follows, and every one of its
accented letters was read as nothing at all — which is worse than it sounds,
because a dropped character leaves no mark: "Příliš" comes back "Píliš" and
still looks like a word.

Counted over the whole 118 833-file corpus: of 52.4 million glyph names,
483 382 could not be turned into text, and the largest nameable share of those
was that block — rcaron 650, zdotaccent 617, Ccaron 611, nacute 602, aogonek
602, scedilla 597, gbreve 597, and a hundred more.

Two rules from the Adobe list were missing as well, each costing real text. A
name may carry a variant after a full stop: "a.sc" is a small-capital A and is
still an A, 7 272 of them. And a name may be its parts with underscores between
them: "f_i" is the fi ligature, and without it "Definition" comes back
"Denition" — not a missing character so much as a misspelt word. A code that
stands for two letters has to give back two, so Text now asks TextOfGlyphName,
and RuneOfGlyphName refuses what will not fit in one character.

Measured the same way before and after, over the same corpus:

  unreadable names          483 382 -> 440 865
  files losing a character    4 789 ->   4 332
  named as a ligature         3 024 ->       0
  named with a variant        7 272 ->     885

What is left is what cannot be recovered rather than what has not been: 279 253
names are g0 or C7, a subset font's own numbering, which says nothing about any
character.

And one that looks recoverable and is not: 49 740 names of the form
uni00000048. Read as the specification says, that is U+0000 then U+0048 — but
the values that appear are 0x15, 0x48, 0x44, 0x55, which are glyph numbers a
producer dressed up in a Unicode-looking name. Decoding them would turn text
that is merely missing into text that is confidently wrong, so they are left
alone.
…umber

A name may spell out more than one character: a letter and the accent over it,
or the three pieces a Hebrew cluster is written in. The reader capped a name at
six hex digits, so every one of those was refused.

It has to be told apart from something that looks exactly like it. Producers
write a glyph's own number in the same shape — uni00000048 is glyph 72, not
U+0000 then U+0048 — and 49 740 names in the figure corpus are that. Reading
them would turn text that is merely missing into text that is confidently
wrong, which is the worse failure of the two.

One observation separates them: a real sequence never begins with U+0000,
because nothing is written after a character that does not exist. Of the 3 875
genuine sequences found across 14 823 embedded fonts, not one starts with a
zero group; of the disguised glyph numbers, all of them do.

My own sweep does not see those 3 875 — it looks at the first five pages of a
file rather than at every font — so the rule is carried on the strength of the
sweep that did, and on its being harmless: a name it refuses today it still
refuses.

A name of five or six digits is left to the older rule that reads it as one
character. What producers mean by those has not been measured, and this change
is not the place to guess.
@tannevaled

Copy link
Copy Markdown
Contributor Author

Pushed a second commit, from the robustness campaign's report.

parseHexName capped a name at six hex digits, so every multi-code-point AGL sequence was refused — uni004A0301 (J plus a combining acute), uni05DC05BC05B9 (a three-code-point Hebrew cluster). The campaign found 3 875 legitimate ones across 28 of 14 823 embedded fonts.

The difficulty is that they are indistinguishable in shape from the 49 740 disguised glyph numbers this PR deliberately refuses. The campaign supplied the rule that separates them: a real sequence never begins with U+0000, because nothing is written after a character that does not exist. Zero of the 3 875 start with a zero group; all of the disguised ones do.

I state plainly that my own census does not see those 3 875 — it reads the first five pages of a file rather than every embedded font — so the rule is carried on the strength of the sweep that did measure it, and on being harmless in mine: a name refused before is still refused.

Re-measured over the forms and adversarial corpora (3 197 files, 4.6 M glyph names), which is where these live:

unreadable names          20 203 -> 15 159
files losing a character     332 ->    248
named with a variant       3 672 ->     25
named as a ligature          587 ->      1

Still 100% statement coverage, go vet and gofmt clean, nine targets.

@tannevaled
tannevaled merged commit 192cd05 into main Aug 27, 2026
2 checks passed
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