| 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 |
|
| 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 |
|
| 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 |
|
| 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 |
|