diff --git a/docs/DECISIONS.md b/docs/DECISIONS.md index 6eb087a..cd5dfb0 100644 --- a/docs/DECISIONS.md +++ b/docs/DECISIONS.md @@ -5,6 +5,40 @@ they can be judged and reversed later. Detail lives in RUST.md (and IRIS.md for iris API changes); this file is only the summary. Newest first. Items marked **DEFERRED** are ones the agent chose not to decide alone. +## 2026-09-07 (platform fonts, not bundled ones) + +- **Iris's own decision, carried out as directed**: removed the 3.6 MB of + bundled Noto Sans/Noto Sans Mono TTFs from `iris-core` and load text + from the platform's own font collection instead (`fontique`'s system + discovery, already on by default). Matches what the Compose app does -- + it takes body text from `FontFamily.Default` and code text from + `FontFamily.Monospace`, both platform-resolved, and ships no text font + of its own. Rejected alternative (the one this pass had left open + 2026-09-06): subsetting the bundled Noto Sans to Latin/common + punctuation instead of removing it outright, which would have kept + identical rendering across devices for a smaller (not zero) size cost; + Iris chose to match Compose instead. +- `.so` **-3,748,136 bytes** (11,193,608 -> 7,445,472), matching the + original 3.6 MB estimate. Fallback still lands on the platform's own + tofu for a codepoint no resolved face has (checked with CJK + emoji on + desktop) rather than blank space, so the UI_RULES unknown-glyph rule + still holds. +- **Flagging for Iris, not blocking on it**: this fontique version's + Android backend never resolves the `Monospace` generic family at all + (confirmed on this checkout's emulator, `mono=None` in the startup + diagnostic) -- a pre-existing ordering bug in fontique's own + `fonts.xml` parsing, not something this change introduced, but this + change is what stopped masking it (the bundled mono font used to be + registered ahead of the broken platform lookup, so it always won). + Effect: code blocks and the tool-card chevron marks fall through to + the same face as body text on Android instead of a genuinely + monospaced one -- still visible, not blank, just not monospaced. + Compose does not share this gap; it resolves `FontFamily.Monospace` + through Android's own `Typeface.MONOSPACE`, not through fontique. + docs/RUST.md's "Platform fonts (2026-09-07)" has the full account and + why a fix (an OEM-specific named-family pin, or patching around + fontique) is left as a follow-up rather than done in this pass. + ## 2026-09-06 (how a tool call looks, P1b) - **A card that never got a result says "no result", in yellow, and it is diff --git a/docs/IRIS.md b/docs/IRIS.md index 2f39bfe..f3eab5d 100644 --- a/docs/IRIS.md +++ b/docs/IRIS.md @@ -8,6 +8,27 @@ capability that moved. Small and trivial changes do not go here. An entry gives the date, what changed, why, and a short before/after where it helps judge the change without the session that made it. Newest first. +## 2026-09-07: `TextData` no longer bundles a font + +Iris's call: "remove the font for now; just match what compose does." +`TextData::default()` used to embed six Noto Sans/Noto Sans Mono `.ttf`s +(3.6 MB, `include_bytes!`) and register them ahead of the platform's own +fonts in the `SansSerif`/`Monospace` fallback lists. That registration is +gone; `TextData::default()`'s signature is unchanged, but what it produces +now depends entirely on `fontique`'s platform discovery (already on by +default, previously shadowed) -- Roboto/Roboto Flex on Android, whatever +the desktop's fontconfig resolves on Linux. No caller-visible type or +method changed, but every consumer of `iris-core` text now renders with +whatever the host platform's fonts are, not a fixed bundled face -- worth +knowing if you were relying on pixel-identical text across devices. +`.so` shrank by 3.75 MB. One real gap surfaced by the switch: this +fontique version's Android backend never resolves the `Monospace` +generic family (a fontique ordering bug, not new in this change), so +`Family::Monospace` text falls through to the same face as +`SansSerif` on Android rather than a true monospaced one -- still +visible, not blank, just not monospaced. docs/RUST.md's "Platform fonts +(2026-09-07)" has the full account. + ## 2026-09-07: a headless harness, replayed touch, and physical-pixel desktop layout Layer 1 and 2 of docs/RUST.md's "Three test layers". diff --git a/docs/RUST.md b/docs/RUST.md index 652438f..db6e13c 100644 --- a/docs/RUST.md +++ b/docs/RUST.md @@ -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 +`DroidSansMono.ttf`, +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). diff --git a/iris/core/assets/fonts/NotoSans-Bold.ttf b/iris/core/assets/fonts/NotoSans-Bold.ttf deleted file mode 100644 index aae7546..0000000 Binary files a/iris/core/assets/fonts/NotoSans-Bold.ttf and /dev/null differ diff --git a/iris/core/assets/fonts/NotoSans-BoldItalic.ttf b/iris/core/assets/fonts/NotoSans-BoldItalic.ttf deleted file mode 100644 index 6f685b2..0000000 Binary files a/iris/core/assets/fonts/NotoSans-BoldItalic.ttf and /dev/null differ diff --git a/iris/core/assets/fonts/NotoSans-Italic.ttf b/iris/core/assets/fonts/NotoSans-Italic.ttf deleted file mode 100644 index 7f53133..0000000 Binary files a/iris/core/assets/fonts/NotoSans-Italic.ttf and /dev/null differ diff --git a/iris/core/assets/fonts/NotoSans-Regular.ttf b/iris/core/assets/fonts/NotoSans-Regular.ttf deleted file mode 100644 index f27f4ff..0000000 Binary files a/iris/core/assets/fonts/NotoSans-Regular.ttf and /dev/null differ diff --git a/iris/core/assets/fonts/NotoSansMono-Bold.ttf b/iris/core/assets/fonts/NotoSansMono-Bold.ttf deleted file mode 100644 index 9218a79..0000000 Binary files a/iris/core/assets/fonts/NotoSansMono-Bold.ttf and /dev/null differ diff --git a/iris/core/assets/fonts/NotoSansMono-Regular.ttf b/iris/core/assets/fonts/NotoSansMono-Regular.ttf deleted file mode 100644 index 159ca4b..0000000 Binary files a/iris/core/assets/fonts/NotoSansMono-Regular.ttf and /dev/null differ diff --git a/iris/core/assets/fonts/OFL.txt b/iris/core/assets/fonts/OFL.txt deleted file mode 100644 index 261eeb9..0000000 --- a/iris/core/assets/fonts/OFL.txt +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/iris/core/src/primitive/text.rs b/iris/core/src/primitive/text.rs index 62dbf69..4810c19 100644 --- a/iris/core/src/primitive/text.rs +++ b/iris/core/src/primitive/text.rs @@ -2,34 +2,14 @@ use crate::{Align, GlyphAtlas, GlyphKey, PlacedGlyph, RegionAlign, Textures, UiC use parley::{ Alignment, AlignmentOptions, FontContext, FontFamily, FontFamilyName, FontStyle, FontWeight, GenericFamily, Layout, LayoutContext, LineHeight, PositionedLayoutItem, StyleProperty, - fontique::{Blob, FamilyId}, }; use std::ops::Range; -use std::sync::Arc; use swash::{ FontRef, scale::{Render, ScaleContext, Source, StrikeWith}, zeno::{Format, Vector}, }; -/// Bundled fonts, registered over the system collection rather than relied -/// on alone -- see `TextData::register_bundled_fonts`'s doc comment for -/// why. Static weight/style cuts, not a variable font: parley/fontique -/// resolve a variable font's weight axis by picking normalized coordinates -/// on whatever single face registers for the family, and a phone whose -/// system "Roboto" is actually the variable "Roboto Flex" is exactly the -/// device class this sidesteps, rather than depends on working correctly. -/// Noto Sans, OFL-licensed (`assets/fonts/OFL.txt`), chosen for coverage -/// breadth (a transcript's content is not known in advance) over a -/// smaller-footprint alternative -- see the doc comment for the size this -/// added. -const NOTO_SANS_REGULAR: &[u8] = include_bytes!("../../assets/fonts/NotoSans-Regular.ttf"); -const NOTO_SANS_BOLD: &[u8] = include_bytes!("../../assets/fonts/NotoSans-Bold.ttf"); -const NOTO_SANS_ITALIC: &[u8] = include_bytes!("../../assets/fonts/NotoSans-Italic.ttf"); -const NOTO_SANS_BOLD_ITALIC: &[u8] = include_bytes!("../../assets/fonts/NotoSans-BoldItalic.ttf"); -const NOTO_SANS_MONO_REGULAR: &[u8] = include_bytes!("../../assets/fonts/NotoSansMono-Regular.ttf"); -const NOTO_SANS_MONO_BOLD: &[u8] = include_bytes!("../../assets/fonts/NotoSansMono-Bold.ttf"); - /// What starting up found about text rendering, for the on-screen /// Diagnostics page and the one startup log line (RUST.md's P0 box, "log /// once at startup ... the number of font families found, the default @@ -80,91 +60,31 @@ pub struct TextData { } impl Default for TextData { + /// Text comes entirely from the platform's own font collection -- + /// `FontContext::new()` builds a `fontique::Collection` with + /// `CollectionOptions::system_fonts` on by default, which is real + /// discovery on both targets this crate ships on: Android's backend + /// parses `/system/fonts` and `/system/etc/fonts.xml` and maps + /// `SansSerif`/`SystemUi` to `["Roboto Flex", "Roboto", "Noto Sans"]` + /// and `Monospace` to the platform's `"monospace"` alias; the desktop + /// build's backend is fontconfig. No font is bundled or registered + /// here -- see DECISIONS.md's 2026-09-07 entry for why (matching what + /// the Compose app does: it takes body/monospace text from + /// `FontFamily.Default`/`FontFamily.Monospace`, i.e. Android's Roboto + /// and its platform monospace face, and ships no text font of its own, + /// only its committed Nerd Fonts icon subset for fixed glyphs). fn default() -> Self { - let mut data = Self { + Self { font_cx: FontContext::new(), layout_cx: LayoutContext::new(), scale_cx: ScaleContext::new(), atlas: GlyphAtlas::default(), density: 1.0, - }; - data.register_bundled_fonts(); - data + } } } impl TextData { - /// Registers Noto Sans (regular/bold/italic/bold-italic) and Noto Sans - /// Mono (regular/bold) as static faces, and puts them **first** in the - /// `SansSerif`/`Monospace` generic-family fallback lists -- ahead of, - /// not instead of, whatever the platform already found, so a script - /// Noto Sans lacks (CJK, emoji, ...) still falls through to the system - /// font the same as before this existed. - /// - /// Exists because text rendering must not depend on the platform's own - /// font enumeration succeeding or resolving weight/style the way this - /// crate assumes: RUST.md's P0 box found bold spans on a real phone - /// rendering as blank gaps of the correct advance width (the glyph - /// simply wasn't rasterised -- `TextData::place`'s `None` arm), while - /// the emulator's system fonts happened to resolve every style. A - /// bundled, static-per-style family removes fontique's Android font - /// scan (`fontique::backend::android::SystemFonts::new`, which parses - /// `/system/fonts` and `/system/etc/fonts.xml`) from the path a glyph - /// has to survive to reach the screen at all. - /// - /// Cost: six static `.ttf`s, ~3.6 MB uncompressed - /// (`iris/core/assets/fonts/`), landing in the APK compressed -- - /// `build-apk.sh`'s own output is what says the delivered number, not - /// this comment. - fn register_bundled_fonts(&mut self) { - fn register(cx: &mut FontContext, bytes: &'static [u8]) -> Option { - let blob = Blob::new(Arc::new(bytes)); - cx.collection - .register_fonts(blob, None) - .into_iter() - .map(|(id, _)| id) - .next() - } - let sans_id = register(&mut self.font_cx, NOTO_SANS_REGULAR); - register(&mut self.font_cx, NOTO_SANS_BOLD); - register(&mut self.font_cx, NOTO_SANS_ITALIC); - register(&mut self.font_cx, NOTO_SANS_BOLD_ITALIC); - let mono_id = register(&mut self.font_cx, NOTO_SANS_MONO_REGULAR); - register(&mut self.font_cx, NOTO_SANS_MONO_BOLD); - - if let Some(sans_id) = sans_id { - let existing: Vec<_> = self - .font_cx - .collection - .generic_families(GenericFamily::SansSerif) - .collect(); - self.font_cx.collection.set_generic_families( - GenericFamily::SansSerif, - std::iter::once(sans_id).chain(existing), - ); - let existing: Vec<_> = self - .font_cx - .collection - .generic_families(GenericFamily::SystemUi) - .collect(); - self.font_cx.collection.set_generic_families( - GenericFamily::SystemUi, - std::iter::once(sans_id).chain(existing), - ); - } - if let Some(mono_id) = mono_id { - let existing: Vec<_> = self - .font_cx - .collection - .generic_families(GenericFamily::Monospace) - .collect(); - self.font_cx.collection.set_generic_families( - GenericFamily::Monospace, - std::iter::once(mono_id).chain(existing), - ); - } - } - /// Builds the startup report -- see `FontDiagnostics`. Queries the /// collection directly (`fontique::Query`) rather than shaping a real /// string, since all that's needed is which family each axis lands on. diff --git a/iris/transcript-ui/src/tool.rs b/iris/transcript-ui/src/tool.rs index 5c5d58a..550df77 100644 --- a/iris/transcript-ui/src/tool.rs +++ b/iris/transcript-ui/src/tool.rs @@ -110,13 +110,19 @@ const _: () = assert!(OUTPUT_LINES > 0 && OUTPUT_BYTES > 0); /// The mark that says a card opens, always drawn from the **monospace** /// face. /// -/// Not a style choice: `NotoSans-Regular.ttf`, which every other string -/// here is set in, has no glyph at U+25B8/U+25BE/U+25B4 at all, while -/// `NotoSansMono-Regular.ttf` does -- read out of both bundled `cmap`s on -/// 2026-09-06. A missing glyph is the failure nobody who wrote the code -/// ever sees, so the face that has the glyph is named at the one place the -/// character is written. IRIS_TODO's "a drawn chevron" has the real fix, -/// which needs a line primitive iris does not have. +/// Not a style choice, historically: with the bundled Noto Sans this crate +/// used to embed, the sans face every other string here is set in had no +/// glyph at U+25B8/U+25BE/U+25B4 at all, while the bundled monospace face +/// did. As of 2026-09-07 iris takes both faces from the platform's own +/// collection instead (DECISIONS.md's 2026-09-07 entry, matching what +/// Compose ships), so this is no longer a checked fact about a specific +/// font's `cmap` -- it is a bet that whatever the platform resolves for +/// `Family::Monospace` covers these three codepoints, same as it was +/// before. If a platform's monospace face turns out not to, the fallback +/// chain still applies (see `TextData::default`'s doc comment) and the +/// glyph should still land, just not necessarily monospaced. IRIS_TODO's +/// "a drawn chevron" has the real fix, which needs a line primitive iris +/// does not have. const CLOSED_MARK: &str = "\u{25b8}"; const OPEN_MARK: &str = "\u{25be}"; const UP_MARK: &str = "\u{25b4}";