fix(fonts): declare the bundled subset's unicode-range - #3086
Open
akzarma wants to merge 1 commit into
Open
Conversation
`generate-font-data.ts` embeds the `-latin-` subset file of every canonical family, but `buildFontFaceCss` emitted those faces with no `unicode-range`, so each one advertised coverage it does not have. The supplementary Google fetch then skipped every subset of a weight the bundle already "covered", on the premise that a covered weight is full-coverage. A bundled family therefore cannot render the scripts its latin subset omits. Decoding the committed font data: `noto-sans-jp` weight 400 is 218 codepoints with zero kana and zero kanji, while the typography reference lists the family as CJK (Japanese); `inter` and `roboto` weight 400 are ~230 codepoints, missing latin-ext, Cyrillic and Greek. Because FONT_ALIAS_MAP routes arial, helvetica, segoe ui and verdana into those families, ordinary Central European, Turkish, Vietnamese and Cyrillic text loses the requested typeface and falls through to whatever the render host has installed. Declare the latin range on the embedded faces, and skip only the fetched face that duplicates that subset instead of the whole weight. Merged PR heygen-com#1123 made the same correction for Google-fetched faces. The exact range is a constant here because the generator does not record the subset it embedded; deriving it per face at generation time would be the more precise follow-up.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What breaks
A pre-bundled family cannot render the scripts its embedded subset omits. The requested typeface is lost for those codepoints and the text falls through to whatever fonts the render host happens to have installed — the outcome a deterministic font injector exists to prevent.
Decoding the committed
fontData.generated.tsand reading each font'scmap:@fontsource/inter:400:normalł,č,ğ,ș,ạ; no Cyrillic; no Greek@fontsource/roboto:400:normal@fontsource/noto-sans-jp:400:normalskills/hyperframes-creative/references/typography.mdlistsNoto Sans JP | 400 · 700 | CJK (Japanese), and the realnoto-sans-jp-japanese-400-normal.woff2(~1 MB) sits unused in the same package directory.This is not limited to CJK, and that is the more common case:
FONT_ALIAS_MAProutesarial,helvetica,segoe ui,verdanaand friends into Inter/Roboto, so ordinary Central European, Turkish, Vietnamese and Cyrillic text hits it.Root cause
packages/producer/src/services/deterministicFonts.ts, path 1 ofbuildFontFaceCss:scripts/generate-font-data.tsresolves${slug}-latin-${weight}-${style}.woff2— the latin subset, always.unicode-range, so it advertises the full range.The fix
unicode-rangeon embedded faces, so they stop claiming coverage they lack.Merged PR #1123 made the same correction for Google-fetched faces (a face without
unicode-rangeclaiming full coverage), so this brings the bundled path in line with it.On download cost:
fetchGoogleFontalready downloads the faces before the caller discards covered weights, so this does not add network requests to the existing fetch — it keeps subsets that were being fetched and then thrown away. It does add@font-facerules and embedded bytes to the generated HTML for bundled families whose weights Google serves in multiple subsets.Known imprecision, called out deliberately: the range is a constant here because the generator does not record which subset it embedded. Deriving the true range per face at generation time would be more precise and is the natural follow-up; it would mean regenerating the committed font data, which felt out of scope for a fix. The constant matches Google's current
latinsubset range, so a handful of combining marks present in some embedded faces (U+0303, U+0309, U+0323) now resolve through a supplementary face instead of the bundled one.Test
deterministicFonts-bundledSubset.test.ts— hermetic, injectsfetchImpl, no network. ForNoto Sans JPweight 400 (a bundled weight) it asserts that every emitted face declares aunicode-range, that the embedded faces declare the latin subset, that the Japanese subset face is injected, and that the duplicate latin face is still skipped.Verified the test bites: against unfixed source 0 pass / 1 fail; with the fix 1 pass / 0 fail.
Test plan
bun test packages/producer/src/services/deterministicFonts-bundledSubset.test.ts→ 1 pass, 0 failbun testondeterministicFonts.test.ts,deterministicFonts-failClosed.test.ts,deterministicFonts-systemCapture.test.ts→ 31 pass, 0 failoxfmt --checkandoxlinton both changed files → cleanfontData.generated.tswith fontTools