iris: drop bundled Noto Sans, match Compose's platform-font fonts

Iris's call: "remove the font for now; just match what compose does."
Removes the six embedded Noto Sans/Noto Sans Mono TTFs (3.6 MB) that
TextData::default used to register ahead of the platform's own fonts;
fontique's system font discovery was already on by default and now
runs unshadowed (Roboto/Roboto Flex on Android, fontconfig on the
desktop). .so -3,748,136 bytes (11,193,608 -> 7,445,472), matching the
estimate. Verified fallback still lands on visible tofu for CJK/emoji
rather than blank, and flagged (not fixed) a fontique Android backend
gap that leaves Monospace unresolved -- see RUST.md's "Platform fonts
(2026-09-07)" and DECISIONS.md/IRIS.md's dated entries.
This commit is contained in:
iris committed 2026-09-07 16:14:34 -04:00
1 parent 690161e5e9
commit 896c93a59a
12 files changed
+193 -312

No files matched your search

+110 -9
View File
@@ -110,6 +110,7 @@ override, reusing the incremental `target/`), and are the raw file sizes
| + `codegen-units = 1` | 15,185,204 | 13,052,736 | -566,008 |
| + `opt-level = "s"` | 13,326,076 | 11,193,608 | -1,859,128 |
| + `opt-level = "z"` (not adopted, see below) | 12,507,276 | 10,374,808 | -818,800 |
| + platform fonts, no bundled Noto (2026-09-07) | 9,577,940 | 7,445,472 | -3,748,136 |
Adopted: `strip = true`, `lto = "fat"`, `codegen-units = 1`, `opt-level = "s"`.
Baseline to final: `libmain.so` **18,546,488 -> 11,193,608 bytes (-39.7%)**,
@@ -181,7 +182,99 @@ rather than a fixed-codepoint one -- a real behaviour change (text in a
language outside the subset would fall back to tofu or a missing glyph) and
out of scope for a size-only pass. Left as a follow-up, flagged for Iris:
subsetting would plausibly save 1-2 MB but changes what scripts render
correctly, which is a product decision.
correctly, which is a product decision. **Superseded 2026-09-07**: Iris
decided to remove the embedding outright rather than subset it -- see
"Platform fonts (2026-09-07)" below.
### Platform fonts (2026-09-07)
Iris's verdict on the open question above: **"remove the font for now;
just match what compose does."** The Compose app takes its body text from
`FontFamily.Default` (platform Roboto on Android) and its code/tool-output
text from `FontFamily.Monospace`, and ships no text font of its own --
only its committed Nerd Fonts icon subset (`app/build-icon-font.sh`) for
~100 fixed glyphs, a different case (a small, known, closed set of
codepoints, unlike arbitrary transcript text). iris had no equivalent icon
font to keep; it draws no icons through a font today, so there was nothing
parallel to preserve.
**What changed**: `TextData::register_bundled_fonts` and the six
`include_bytes!` Noto Sans/Noto Sans Mono constants are gone from
`iris/core/src/primitive/text.rs`, along with the `.ttf`s themselves and
their `OFL.txt` (`iris/core/assets/fonts/`, now removed -- nothing else in
the tree referenced the licence file). `TextData::default` now does
nothing but `FontContext::new()`, which was already discovering the
platform's fonts underneath the bundled ones -- `fontique` 0.11.1's
`CollectionOptions::system_fonts` defaults to `true`, and both platforms
this crate ships on have a real backend behind it: `backend/fontconfig.rs`
on Linux (this VM's desktop has a full Noto install, confirmed with
`fc-match sans-serif`/`fc-match monospace`), `backend/android.rs` on
Android (parses `/system/fonts` and `/system/etc/fonts.xml`, mapping
`SansSerif`/`SystemUi` to `["Roboto Flex", "Roboto", "Noto Sans"]` and
`Monospace` to `["monospace"]` -- see the fallback finding below for why
that last one does not actually resolve on this fontique version). So
removing the bundled registration did not need a replacement call; the
platform path was already live, just shadowed.
**Fallback, and the unknown-glyph state (UI_RULES: design it, don't let
it default to blank)**. `parley`'s shaper sets both an explicit family
list *and* a script/locale-keyed fallback chain per run
(`parley-0.11.1/src/shape/mod.rs`'s `query.set_families`/
`query.set_fallbacks`), so a codepoint the resolved family lacks still
gets a query against fontique's fallback map before giving up. Checked
with a throwaway example (`iris/examples/font_check.rs`, deleted after
use -- not part of the crate) shaping `"🎉🔥▸▾▲你好"` headless on the
desktop at `run-headless.sh`: the emoji and CJK characters drew as
visible **tofu boxes** (the platform's own missing-glyph box, not blank
space), and the chevron marks (U+25B8/25BE/25B4, the ones
`transcript-ui/src/tool.rs`'s `CLOSED_MARK`/`OPEN_MARK`/`UP_MARK` draw)
shaped as real triangles. So the failure mode this crate now depends on
is "the platform's own tofu," which is the correct unknown-glyph state
per UI_RULES, not "nothing drawn." `tool.rs`'s doc comment on those marks
is updated to say this is now a bet on the platform's coverage rather
than a checked fact about a bundled `cmap`.
**One real gap, found on this checkout's emulator, not the desktop**:
`fonts: 208 families found, default=Some("Roboto Flex") mono=None` in the
startup log (`FontDiagnostics`, read via `adb logcat` after installing the
`force-gles` debug build -- the emulator's default Vulkan backend has no
adapter here, a pre-existing, documented condition unrelated to this
change, and aborts with `Could not get adapter!` without that feature).
`mono=None` means fontique's Android backend never resolves the
`Monospace` generic family at all on this system image: reading
`fontique-0.11.1/src/backend/android.rs`, `DEFAULT_GENERIC_FAMILIES`'s
`["monospace"]` is looked up against `name_map` *before* the `fonts.xml`
parse that would register a family literally named `"monospace"` runs --
so even though this AVD's `/system/etc/fonts.xml` does declare
`<family name="monospace"><font ...>DroidSansMono.ttf</font></family>`,
fontique's own ordering means that declaration is registered too late to
be found by the generic-family lookup, on every Android device this
fontique version runs on, not just this AVD. The visible effect is not
blank text -- `Family::Monospace`'s explicit-family list comes up empty,
but the script-based fallback chain (independent of the generic-family
list) still resolves a real font, the same one `SansSerif` gets -- so
code blocks and the tool-card chevrons render, just without a genuinely
monospaced face. Compose does not have this gap: `FontFamily.Monospace`
resolves through Android's own `Typeface.MONOSPACE` constant, a different
and unconditionally-populated path that fontique does not use. Left as a
follow-up rather than fixed here, since a fix means either patching
around fontique's Android backend or pinning `Family::Named("Droid Sans
Mono")` (fragile: an OEM-specific font name, not guaranteed across real
devices) -- out of the scope Iris gave this pass ("remove the font,
match Compose"), and a real product-visible difference worth her knowing
about rather than silently living with.
**Verified**: `cargo test -p transcript-fixture` (6 tests, all headless
layers) and `cargo clippy -p iris-core --all-targets` both clean;
`./run-headless.sh phone --phone --shot` (the bench-fixture transcript,
bold/italic/monospace code fences all shaping correctly) and
`./run-headless.sh tabs` (the desktop example with several distinct text
styles, per this task's "negative case" check) both look right by eye;
the emulator's own bench run (`run-bench.sh`, `force-gles` debug build)
completed a full fling/stream/type/keyboard cycle with text visibly
drawn throughout and no crash. `.so` size: see the APK size table's final
row, **11,193,608 -> 7,445,472 bytes**, a 3,748,136-byte drop matching
the 3.6 MB estimate almost exactly.
**naga/wgpu backend features: investigated, not trimmed, because the
trim would not change the binary.** `iris/core/Cargo.toml` and
@@ -279,14 +372,22 @@ closes it.
recent log; a debug button copies it; Dev Updater reads it), write
the decision in docs/DECISIONS.md, build it.
- [x] APK size: release profile tuned (`42af780`), -35% APK, -40% .so;
see "APK size (2026-09-07)". **Open question for Iris**: 3.6 MB of the
remaining 11.2 MB .so is six embedded Noto Sans TTFs
(`iris/core/src/primitive/text.rs`). Loading the platform's own fonts
instead (fontique's system collection: Roboto/Noto on Android, the
desktop's own on Linux) removes them and makes text match what the
rest of her phone shows (UI_RULES "show what the reader already sees
elsewhere"), at the cost of the app no longer looking identical on
every device. Not done without her verdict.
see "APK size (2026-09-07)". **Iris's verdict, 2026-09-07: "remove the
font for now; just match what compose does."** Done same day -- the six
bundled Noto Sans TTFs are gone, text now loads from
`fontique`'s platform collection (`FontContext::new()`'s default
`CollectionOptions::system_fonts`), and `.so` dropped by 3,748,136 bytes
(11,193,608 -> 7,445,472), matching the 3.6 MB estimate almost exactly.
See "APK size" table's final row and "Platform fonts (2026-09-07)"
below for the fallback behaviour and one real gap it surfaced: this
fontique version's Android backend never resolves the `Monospace`
generic family at all (`mono=None` in the startup diagnostic, measured
on this checkout's emulator) -- code/tool-card text still renders (the
script fallback chain still lands on a real face, never blank), just
not in a genuinely monospaced one. Compose does not have this gap; it
resolves `FontFamily.Monospace` through Android's own Typeface
constant rather than through fontique. Flagged as a follow-up, not
fixed here -- out of the scope Iris gave.
- [ ] Scroll clamped at both ends, and Compose's impulse velocity
estimator with min/max fling velocity (docs/IRIS_TODO.md, 2026-09-07
later). After the culling fix lands (same file).