# Fonts: what is shipped, what is covered, what catches the gaps Companion to [STACK.md](STACK.md), which holds the reasoning behind each pick, and to [ICONOGRAPHY.md](ICONOGRAPHY.md), which holds the icon tiers. This file holds the measurement: which codepoints Alloy's own surfaces put on a screen, which of them the shipped fonts cover, and what the fallback chain does with the rest. Measured against the built image, read out of `podman image mount localhost/alloy:local` rather than out of the package list. A package list says what was requested; the mounted rootfs says what is there. The image carries `Quasi Mono` and `Quasi Body`, cut in the build by `quasi-type` from Atkinson Hyperlegible Mono and Atkinson Hyperlegible Next (STACK.md#fonts, wiki `typography-standard`). Every number below that names a font is read from the `cmap` of those two built faces. The widget ceiling is a scan of `alloy_tui`, `ratatui-*` and `makeover` and moves with the lock, so treat the 723 as a last-measured ceiling rather than today's. Reading the built faces rather than a mounted rootfs is the stronger reading for this question and the weaker one for "what is installed": a cut face is fully determined by the pipeline's own pins, so the `cmap` of the file the build produces is the `cmap` the image gets. What still wants a booted image is the fallback-chain table and the `fc-match` answers, and both say where they have not been re-taken. ## Method, so the numbers can be re-derived Two enumerations and a set difference. - **What a font covers** is its `cmap` table: the Unicode subtable (format 4 or 12), with every codepoint whose glyph id is non-zero. Not the `name` table's language claims, which count languages and not glyphs. - **What Alloy emits** is every non-ASCII codepoint in its own source and templates, with Rust comments and `#[cfg(test)]` modules cut out first. A comment is never rendered and a test fixture is never shipped, and both are full of characters that would otherwise read as coverage requirements. Sources: `crates/`, `etc/`, `usr/`, `templates/`, `schemas/`, `tools/`, `scripts/`. - **What the widget stack can emit** is the same scan over `alloy_tui 5.0.0`, `ratatui-core 0.1.2`, `ratatui-widgets 0.3.2` and `makeover 2.4.1`, the versions in `Cargo.lock`. This is a ceiling and not a usage count: it includes symbol sets for widgets Alloy does not draw. ## What Alloy's own surfaces emit Eighteen distinct non-ASCII codepoints, in total, across the console, the installer TUI and every shipped config template. | Codepoint | Glyph | Where | |---|---|---| | U+00B7 | `·` | helix config, whitespace render | | U+00BB | `»` | starship prompt character | | U+2014 | `—` | 97 sites, string literals across the console | | U+2022 | `•` | installer, list bullets | | U+2026 | `…` | truncation, six sites | | U+2191 U+2192 U+2193 U+2195 | `↑ → ↓ ↕` | starship git-status, helix whitespace | | U+23CE | `⏎` | helix, newline render | | U+2423 | `␣` | helix, space render | | U+2502 | `│` | helix, indent guide | | U+2588 U+258F U+2591 | `█ ▏ ░` | installer progress bar, helix indent guide | | U+25B8 U+25C2 | `▸ ◂` | starship segment separators | | U+2718 | `✘` | starship, error state | **Twenty since the backdrop landed.** `crates/backdrop` adds U+2571 and U+2572, the two box-drawing diagonals, which are 10 PRINT's whole vocabulary. They are not in the table above because they are not authored: the eighteen are drawn glyph by glyph in `quasi-type`'s manifest, and the diagonals arrive from its `[[generated]]` box-drawing block, which cuts the whole of U+2500-U+257F cell-exact so it tiles. That is also why they are the right two glyphs for the job -- a maze made of diagonals only reads as a maze if they meet at the cell corners, which is a property of the generation and not of anyone's taste. The consequence for the coverage floor: `ALLOY_SURFACE` in `src/assert.rs` does not name them, so a face that somehow shipped the eighteen and no box drawing would pass the assertion and draw tofu on every desktop. Nothing can currently produce such a face -- the generated blocks are unconditional -- so this is recorded rather than fixed. The day a base is added that supplies box drawing of its own and the generation is made conditional, the floor is where it has to be caught. Latin-1 punctuation, arrows, four block-element characters and one dingbat. No Private Use Area: **Alloy itself emits no Nerd Font icons**, and there is no Private Use Area in the image to emit. The PUA reasoning in `etc/skel/.config/fontconfig/fonts.conf` and in STACK.md is entirely about third-party consumers (starship, yazi, bottom, helix), which is worth knowing before anyone treats the `Mono` family constraint as an Alloy-internal requirement. ## What the widget stack can reach 723 distinct non-ASCII codepoints, once tests are excluded. As ranges: - **U+2022**, the bullet `Canvas` uses as its default marker. - **U+2190-U+2193**, the four arrows, for scrollbar ends. - **U+2500-U+2570** (113) box drawing: `Block` ships plain, thick, double, rounded and dashed at three densities, so nearly the whole block is reachable. - **U+2574-U+259F** (44) the half-lines and the block elements, for gauges, bars and the bevel `alloy_tui` draws. - **U+25B2 U+25B6 U+25B8 U+25BA U+25BC U+25BE U+25C4**, triangles, for selection and scroll markers. - **U+2800-U+28FF** (256) the full braille range, which `Canvas` uses as a sub-cell pixel grid. - **U+1CD00-U+1CDE5** (230) and **U+1CEA0-U+1CEAB**, Symbols for Legacy Computing: ratatui's sextant and octant marker sets. - **U+1FB00-U+1FB3B** (60), **U+1FB82**, **U+1FB85**, **U+1FBE6-U+1FBE7**, Symbols for Legacy Computing Supplement, same purpose. The last three ranges are the interesting ones: they are recent Unicode, and a font that covers box drawing convincingly can still have nothing there. ## Coverage against the shipped fonts | Font | Codepoints | Missing from Alloy's set | Missing from the widget ceiling | |---|---|---|---| | Quasi Mono (shipped) | 535 | **0** | 562 | | Quasi Body (shipped) | 378 | 4 | 719 | | Atkinson Hyperlegible Mono (the base, not shipped) | 359 | 13 | 730 | | Departure Mono NF Mono (not in the image) | 11,468 | 5 | 557 | **The monospace answer is still that there is no gap in what Alloy emits**, and it is now a built guarantee rather than a lucky one: `quasi-type` asserts the whole of Alloy's eighteen plus the two sort carets against the `cmap` of every face it cuts, so a face that lost one fails the font build instead of the image. The base underneath covers 5 of the 18 by itself; the other 13 are drawn. **562 of the widget ceiling is missing, and it is exactly braille and legacy computing.** 256 braille, 230 at U+1CD00-U+1CDE5, 12 at U+1CEA0, 64 in the supplement. Those are ratatui's `Canvas` and sparkline marker sets, and there is no `Canvas` and no `Sparkline` anywhere in `alloy/crates/`, so this is a ceiling and not a use. **Five more were missing when this was first re-measured, and they were closed rather than recorded.** `←` and `▶ ◀ ► ▾`: the set drew three of the four scrollbar arrows and two of the six triangles, and the old base drew the rest so nothing showed. They went into the house glyph set (v2) the same day, because one arrow in a row of four arriving from a fallback face — at another weight, on another baseline — is visible in a way a missing sextant is not. **The rule that replaces "IosevkaTerm covers everything":** what a cell-grid surface can emit is a set the house glyph set has to carry, and adding to it is an edit to `quasi-type`'s manifest plus a rebuild. Braille is the live example — the day a TUI draws a sparkline, the answer is a generated recipe in the set, not a fallback face on its own baseline. **Quasi Body's four gaps are not gaps**, because nothing routes box drawing to a sans font — they are `│ █ ▏ ░`, the four codepoints that exist to tile, and the slot deliberately does not carry them. Quasi Body serves the `sans-serif` alias and `gtk-font-name`, which is proportional UI text in GTK dialogs and the portal file chooser. The rule worth writing down: **cell-grid surfaces get the mono face and the cell tier with it; proportional surfaces get the body face and never emit one.** A future surface that breaks that rule is where this becomes a real gap. The pipeline enforces the same split from its own side: the body slot's coverage floor is Alloy's eighteen minus those four. **Departure's gaps only matter outside the running system.** It is not in the image (STACK.md), so it can only appear in a mockup, the README, the social card or a splash plate. Of the eighteen codepoints Alloy emits it lacks five: `⏎` (U+23CE), `␣` (U+2423), `▸` (U+25B8), `◂` (U+25C2), `✘` (U+2718). Four of those five are starship and helix prompt furniture, which is exactly what a terminal mockup renders, so a mockup set in Departure loses its prompt separators silently. It also has no braille, no sextants and no octants, so no Departure render can show a `Canvas` or a sparkline. Use the house mono for any mockup of a running surface, and keep Departure for headers and brand plates, which is what STACK.md already asks for on other grounds. ## The fallback chain, in order, and which links exist From `etc/skel/.config/fontconfig/fonts.conf`. Each `` list is tried in order and fontconfig skips a family the system does not have. | Alias | Order | In the image? | |---|---|---| | `monospace` | Quasi Mono | yes, one variable face under `/usr/share/fonts/quasi` | | `sans-serif` | Quasi Body | yes, the same directory | | | Cantarell | yes, `abattis-cantarell-vf-fonts` | | `serif` | Liberation Serif | yes | | `emoji` | (deliberately absent) | no emoji font is installed | 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. **Both house faces are variable, and fontconfig enumerates their named instances**, so `fc-match monospace` answers `Quasi Mono Regular` rather than the file's own default instance, which is ExtraLight, because a cut keeps its base's default. Measured by installing the built faces and querying fontconfig directly rather than on a booted image; the build asserts the same thing so the trap cannot reach one. **Every family the aliases name is installed, and none may name one that is not.** A name that is absent costs nothing at runtime, because `` skips what is missing, and it is a reading hazard: the list looks like a depth of fallback that is not there. **The `monospace` chain has one link.** If the house face ever failed to install, resolution does not reach a second Alloy pick. It drops straight into Fedora's generic rules (`45-latin.conf`, `60-latin.conf`) and lands on whatever those order first, which is the layer described below. **Inter is deliberately out of `sans-serif`.** It is not in the image, and a face named at position 2 of the alias means anyone who installs it for an unrelated app silently repoints Alloy's whole UI font. Preferring Inter is supported and is what STACK.md describes: an edit to the alias and to `gtk-font-name`, which is a choice rather than a side effect of having the font on disk. **What actually catches a gap is the layer below this file**, and it is worth naming because the chain above suggests otherwise: 1. `/etc/fonts/conf.d/45-latin.conf` and `60-latin.conf`, Fedora's generic-family ordering, which is what a request resolves through once the `` list is exhausted. 2. `59-adwaita-mono-fonts.conf`, `59-liberation-*.conf`, the URW base-35 set: the real substitutes present on disk for mono, sans and serif. 3. The Noto set for everything non-Latin: `NotoSans[wght].ttf` plus 24 per-script variable faces, `NotoSansCJK-VF.ttc` and `NotoSansMonoCJK-VF.ttc`. This is what stops an arbitrary web page from rendering as rows of missing glyphs. 4. Nothing, for emoji. That is the decided position, not a gap. ## Re-running this The scripts are not in the repo: the measurement is a one-off against a built image, and a checked-in tool that nobody runs drifts into a lie. Re-derive it by mounting the image and reading `cmap` tables directly. Half of it needs no image. The faces are cut rather than installed, so `cargo run -- build quasi-mono` in `quasi-type` produces the exact file the image gets, and `cargo run -- proof quasi-mono` rasterises it to a PNG — which is the check that catches a wrong shape, since a `cmap` count cannot. What still needs the image is everything about what else is on disk: the Noto set, the substitutes, and the `fc-match` answers under `HOME=/etc/skel`. The numbers worth re-checking after a font change are the size of Alloy's own emitted set (18), whether "missing from Alloy's set" is still 0 for the mono face, and whether the widget-ceiling miss is still only braille and legacy computing.