Skip to main content

max / alloy

12.9 KB · 225 lines History Blame Raw
1 # Fonts: what is shipped, what is covered, what catches the gaps
2
3 Companion to [STACK.md]STACK.md, which holds the reasoning behind each pick, and to
4 [ICONOGRAPHY.md]ICONOGRAPHY.md, which holds the icon tiers. This file holds the
5 measurement: which codepoints Alloy's own surfaces put on a screen, which of them the
6 shipped fonts cover, and what the fallback chain does with the rest.
7
8 Measured against the built image, read out of `podman image mount localhost/alloy:local`
9 rather than out of the package list. A package list says what was requested; the mounted
10 rootfs says what is there.
11
12 The image carries `Quasi Mono` and `Quasi Body`, cut in the build by `quasi-type` from
13 Atkinson Hyperlegible Mono and Atkinson Hyperlegible Next (STACK.md#fonts, wiki
14 `typography-standard`). Every number below that names a font is read from the `cmap` of
15 those two built faces. The widget ceiling is a scan of `alloy_tui`, `ratatui-*` and
16 `makeover` and moves with the lock, so treat the 723 as a last-measured ceiling rather
17 than today's.
18
19 Reading the built faces rather than a mounted rootfs is the stronger reading for this
20 question and the weaker one for "what is installed": a cut face is fully determined by the
21 pipeline's own pins, so the `cmap` of the file the build produces is the `cmap` the image
22 gets. What still wants a booted image is the fallback-chain table and the `fc-match`
23 answers, and both say where they have not been re-taken.
24
25 ## Method, so the numbers can be re-derived
26
27 Two enumerations and a set difference.
28
29 - **What a font covers** is its `cmap` table: the Unicode subtable (format 4 or 12), with
30 every codepoint whose glyph id is non-zero. Not the `name` table's language claims,
31 which count languages and not glyphs.
32 - **What Alloy emits** is every non-ASCII codepoint in its own source and templates, with
33 Rust comments and `#[cfg(test)]` modules cut out first. A comment is never rendered and
34 a test fixture is never shipped, and both are full of characters that would otherwise
35 read as coverage requirements. Sources: `crates/`, `etc/`, `usr/`, `templates/`,
36 `schemas/`, `tools/`, `scripts/`.
37 - **What the widget stack can emit** is the same scan over `alloy_tui 5.0.0`,
38 `ratatui-core 0.1.2`, `ratatui-widgets 0.3.2` and `makeover 2.4.1`, the versions in
39 `Cargo.lock`. This is a ceiling and not a usage count: it includes symbol sets for
40 widgets Alloy does not draw.
41
42 ## What Alloy's own surfaces emit
43
44 Eighteen distinct non-ASCII codepoints, in total, across the console, the installer TUI
45 and every shipped config template.
46
47 | Codepoint | Glyph | Where |
48 |---|---|---|
49 | U+00B7 | `·` | helix config, whitespace render |
50 | U+00BB | `»` | starship prompt character |
51 | U+2014 | `` | 97 sites, string literals across the console |
52 | U+2022 | `` | installer, list bullets |
53 | U+2026 | `` | truncation, six sites |
54 | U+2191 U+2192 U+2193 U+2195 | `↑ → ↓ ↕` | starship git-status, helix whitespace |
55 | U+23CE | `` | helix, newline render |
56 | U+2423 | `` | helix, space render |
57 | U+2502 | `` | helix, indent guide |
58 | U+2588 U+258F U+2591 | `█ ▏ ░` | installer progress bar, helix indent guide |
59 | U+25B8 U+25C2 | `▸ ◂` | starship segment separators |
60 | U+2718 | `` | starship, error state |
61
62 **Twenty since the backdrop landed.** `crates/backdrop` adds U+2571 and U+2572, the two
63 box-drawing diagonals, which are 10 PRINT's whole vocabulary. They are not in the table
64 above because they are not authored: the eighteen are drawn glyph by glyph in
65 `quasi-type`'s manifest, and the diagonals arrive from its `[[generated]]` box-drawing
66 block, which cuts the whole of U+2500-U+257F cell-exact so it tiles. That is also why
67 they are the right two glyphs for the job -- a maze made of diagonals only reads as a
68 maze if they meet at the cell corners, which is a property of the generation and not of
69 anyone's taste.
70
71 The consequence for the coverage floor: `ALLOY_SURFACE` in `src/assert.rs` does not name
72 them, so a face that somehow shipped the eighteen and no box drawing would pass the
73 assertion and draw tofu on every desktop. Nothing can currently produce such a face --
74 the generated blocks are unconditional -- so this is recorded rather than fixed. The day
75 a base is added that supplies box drawing of its own and the generation is made
76 conditional, the floor is where it has to be caught.
77
78 Latin-1 punctuation, arrows, four block-element characters and one dingbat. No Private
79 Use Area: **Alloy itself emits no Nerd Font icons**, and there is no
80 Private Use Area in the image to emit. The PUA reasoning in
81 `etc/skel/.config/fontconfig/fonts.conf` and in STACK.md is entirely about third-party
82 consumers (starship, yazi, bottom, helix), which is worth knowing before anyone treats
83 the `Mono` family constraint as an Alloy-internal requirement.
84
85 ## What the widget stack can reach
86
87 723 distinct non-ASCII codepoints, once tests are excluded. As ranges:
88
89 - **U+2022**, the bullet `Canvas` uses as its default marker.
90 - **U+2190-U+2193**, the four arrows, for scrollbar ends.
91 - **U+2500-U+2570** (113) box drawing: `Block` ships plain, thick, double, rounded and
92 dashed at three densities, so nearly the whole block is reachable.
93 - **U+2574-U+259F** (44) the half-lines and the block elements, for gauges, bars and the
94 bevel `alloy_tui` draws.
95 - **U+25B2 U+25B6 U+25B8 U+25BA U+25BC U+25BE U+25C4**, triangles, for selection and
96 scroll markers.
97 - **U+2800-U+28FF** (256) the full braille range, which `Canvas` uses as a sub-cell pixel
98 grid.
99 - **U+1CD00-U+1CDE5** (230) and **U+1CEA0-U+1CEAB**, Symbols for Legacy Computing:
100 ratatui's sextant and octant marker sets.
101 - **U+1FB00-U+1FB3B** (60), **U+1FB82**, **U+1FB85**, **U+1FBE6-U+1FBE7**, Symbols for
102 Legacy Computing Supplement, same purpose.
103
104 The last three ranges are the interesting ones: they are recent Unicode, and a font that
105 covers box drawing convincingly can still have nothing there.
106
107 ## Coverage against the shipped fonts
108
109 | Font | Codepoints | Missing from Alloy's set | Missing from the widget ceiling |
110 |---|---|---|---|
111 | Quasi Mono (shipped) | 535 | **0** | 562 |
112 | Quasi Body (shipped) | 378 | 4 | 719 |
113 | Atkinson Hyperlegible Mono (the base, not shipped) | 359 | 13 | 730 |
114 | Departure Mono NF Mono (not in the image) | 11,468 | 5 | 557 |
115
116 **The monospace answer is still that there is no gap in what Alloy emits**, and it is now
117 a built guarantee rather than a lucky one: `quasi-type` asserts the whole of Alloy's
118 eighteen plus the two sort carets against the `cmap` of every face it cuts, so a face that
119 lost one fails the font build instead of the image. The base underneath covers 5 of the
120 18 by itself; the other 13 are drawn.
121
122 **562 of the widget ceiling is missing, and it is exactly braille and legacy computing.**
123 256 braille, 230 at U+1CD00-U+1CDE5, 12 at U+1CEA0, 64 in the supplement. Those are
124 ratatui's `Canvas` and sparkline marker sets, and there is no `Canvas` and no `Sparkline`
125 anywhere in `alloy/crates/`, so this is a ceiling and not a use.
126
127 **Five more were missing when this was first re-measured, and they were closed rather
128 than recorded.** `` and `▶ ◀ ► ▾`: the set drew three of the four scrollbar arrows and
129 two of the six triangles, and the old base drew the rest so nothing showed. They went
130 into the house glyph set (v2) the same day, because one arrow in a row of four arriving
131 from a fallback face — at another weight, on another baseline — is visible in a way a
132 missing sextant is not.
133
134 **The rule that replaces "IosevkaTerm covers everything":** what a cell-grid surface can
135 emit is a set the house glyph set has to carry, and adding to it is an edit to
136 `quasi-type`'s manifest plus a rebuild. Braille is the live example — the day a TUI draws
137 a sparkline, the answer is a generated recipe in the set, not a fallback face on its own
138 baseline.
139
140 **Quasi Body's four gaps are not gaps**, because nothing routes box drawing to a sans
141 font — they are `│ █ ▏ ░`, the four codepoints that exist to tile, and the slot
142 deliberately does not carry them. Quasi Body serves the `sans-serif` alias and
143 `gtk-font-name`, which is proportional UI text in GTK dialogs and the portal file
144 chooser. The rule worth writing down: **cell-grid surfaces get the mono face and the cell
145 tier with it; proportional surfaces get the body face and never emit one.** A future
146 surface that breaks that rule is where this becomes a real gap. The pipeline enforces the
147 same split from its own side: the body slot's coverage floor is Alloy's eighteen minus
148 those four.
149
150 **Departure's gaps only matter outside the running system.** It is not in the image
151 (STACK.md), so it can only appear in a mockup, the README,
152 the social card or a splash plate. Of the eighteen codepoints Alloy emits it lacks five:
153 `` (U+23CE), `` (U+2423), `` (U+25B8), `` (U+25C2), `` (U+2718). Four of those five
154 are starship and helix prompt furniture, which is exactly what a terminal mockup renders,
155 so a mockup set in Departure loses its prompt separators silently. It also has no
156 braille, no sextants and no octants, so no Departure render can show a `Canvas` or a
157 sparkline. Use the house mono for any mockup of a running surface, and keep Departure for
158 headers and brand plates, which is what STACK.md already asks for on other grounds.
159
160 ## The fallback chain, in order, and which links exist
161
162 From `etc/skel/.config/fontconfig/fonts.conf`. Each `<prefer>` list is tried in order and
163 fontconfig skips a family the system does not have.
164
165 | Alias | Order | In the image? |
166 |---|---|---|
167 | `monospace` | Quasi Mono | yes, one variable face under `/usr/share/fonts/quasi` |
168 | `sans-serif` | Quasi Body | yes, the same directory |
169 | | Cantarell | yes, `abattis-cantarell-vf-fonts` |
170 | `serif` | Liberation Serif | yes |
171 | `emoji` | (deliberately absent) | no emoji font is installed |
172
173 The column answers for the client profile. The server profile installs neither house face nor fontconfig, so nothing on it reads this file: a TUI reached over ssh is drawn by the client's font stack, and the profile prune takes `etc/skel/.config/fontconfig` with the rest of the skeleton.
174
175 **Both house faces are variable, and fontconfig enumerates their named instances**, so
176 `fc-match monospace` answers `Quasi Mono Regular` rather than the file's own default
177 instance, which is ExtraLight, because a cut keeps its base's default. Measured by
178 installing the built faces and querying fontconfig directly rather than on a booted
179 image; the build asserts the same thing so the trap cannot reach one.
180
181 **Every family the aliases name is installed, and none may name one that is not.** A name
182 that is absent costs nothing at runtime, because `<prefer>` skips what is missing, and it
183 is a reading hazard: the list looks like a depth of fallback that is not there.
184
185 **The `monospace` chain has one link.** If the house face ever failed to install,
186 resolution does not reach a second Alloy pick. It drops straight into Fedora's generic
187 rules (`45-latin.conf`, `60-latin.conf`) and lands on whatever those order first, which
188 is the layer described below.
189
190 **Inter is deliberately out of `sans-serif`.** It is not in the image, and a face named
191 at position 2 of the alias means anyone who installs it for an unrelated app silently
192 repoints Alloy's whole UI font. Preferring Inter is supported and is what STACK.md
193 describes: an edit to the alias and to `gtk-font-name`, which is a choice rather than a
194 side effect of having the font on disk.
195
196 **What actually catches a gap is the layer below this file**, and it is worth naming
197 because the chain above suggests otherwise:
198
199 1. `/etc/fonts/conf.d/45-latin.conf` and `60-latin.conf`, Fedora's generic-family
200 ordering, which is what a request resolves through once the `<prefer>` list is
201 exhausted.
202 2. `59-adwaita-mono-fonts.conf`, `59-liberation-*.conf`, the URW base-35 set: the real
203 substitutes present on disk for mono, sans and serif.
204 3. The Noto set for everything non-Latin: `NotoSans[wght].ttf` plus 24 per-script
205 variable faces, `NotoSansCJK-VF.ttc` and `NotoSansMonoCJK-VF.ttc`. This is what stops
206 an arbitrary web page from rendering as rows of missing glyphs.
207 4. Nothing, for emoji. That is the decided position, not a gap.
208
209 ## Re-running this
210
211 The scripts are not in the repo: the measurement is a one-off against a built image, and
212 a checked-in tool that nobody runs drifts into a lie. Re-derive it by mounting the image
213 and reading `cmap` tables directly.
214
215 Half of it needs no image. The faces are cut rather than installed, so
216 `cargo run -- build quasi-mono` in `quasi-type` produces the exact file the image gets,
217 and `cargo run -- proof quasi-mono` rasterises it to a PNG — which is the check that
218 catches a wrong shape, since a `cmap` count cannot. What still needs the image is
219 everything about what else is on disk: the Noto set, the substitutes, and the `fc-match`
220 answers under `HOME=/etc/skel`.
221
222 The numbers worth re-checking after a font change are the size of Alloy's own emitted set
223 (18), whether "missing from Alloy's set" is still 0 for the mono face, and whether the
224 widget-ceiling miss is still only braille and legacy computing.
225