feat(fonts): bundle families for Georgian, Armenian and Korean - #539
Conversation
6efa059 to
d9466dbCompareDemchaAV
commented
Aug 11, 2026
Rebased onto the updated Script blocks are swept by letter, not by hand-picked range. The Georgian assertions Amiri is swept in all four faces it ships rather than three. The sibling assertion that On the fonts version. The same runbook said three things about the fonts artifact that had stopped being true, Full reactor gate green. |
355a469 to
6c1efb1Compared9466db to
db7c212Compared250dfd to
9d2cf46Comparedb7c212 to
3528a4fCompareDemchaAV
commented
Aug 11, 2026
Pushed The Greek and Cyrillic claims were mine and they were too big. The Greek assertion ran Both are swept by letter now, which skips the unassigned code points without shortening On the version metadata in Full reactor gate green. |
3528a4f to
db75bd8CompareDemchaAV
commented
Aug 11, 2026
Pushed The coverage table was measured on narrower ranges than it named, and the gap is bigger The sweep goes by letter now, so a range can span the unassigned gaps and the punctuation a On the release order — the runbook was fair to criticise. "Bump, tag, then update the Full reactor gate green here and at the tip of the stack. |
1b99cfb to
3c7b4f2Comparedb75bd8 to
d738b53Compare3c7b4f2 to
25d7aeeCompared738b53 to
a0ca04cCompareDemchaAV
commented
Aug 12, 2026
Pushed The table's preamble overclaimed what the test pins. It said And the Greek constant left behind when the wide rows moved to a letters-only sweep was Full reactor gate green here and at the tip. |
25d7aee to
6f704a6CompareGeorgian and Armenian had no covering family in the bundled set, so a document written in either rendered as rows of question marks unless the author registered a font of their own. Noto Sans Georgian and Noto Sans Armenian join the catalog, shipping in the same graph-compose-fonts 1.1.0 as the Arabic and Hebrew pair. Both cover their script in full — Georgian in Mkhedruli and in Mtavruli, the capitals a heading is set in, which Unicode encodes in a block of its own far from the lowercase range. Upstream publishes them as variable fonts with no static weights to take, so the artifact carries the regular instance and every other face resolves to it: bold renders unemboldened rather than failing, which the tests state rather than leave to be found. The family list the measurement/render width-parity test walks is now read off the catalog instead of repeated, so a family added to the catalog is covered the day it lands.
Korean had no covering family, so a document written in it rendered as rows of question marks unless the author registered a font of their own. Gothic A1 joins the catalog with a drawn regular and bold. It carries all 11 172 precomposed Hangul syllables, both jamo forms, and — unlike the better-known Korean families — Latin-1, Latin Extended-A, Cyrillic and Greek. That last part decided it: a paragraph is drawn in one family, so a Korean sentence holding a European name is drawn in the Korean font, and a family with ASCII but no accents turns Müller into M?ller with the Korean around it rendering perfectly. Hanja are not covered. Chinese and Japanese stay unbundled, and the docs now say why rather than leaving it looking like an omission: the official static Noto CJK faces use CFF outlines, which the PDF backend cannot embed at all, and the variable ones default to weight 100 — the weight a PDF draws, since it applies no instancing. The script table in the fonts README was measured against the shipped binaries and two of its rows were wrong. Hebrew was listed as reachable only through David Libre, while Tinos and Cousine carry the whole block; Latin Extended-A and Vietnamese were listed as covered by any bundled family, when nine families cover almost none of either. Both rows are corrected and the whole table is now held to the binaries by a test, so it fails a build instead of going quietly stale. Three guards stop keying on the absence of a marker and start keying on the signal they exist for: the script demos assert their script actually reached the page rather than only that no '?' did, the stale-artifact diagnostic is driven through every family that needs it instead of one sample, and the fonts and emoji version pins are discovered across the poms that declare them rather than listed two at a time. The Georgian and Armenian binaries are the variable fonts upstream publishes, not static instances, and the comments describing them said otherwise. What makes them safe is that their wght axis defaults to 400, so that is now asserted — the same vendoring done with a Noto CJK variable font would have shipped hairline Thin with nothing failing.
The Georgian assertions stopped at U+10FA and U+1CBA, where the blocks used to end. Unicode has since assigned letters past both, and the bundled family carries them — so a test that reported complete coverage had not looked at them. Extending the ranges then failed on U+1CBB and U+1CBC, which are unassigned: a block is not a solid run of letters, it has gaps, and punctuation and modifiers sit among them. Rather than carry a hand-kept list of holes — a list that needs an edit every Unicode release — the sweep asks whether each code point is a letter and skips the rest. The range stays whole and the claim stays honest without anything to maintain. Amiri is swept in all four of the faces it ships, not three. The sibling assertion that the faces never collapse onto one another meant the fourth carried the whole script with nothing checking it could draw it. The release runbook said three things about the fonts artifact that had stopped being true, each in the direction of doing too little: that core carries no pin on it (it does, at test scope, so a single-module build resolves it from the local repository), that the engine needs no fonts bootstrap, and that two poms re-pin the version where six do. It also now says when the tag may be cut — after every family meant for that version has landed, since a published artifact is immutable while the catalog goes on naming it as the version a family arrived in.
The Greek assertion ran U+0391..U+03A1 — half the capitals, no lowercase — because that dodged the unassigned U+03A2, while the Javadoc and both docs promised the script. Cyrillic ran U+0410..U+044F, the Russian alphabet inside the block, without Ё or Ї. Both are swept by letter now, which skips the unassigned code points without shortening the range: the modern Greek alphabet including the accented capitals, and the whole Cyrillic block. Greek stops there rather than covering its block, because the archaic and Coptic letters at U+0370 are carried by no text face — so the prose says "the modern Greek alphabet" instead of "Greek", which is what was measured.
The table row said "Cyrillic" and the assertion behind it swept U+0410..U+044F — the Russian and Ukrainian letters, not the block. Over the whole block the count is not eighteen families but four: the extended letters the Central Asian and Caucasian languages need are carried by Lato, Tinos, Cousine and Gothic A1 and by nothing else in the set. Those are different claims and the table now makes both, each with its own assertion. Greek is likewise the modern alphabet rather than its block, whose archaic and Coptic letters no text face carries. The sweep goes by letter, so a range can span the gaps and the punctuation a block really contains instead of being trimmed until it passes — which is how the Cyrillic row came to be measured on the alphabet in the first place. The changelog also states a shaping limit found while checking a review claim, rather than leaving it to be discovered: a word too long for its column is broken with the contextual forms it was given while whole, so the letters either side of the break keep their connecting strokes. Shaping runs before wrapping because wrapping measures widths and the forms carry them, so re-shaping the halves would change the widths the wrap already spent. The fonts release procedure is one numbered sequence now. It read as "bump, tag, then update the pins" beside a separate rule that the tag is cut last; both were true, and a reader following the first in order publishes an artifact that the catalog then promises families it does not carry.
The table's own preamble promised the test holds every row "to those exact families". It holds the single-family rows that way; the wider ones it holds to a count, which is a weaker claim and the one worth stating, since a reader deciding whether to trust a row deserves to know which kind it is. The Greek constant the wide rows used before they moved to a letters-only sweep is unreferenced now, so it goes.
a0ca04c to
9a9717eCompareUh oh!
There was an error while loading. Please reload this page.
Why
Georgian, Armenian and Korean had no covering family in the bundled set. A document
written in any of them rendered as rows of question marks — the substitution is logged at
WARN and the PDF still opens, so nothing about the output announced that the font was the
problem — unless the author registered a font of their own.
What
Three families join the catalog, shipping in the same
graph-compose-fonts1.1.0 as theArabic and Hebrew pair:
NOTO_SANS_GEORGIAN,NOTO_SANS_ARMENIANandGOTHIC_A1.Each covers the second case its script has, which is the part that is easy to lose:
Armenian capitals and the
ևligature that is the word "and"; Georgian Mtavruli, thecapitals a heading is set in, which Unicode encodes in a block of its own (U+1C90) far from
the lowercase range; and for Korean both the 11 172 precomposed syllables and the
conjoining jamo a decomposed (NFD) string is made of.
Gothic A1 was chosen over the better-known Korean families for its Latin. A paragraph is
drawn in one family, so a Korean sentence holding a European name is drawn entirely in the
Korean font — Nanum Gothic covers 17 % of Latin-1 and no Cyrillic, which turns Müller
into M?ller while the Korean around it renders perfectly. Gothic A1 covers Latin-1,
Latin Extended-A, Cyrillic and Greek in full.
Georgian and Armenian upstream publish only as variable fonts with no static face to take.
The bundled file is that variable font; a PDF applies no instancing and draws the
fvardefault, which for these two is regular — so bold resolves to it and renders unemboldened.
That default is now asserted rather than assumed: the Noto CJK variable fonts default to
weight 100, so the same vendoring done with one of those would have shipped a family that
renders every page hairline Thin with full coverage and nothing failing.
Chinese and Japanese stay unbundled and the docs say why rather than leaving it looking
like an omission: the official static Noto CJK faces use CFF outlines, which the PDF
backend cannot embed at all, and the variable ones carry the weight-100 default above.
fonts/README.mdgains a "which script needs which family" table, measured against theshipped binaries. Two rows written from reputation rather than from the
cmapwere wrongand are corrected: Hebrew is carried by Tinos and Cousine as well as David Libre, and
Latin Extended-A and Vietnamese are not covered by "any bundled family" — nine families
cover almost none of either.
Three guards also stop keying on the absence of a marker and start keying on the signal
they exist for — the script demos assert their script reached the page rather than only
that no
?did; the stale-artifact diagnostic is driven through every family that needsit instead of one sample; and the fonts and emoji version pins are discovered across the
poms that declare them rather than listed two at a time, which was already two short.
Tests
BundledScriptCoverageTest— holds every row of the README table to the binaries, sothe table fails a build rather than going stale.
KoreanFontCoverageTest— all 11 172 syllables in both faces, both jamo forms, and theLatin-1 / Latin Extended-A / Cyrillic / Greek the docs promise; bold is a distinct face.
GeorgianArmenianFontCoverageTest— both cases of each script, and that the variablefonts resolve to their regular instance.
BundledFamiliesTest— the families are listed, the faces upstream does not shipcollapse onto a real resource, and every family needing the stale-artifact diagnostic
gets it.
KoreanGlyphsDemoTest,GeorgianArmenianGlyphsDemoTest— render each script (Koreanalongside accented Latin, and decomposed) and read the PDF back to prove the glyphs
reached the page.
available_fonts_previewlayout snapshot refreshed: the showcase grows from five pagesto six.
clean verifyacross core, render-pdf/docx/pptx, templates,testing, qa, coverage).