Skip to content

feat(core,fonts): font coverage — M4 4.2 - #33

Merged
aenawi merged 1 commit into
mainfrom
font-coverage
Sep 5, 2026
Merged

aenawi merged 1 commit into
mainfrom
font-coverage

Conversation

@aenawi

@aenawi aenawi commented Sep 5, 2026

Copy link
Copy Markdown
Owner

Shaping asks what a font did with a letter it has. This asks the question underneath it: has the font got the letter at all?

A font with no shaping tables renders Arabic as disconnected letters; a font with no Arabic renders it as a row of empty boxes, and no shaping table would have saved it. "Install a font" is the wrong advice for the first and the only advice for the second, so they are separate modules reporting separate facts.

What a font is judged over

Only the characters it answers for. The Latin, the European digits and the ASCII punctuation of a mixed paragraph are drawn by the other font slot, so reporting an Arabic font for having no Q would be reporting a font for text it was never asked to draw — ADR 0004's first failure mode through a new door.

Two exclusions inside that:

  • Format characters. No font draws U+0600 ARABIC NUMBER SIGN; a check that said otherwise would report every font there is.
  • Presentation forms. Pre-shaped text is already a blocking defect with a repair of its own, and a second finding there would be the tool arguing with itself about one character.

By name

charname.rs is the Arabic script's Unicode names, generated by scripts/make-char-names.py from the UCD Python itself ships — no network, no third-party module — and refreshed by make names. A finding reads U+067E ARABIC LETTER PEH, not U+067E. It answers None outside the four logical-order Arabic blocks rather than inventing a name, which is the refusal joining::JoiningType::Unstated already makes.

The port and its adapter

ports::FontSource takes a family name and answers with bytes; mirsam-fonts implements it over the platform's font directories. mirsam-core still opens nothing and invariant 1 holds. The index reads each file's naming table alone rather than the file — 690 families in 118 ms on macOS instead of half a gigabyte of outlines — and a family nobody has is an answer, not an error.

Two things real fonts taught that the specification did not

  1. macOS states English family names in Mac Roman and keeps the Unicode records for the localisations, so refusing the legacy encoding leaves Helvetica.ttc naming itself nothing at all. Mac Roman agrees with ASCII below 0x80 and diverges above it, so an all-ASCII record is decoded exactly and anything else is left unread rather than guessed at — a wrong name is worse than a missing one, because it resolves.
  2. A family is spread over files that all state the family name, so a document saying Arial means the regular face. Taking the first match gives Arial Bold Italic.ttf, which on this machine has no Arabic while Arial.ttf has. bold.ttf joined the font fixtures so that regressing on this fails a committed test rather than a user's deck.

The boundary this does not cross

Nothing in the audit path calls any of this yet. Turning a coverage report into a finding is 4.3's decision, and Coverage::covers_nothing is deliberately the only conclusion the evidence supports on its own.

Validation

cargo test runs coverage against the hand-built fonts, which map U+0621..U+064A and nothing else, so every boundary the check draws shows up as a character the fixture has or has not got — and the four fonts agree about coverage exactly while disagreeing about shaping. mirsam-fonts resolves a family name to bytes and proves it got the right file by shaping through it.

Beyond the fixtures, against macOS:

font result
Helvetica, Comic Sans MS every Arabic letter of the text missing
Times New Roman, Geeza Pro complete
Mishafi complete but for Persian peh and Urdu tteh
Arial Arial.ttf, complete, four joins of five — ADR 0008's reh, still not a defect

make verify passes.

Shaping asks what a font did with a letter it has. This asks the question
underneath it: has the font got the letter at all? A font with no shaping
tables renders Arabic as disconnected letters; a font with no Arabic renders it
as a row of empty boxes, and no shaping table would have saved it. "Install a
font" is the wrong advice for the first and the only advice for the second, so
they are separate modules reporting separate facts.

A font is judged only over the characters it answers for. The Latin, the
European digits and the ASCII punctuation of a mixed paragraph are drawn by the
other font slot, so reporting an Arabic font for having no Q would be reporting
a font for text it was never asked to draw — ADR 0004's first failure mode
through a new door. Format characters are out because they are meaning rather
than shape: no font draws U+0600 ARABIC NUMBER SIGN, and a check that said
otherwise would report every font there is. Presentation forms are out because
pre-shaped text is already a blocking defect with a repair of its own, and a
second finding there would be the tool arguing with itself about one character.

Missing characters come back named. charname.rs is the Arabic script's Unicode
names, generated by scripts/make-char-names.py from the UCD Python itself ships
— no network, no third-party module — and refreshed by make names. It answers
None outside the four logical-order Arabic blocks rather than inventing a name,
which is the refusal joining::JoiningType::Unstated already makes, and
coverage::judges is built on it.

The rest is not the domain's. ports::FontSource takes a family name and answers
with bytes; mirsam-fonts implements it over the platform's font directories;
mirsam-core still opens nothing and invariant 1 holds. The index reads each
file's naming table alone rather than the file — 690 families in 118 ms on
macOS instead of half a gigabyte of outlines — and a family nobody has is an
answer, not an error.

Two things real fonts taught that the specification did not. macOS states
English family names in Mac Roman and keeps the Unicode records for the
localisations, so refusing the legacy encoding leaves Helvetica.ttc naming
itself nothing at all; Mac Roman agrees with ASCII below 0x80 and diverges
above it, so an all-ASCII record is decoded exactly and anything else is left
unread rather than guessed at. And a family is spread over files that all state
the family name, so a document saying Arial means the regular face: taking the
first match gives Arial Bold Italic.ttf, which on this machine has no Arabic
while Arial.ttf has. bold.ttf joined the font fixtures so that regressing on
that fails a committed test rather than a user's deck.

Nothing in the audit path calls any of this yet. Turning a coverage report into
a finding is 4.3's decision, and Coverage::covers_nothing is deliberately the
only conclusion the evidence supports on its own.

Validated beyond the fixtures against macOS: Helvetica and Comic Sans MS come
back missing every Arabic letter of the text, Times New Roman and Geeza Pro
come back complete, Mishafi shapes perfectly and lacks only Persian peh and
Urdu tteh, and Arial resolves to Arial.ttf with complete coverage and four
joins of five — ADR 0008's reh, still not a defect.
@aenawi
aenawi merged commit 6463f28 into main Sep 5, 2026
1 of 2 checks passed
@aenawi
aenawi deleted the font-coverage branch September 5, 2026 05:57
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