feat(fonts): offline CJK subsetting + zero-CDN fallback (12.7MB -> 2.14MB) - #1135
Conversation
Ship the interface-charset subset of Noto Sans CJK (SC/TC/HK/JP/KR) plus full Thai/Arabic/Latin-ext and Roboto, declared eager under pubspec `fonts:` so the CanvasKit fallback manager finds them locally and never probes the CDN. CJK drops from 12.7MB to 2.14MB (84%) while every language keeps correct glyphs. - assets/fonts/fallback/: 8 subset/full woff2 + Roboto (engine default fallback) - pubspec: eager `fonts:` declarations (packages/ui_kit_library/* + bare Roboto), ui_kit bumped to v2.28.1 (adds injectable LocaleFallbackFont hook) - lib/localization/fallback_font_resolver.dart: single source of the locale to family mapping; install() injects it into ui_kit at startup - app.dart: add per-locale fallback to ThemeData.textTheme (covers raw Text) - language_tile.dart: per-item Localizations.override so the picker renders every language's native name with the correct family - flutter_bootstrap.js: fontFallbackBaseUrl stays on CDN so online, rare user-typed glyphs outside the subset are still fetched on demand Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
These full Noto woff2 chunk sets were mirrored locally for CanvasKit's engine-level fallback. They are now superseded by the eager-bundled subset fonts (previous commit): interface text is covered by the subset, and rare user-typed glyphs are filled from the CDN when online. Removing them saves ~13MB of source tree / product weight with no offline regression (verified: all locales still render offline via the bundled subsets). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Build-time tool that regenerates the bundled CJK subset fonts from the current interface charset. MUST be re-run after any change that adds CJK glyphs (ARB strings, language names, hardcoded literals) — otherwise the subset silently misses them (offline tofu / online CDN fetch). - regenerate.sh: one-command pipeline (download full OTFs, extract charset, subset, deploy to assets/fonts/fallback/) - extract_charset.py: unions ARB values + CJK punctuation blocks + picker native names + hardcoded CJK in Dart source - make_test_page.py: renders per-locale samples for glyph-correctness eyeballing - README documents the "when to re-run" maintenance rule - .venv/full_fonts/out are reproducible intermediates (gitignored) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Qodo reviews are paused for this user.Troubleshooting steps vary by plan Learn more → On a Teams plan? Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center? |
…font-subset-poc # Conflicts: # pubspec.yaml
|
🤖 Automated Review — Oversize PR This round's changes exceed the automated-review limit (551 lines / 300 files, limit 6000 lines / 100 files); AI review was not run. Manual review recommended. First 15 changed files (for a quick scan): |
|
🤖 Automated Review — Oversize PR This round's changes exceed the automated-review limit (551 lines / 300 files, limit 6000 lines / 100 files); AI review was not run. Manual review recommended. First 15 changed files (for a quick scan): |
|
🤖 Automated Review — Oversize PR This round's changes exceed the automated-review limit (551 lines / 300 files, First 15 changed files (for a quick scan): |
|
🤖 Automated Review — Oversize PR This round's changes exceed the automated-review limit (551 lines / 300 files, limit 6000 lines / 100 files); AI review was not run. Manual review recommended. First 15 changed files (for a quick scan): |
|
🤖 Automated Review — Oversize PR This round's changes exceed the automated-review limit (551 lines / 300 files, limit 6000 lines / 100 files); AI review was not run. Manual review recommended. First 15 changed files (for a quick scan): |
PeterJhongLinksys
left a comment
There was a problem hiding this comment.
Automated PR Review
Verdict: 🟢 Approve — no unresolved critical issues
Findings
- 🔴 Critical — none
- 🟡 Warning —
web/flutter_bootstrap.js:14switchesfontFallbackBaseUrlfrom the self-hosted./assets/mirror (deleted in this PR) to the publichttps://fonts.gstatic.com/s/CDN; interface text stays at 0 requests (bundled), but rare on-demand glyphs now hit a Google third-party endpoint at runtime — a behavioral/privacy shift from the prior zero-external-request design. Intentional per the PR body, flagging for awareness. - 🟡 Warning —
lib/localization/fallback_font_resolver.dart:56returns null for Polish/Turkish/Nordic (pl/tr/da/nb/sv/fi), whose ł/ğ/ş/å live in Latin Extended-A; these now rely entirely on the primary font offline (no bundled fallback, CDN unreachable). If NeueHaasGrotTextRound lacks any of those glyphs, offline renders tofu where the old self-hosted mirror would have covered it.viwas explicitly routed to NotoSansLatinExt for exactly this reason — consider whether pl/tr need the same. - 🟢 Nit —
tools/font_subset/README.md:11links to an out-of-repo Obsidian vault path (../../../Documents/docs/raw/...), andextract_charset.py:9docstring points to a different path (doc/theme/...); neither resolves in-repo.make_test_page.py:59warns to "run subset_fonts.sh first" but the script isregenerate.sh. Resolver class doc says families are declaredpackages/ui_kit_library/<Family>, but Roboto is declared bare in pubspec.
Already raised (skipped to avoid duplication)
- "Oversize PR, AI review not run" — AustinChangLinksys automated bot, informational only (no code concern). qodo reviews are paused for this user, so no bot findings exist.
Verified
NotoSans-Latin.woff2genuinely covers Greek (121), Cyrillic (256), Vietnamese-ext (256) glyphs →el/ru/vi→NotoSansLatinExtmapping is functionally correct despite the name.- SC CJK subset (2038 codepoints) covers 100% of the 709 CJK glyphs in
app_zh.arb— no silent glyph drop for interface strings. LocaleFallbackFontexists in ui_kit v2.28.1 (tag present, sha a1081d4);AppText.resolve()merges the injected resolver's fallback per-locale, and the bare-name-vs-prefixed distinction the resolver relies on matches ui_kit'scopyWith(package:)auto-prefix behavior.- CI runs
flutter pub getwithout--enforce-lockfile, so the v2.28.0→v2.28.1 bump resolves correctly even thoughpubspec.lock(now gitignored) is not in the PR. web/index.htmlload order preserved (usp_init.js → flutter_bootstrap.js); build_web.sh parameters untouched; no CSP meta tag to break;effectiveLocalein app.dart matches theMaterialApp.localeresolution (appSettings.locale ?? systemLocale).main.dartinstalls the resolver beforerunApp(), so AppText fallback is wired before first frame.
What
Reduce the offline web font bundle by subsetting CJK/non-Latin fallback fonts, and rework how fallback fonts are supplied so every locale renders offline with zero CDN requests while online users can still fetch rare glyphs on demand.
CJK fonts: 12.7MB → 2.14MB (84% smaller). All 26 locales render offline, glyph shapes stay correct per language, and interface text triggers 0
fonts.gstatic.comrequests.Full background, root-cause analysis, and verification: #1134
How ("A+" strategy)
fonts:so CanvasKit registers them before first frame — this is what stops CDN probing.fontFallbackBaseUrlon the CDN so online, glyphs outside the subset are fetched on demand.LocaleFallbackFont.resolver; the app owns the single locale-to-family mapping (lib/localization/fallback_font_resolver.dart).Commits
feat(fonts): bundle CJK/non-Latin subset fonts for offline renderingchore(fonts): remove redundant full CJK engine-fallback fonts (~13MB)chore(tools): add CJK subset font regeneration toolingMaintenance
Re-run
tools/font_subset/regenerate.shafter any change that adds a CJK glyph to interface text (ARB strings, language names, hardcoded literals) — otherwise the subset silently misses it. Seetools/font_subset/README.md.Dependencies
Requires ui_kit v2.28.1 (injectable
LocaleFallbackFonthook) — already bumped inpubspec.yaml.Verification
fonts.gstatic.com/s/notosans*requests for interface text (netlog verified)./wwwand verified on-device.Closes #1134