max / quasi-type
- Co-Authored-By
- Claude Opus 5 (1M context) <noreply@anthropic.com>
- Claude-Session
- https://claude.ai/code/session_01MptwXZ8k65v19rFmdGAyki
10 files changed,
+88 insertions,
-115 deletions
| @@ -21,17 +21,15 @@ | |||
| 21 | 21 | ||
| 22 | 22 | The faces worth setting text in ship minimal mark inventories on principle. | |
| 23 | 23 | Atkinson Hyperlegible Mono draws 359 codepoints and not one of them is box | |
| 24 | - | drawing, a block element or an arrow; IBM Plex Mono, which this pipeline cut | |
| 25 | - | from until 2026-08-17, has a single glyph in the whole geometric-shapes block. | |
| 26 | - | That is a property of a good text face rather than a defect, so the answer is to | |
| 27 | - | patch rather than to shop. | |
| 24 | + | drawing, a block element or an arrow. That is a property of a good text face | |
| 25 | + | rather than a defect, so the answer is to patch rather than to shop. | |
| 28 | 26 | ||
| 29 | 27 | Split the vocabulary in two. **Letters, digits and punctuation** are the base | |
| 30 | - | face's job and are meant to differ per slot. **Marks** — carets, separators, | |
| 31 | - | status symbols, whitespace renders — carry meaning, and the meaning is the same | |
| 32 | - | in a terminal, a webview and an egui panel, so the drawing should be too. Before | |
| 33 | - | this, `▲` in a webview fell back per glyph to whatever the OS ordered first, so | |
| 34 | - | "unify the sort caret on the triangles" meant unifying on three platforms' | |
| 28 | + | face's job and are meant to differ per slot. **Marks** (carets, separators, | |
| 29 | + | status symbols, whitespace renders) carry meaning, and the meaning is the same | |
| 30 | + | in a terminal, a webview and an egui panel, so the drawing should be too. | |
| 31 | + | Without a cut face, a mark like `▲` falls back per glyph to whatever the OS | |
| 32 | + | orders first, and unifying the sort caret means unifying on three platforms' | |
| 35 | 33 | opinions. | |
| 36 | 34 | ||
| 37 | 35 | ## What is in the box | |
| @@ -99,13 +97,11 @@ | |||
| 99 | 97 | and the X is calibrated against the base's own `×` rather than by eye. | |
| 100 | 98 | ||
| 101 | 99 | **A coefficient is a relationship to the base, so it is refitted when the base | |
| 102 | - | moves.** Two did when `quasi-mono` went from Plex Mono to Atkinson: the ballot | |
| 103 | - | X's, because Atkinson's bar is thinner against its own `×` and the old number | |
| 104 | - | came out lighter than the multiplication sign beside it; and the space render's, | |
| 105 | - | because Atkinson's stem nearly triples across `wght` 200-800 where Plex's grew | |
| 106 | - | 1.8x between two static cuts, so the box closed up into a blob. Both are checked | |
| 107 | - | against the base at each end of the axis rather than against a number somebody | |
| 108 | - | wrote down. | |
| 100 | + | moves.** Check each one against the base at both ends of the axis rather than | |
| 101 | + | against a number somebody wrote down. The two that move most are the ballot X's, | |
| 102 | + | which reads light if the base's bar is thin against its own `×`, and the space | |
| 103 | + | render's, which closes into a blob if the base's stem grows steeply across | |
| 104 | + | `wght`. | |
| 109 | 105 | ||
| 110 | 106 | ## Adding a glyph | |
| 111 | 107 | ||
| @@ -117,8 +113,8 @@ | |||
| 117 | 113 | ||
| 118 | 114 | ## Variable bases | |
| 119 | 115 | ||
| 120 | - | A variable base is one file covering a range — Atkinson Hyperlegible Mono is | |
| 121 | - | `wght` 200 to 800 — and a cut **keeps that axis** rather than instancing a | |
| 116 | + | A variable base is one file covering a range (Atkinson Hyperlegible Mono is | |
| 117 | + | `wght` 200 to 800) and a cut **keeps that axis** rather than instancing a | |
| 122 | 118 | weight out of it. Instancing is the cheaper path and throws away the thing the | |
| 123 | 119 | base was chosen for. | |
| 124 | 120 | ||
| @@ -144,7 +140,7 @@ | |||
| 144 | 140 | to `wght` 200 and its own name table reads `ExtraLight`. A face cut from it is | |
| 145 | 141 | `ExtraLight` at rest, whatever a consumer hoped. The pin declares the style and | |
| 146 | 142 | the build asserts it against the base, so the trap fails the build rather than | |
| 147 | - | reaching a screen — and a consumer names the weight it wants | |
| 143 | + | reaching a screen. A consumer names the weight it wants | |
| 148 | 144 | (`font-weight: 200 800` in the `@font-face`, and the browser resolves `normal` | |
| 149 | 145 | to 400) instead of loading the file and taking what it opens at. | |
| 150 | 146 | ||
| @@ -156,23 +152,21 @@ | |||
| 156 | 152 | box drawing, arcs, dashes and block elements set adjacently on the cell grid, | |
| 157 | 153 | over a faint cell rule and baseline. | |
| 158 | 154 | ||
| 159 | - | This exists because every other check here measures a bounding box or an ink | |
| 160 | - | area, and three real defects walked past all of them — arcs curling the wrong | |
| 161 | - | way, a dashed glyph drawn as two arms with a seam, and an up-down arrow whose | |
| 162 | - | heads met in the middle and read as a bowtie. Each had a correct bbox. Draw the | |
| 163 | - | glyphs on a screen before believing a green suite. | |
| 155 | + | Every other check here measures a bounding box or an ink area, and a glyph can | |
| 156 | + | have a correct bbox while curling the wrong way, showing a seam, or reading as a | |
| 157 | + | bowtie. Draw the glyphs on a screen before believing a green suite. | |
| 164 | 158 | ||
| 165 | 159 | ## Slots, and what a slot takes | |
| 166 | 160 | ||
| 167 | 161 | A slot is a house type role, and the output family tracks the slot rather than | |
| 168 | - | the base: `quasi-mono` moved from IBM Plex Mono to Atkinson without a consumer | |
| 169 | - | changing a family name. | |
| 162 | + | the base, so a slot can change its base without a consumer changing a family | |
| 163 | + | name. | |
| 170 | 164 | ||
| 171 | 165 | A slot may take the whole set (`glyphs = "*"`) or the authored marks alone | |
| 172 | 166 | (`glyphs = "marks"`). `quasi-body` takes the marks: box drawing and block | |
| 173 | 167 | elements are sized against the cell and have to be cell-exact to tile, and a | |
| 174 | 168 | proportional face has no cell, so a `┌` in it would draw a corner that joins | |
| 175 | - | nothing. Its coverage floor is the role's for the same reason — a body face is | |
| 169 | + | nothing. Its coverage floor is the role's for the same reason: a body face is | |
| 176 | 170 | asked for everything a described screen sets in copy and for none of the four | |
| 177 | 171 | codepoints that exist to tile. | |
| 178 | 172 | ||
| @@ -180,9 +174,9 @@ | |||
| 180 | 174 | measure their band against and what `quasi-type params` reads the cell off. On a | |
| 181 | 175 | monospace base that is the cell. On a proportional one it is the width the base | |
| 182 | 176 | sets its own symbols on, and it answers the axis by pointing at that glyph's own | |
| 183 | - | `HVAR` delta set, so a mark narrows with `+` where `+` narrows. The trap this | |
| 184 | - | replaces is silent: `HVAR`'s advance map is indexed by glyph id, and a glyph | |
| 185 | - | appended past its end inherits whatever the last entry happens to say. | |
| 177 | + | `HVAR` delta set, so a mark narrows with `+` where `+` narrows. `HVAR`'s advance | |
| 178 | + | map is indexed by glyph id, so a glyph appended past its end and left to itself | |
| 179 | + | inherits whatever the last entry happens to say. | |
| 186 | 180 | ||
| 187 | 181 | ## Adding a base | |
| 188 | 182 | ||
| @@ -197,19 +191,15 @@ | |||
| 197 | 191 | ||
| 198 | 192 | A base arrives one of two ways and the pin says which. A project that publishes | |
| 199 | 193 | releases gets an archive `url` plus a `path` per face; one that publishes none | |
| 200 | - | gets a `url` per face instead, pinned at a commit. The shape that is deliberately | |
| 201 | - | missing is a generated source tarball: GitHub builds those on demand and has | |
| 202 | - | changed the bytes before, which would read here as "upstream moved" and mean | |
| 203 | - | nothing of the sort. | |
| 194 | + | gets a `url` per face instead, pinned at a commit. Do not pin a generated source tarball: GitHub builds those on | |
| 195 | + | demand and the bytes can change, which would read here as "upstream moved" and | |
| 196 | + | mean nothing of the sort. | |
| 204 | 197 | ||
| 205 | - | One refusal is left, about an assumption the pipeline makes elsewhere: a base | |
| 206 | - | that varies on more than one axis needs a decision about the master grid, since | |
| 207 | - | drawing at each end of one axis says nothing about the corners of two. | |
| 208 | - | ||
| 209 | - | A base whose advances vary was the second refusal until 2026-08-17, when the | |
| 210 | - | body slot answered it (see "Slots, and what a slot takes"). What is still | |
| 211 | - | refused is a base whose advances vary and whose `HVAR` carries no advance | |
| 212 | - | mapping, since there is then nothing for an appended glyph to point at. | |
| 198 | + | Two bases are refused. One that varies on more than one axis needs a decision | |
| 199 | + | about the master grid first, since drawing at each end of one axis says nothing | |
| 200 | + | about the corners of two. And one whose advances vary while its `HVAR` carries | |
| 201 | + | no advance mapping, since there is then nothing for an appended glyph to point | |
| 202 | + | at. | |
| 213 | 203 | ||
| 214 | 204 | ## Licence | |
| 215 | 205 |
| @@ -1,12 +1,10 @@ | |||
| 1 | 1 | //! The coverage assertion, run once per built face. | |
| 2 | 2 | //! | |
| 3 | - | //! This used to live in Alloy's `Containerfile`, which greps | |
| 4 | - | //! `fc-list ':charset=25B2'` after installing the font. That check is in the | |
| 5 | - | //! wrong place: it asks the image whether a face somebody else built covers a | |
| 6 | - | //! mark, so a gap surfaces as a failed image build rather than as a failed font | |
| 7 | - | //! build, and only for the one consumer that thought to grep. Asserting here | |
| 8 | - | //! means every consumer inherits the guarantee, and the image is left to assert | |
| 9 | - | //! only that it installed the right family. | |
| 3 | + | //! That check is in the wrong place: it asks the image whether a face somebody | |
| 4 | + | //! else built covers a mark, so a gap surfaces as a failed image build rather | |
| 5 | + | //! than as a failed font build, and only for the one consumer that thought to | |
| 6 | + | //! grep. Asserting here means every consumer inherits the guarantee, and the | |
| 7 | + | //! image is left to assert only that it installed the right family. | |
| 10 | 8 | ||
| 11 | 9 | use std::collections::BTreeMap; | |
| 12 | 10 | ||
| @@ -15,8 +13,8 @@ | |||
| 15 | 13 | use crate::Error; | |
| 16 | 14 | use crate::manifest::format_codepoint; | |
| 17 | 15 | ||
| 18 | - | /// Every codepoint Alloy's own surfaces put on a screen, measured 2026-08-06 | |
| 19 | - | /// against the built image and recorded in `alloy/docs/FONTS.md`. | |
| 16 | + | /// Every codepoint Alloy's own surfaces put on a screen against the built | |
| 17 | + | /// image and recorded in `alloy/docs/FONTS.md`. | |
| 20 | 18 | /// | |
| 21 | 19 | /// A face for the `monospace` alias has to cover all of it or a shipped config | |
| 22 | 20 | /// renders a missing glyph. Kept here as the pipeline's floor rather than in |
| @@ -685,14 +685,9 @@ | |||
| 685 | 685 | /// below, not the transport. | |
| 686 | 686 | /// | |
| 687 | 687 | /// **It retries, and that is not defensive coding.** The pins point at | |
| 688 | - | /// raw.githubusercontent.com, which rate-limits by IP, and a machine that builds | |
| 689 | - | /// Alloy asks it four times in one build: the image cuts two slots, and shop's | |
| 690 | - | /// build script cuts a third face into its own cache. Measured 2026-08-17, that | |
| 691 | - | /// is enough to earn a 429 on a developer's own laptop, and without a retry the | |
| 692 | - | /// image build fails on somebody else's rate limiter with the pinned bytes | |
| 693 | - | /// sitting one second away. curl retries 408, 429 and the 5xx family on | |
| 694 | - | /// `--retry` by itself; `--retry-all-errors` adds the connection failures, which | |
| 695 | - | /// are the other half of what a shared network does to a build. | |
| 688 | + | /// raw.githubusercontent.com, which rate-limits by IP, and a machine that | |
| 689 | + | /// builds Alloy asks it four times in one build: the image cuts two slots, and | |
| 690 | + | /// shop's build script cuts a third face into its own cache. | |
| 696 | 691 | /// | |
| 697 | 692 | /// This bounds the damage rather than fixing the cause. The cause is that four | |
| 698 | 693 | /// fetches of the same two files leave the machine, and the answer is a mirror |
| @@ -370,9 +370,9 @@ | |||
| 370 | 370 | }) | |
| 371 | 371 | } | |
| 372 | 372 | ||
| 373 | - | /// THE ADVANCE DECISION, settled 2026-08-17 cutting the body slot from Atkinson | |
| 374 | - | /// Hyperlegible Next: **a mark takes the reference glyph's advance, and answers | |
| 375 | - | /// the axis by pointing at that glyph's own `HVAR` delta set.** | |
| 373 | + | /// THE ADVANCE DECISION cutting the body slot from Atkinson Hyperlegible Next: | |
| 374 | + | /// **a mark takes the reference glyph's advance, and answers the axis by | |
| 375 | + | /// pointing at that glyph's own `HVAR` delta set.** | |
| 376 | 376 | /// | |
| 377 | 377 | /// The reference glyph is `+`, which is already what `quasi-type params` reads | |
| 378 | 378 | /// the cell off and what the recipes measure their band against. On a monospace |
| @@ -363,8 +363,8 @@ | |||
| 363 | 363 | use super::*; | |
| 364 | 364 | use crate::manifest::{Manifest, WeightResponse}; | |
| 365 | 365 | ||
| 366 | - | /// Atkinson Hyperlegible Mono at `wght` 200, its own default instance, | |
| 367 | - | /// measured 2026-08-17 from the pinned file (`quasi-type params`). | |
| 366 | + | /// Atkinson Hyperlegible Mono at `wght` 200, its own default instance from | |
| 367 | + | /// the pinned file (`quasi-type params`). | |
| 368 | 368 | /// | |
| 369 | 369 | /// These were Plex Mono Regular and Bold until the base moved. The two ends | |
| 370 | 370 | /// of one axis are a wider span than two static cuts were — 200 to 800 | |
| @@ -701,14 +701,14 @@ | |||
| 701 | 701 | ||
| 702 | 702 | /// The X is calibrated against the base's own cross rather than by eye. | |
| 703 | 703 | /// | |
| 704 | - | /// Atkinson Hyperlegible Mono's `×` (U+00D7) fills 30.0% of its bounding box | |
| 705 | - | /// at `wght` 200 and 56.9% at 800, measured 2026-08-17 by flattening its | |
| 706 | - | /// outline. U+2718 is the HEAVY ballot X, so it has to sit above that and | |
| 707 | - | /// not far above it. The first attempt used a flat 1.5x the base stroke and | |
| 708 | - | /// put 213 units of ink across a 380-unit mark, which is the failure this | |
| 709 | - | /// bounds — and the coefficient is refitted per base rather than carried | |
| 710 | - | /// over, since Plex's numbers were 38.2% and 53.4% and its bar was heavier | |
| 711 | - | /// against its own cross than Atkinson's is. | |
| 704 | + | /// Atkinson Hyperlegible Mono's `×` (U+00D7) fills 30.0% of its bounding | |
| 705 | + | /// box at `wght` 200 and 56.9% at 800 by flattening its outline. U+2718 is | |
| 706 | + | /// the HEAVY ballot X, so it has to sit above that and not far above it. | |
| 707 | + | /// The first attempt used a flat 1.5x the base stroke and put 213 units of | |
| 708 | + | /// ink across a 380-unit mark, which is the failure this bounds — and the | |
| 709 | + | /// coefficient is refitted per base rather than carried over, since Plex's | |
| 710 | + | /// numbers were 38.2% and 53.4% and its bar was heavier against its own | |
| 711 | + | /// cross than Atkinson's is. | |
| 712 | 712 | #[test] | |
| 713 | 713 | fn the_cross_is_heavier_than_the_bases_own_and_no_heavier() { | |
| 714 | 714 | for (params, base_fill) in [(light(), 0.300), (heavy(), 0.569)] { |
| @@ -213,11 +213,10 @@ | |||
| 213 | 213 | ||
| 214 | 214 | /// Cut the slot and rasterise it, one sheet per end of the axis. | |
| 215 | 215 | /// | |
| 216 | - | /// The last check in this pipeline is a person looking at the glyphs, and until | |
| 217 | - | /// this existed that meant installing the face. A variable cut gets a sheet at | |
| 218 | - | /// each master rather than at its default alone, because the defects the axis | |
| 219 | - | /// introduces — a mark whose stroke closes its own counter at the heavy end — | |
| 220 | - | /// are invisible at rest, which is exactly where every other check reads it. | |
| 216 | + | /// A variable cut gets a sheet at each master rather than at its default | |
| 217 | + | /// alone, because the defects the axis introduces — a mark whose stroke closes | |
| 218 | + | /// its own counter at the heavy end — are invisible at rest, which is exactly | |
| 219 | + | /// where every other check reads it. | |
| 221 | 220 | fn proof( | |
| 222 | 221 | root: &Path, | |
| 223 | 222 | out: &Path, |
| @@ -14,12 +14,12 @@ | |||
| 14 | 14 | /// value = band_extent * band + base_stroke * weight | |
| 15 | 15 | /// ``` | |
| 16 | 16 | /// | |
| 17 | - | /// The weight term is what the 2026-08-16 measurement of the base forced. Plex | |
| 18 | - | /// Mono redraws its own symbols heavier in Bold rather than merely thickening | |
| 19 | - | /// them in place: `•` goes 244 to 308 units wide, `×` 424 to 460, `↑` 414 to | |
| 20 | - | /// 478. Only the block elements hold still, and those are cell-fill primitives | |
| 21 | - | /// rather than symbols. A mark sized off the band alone would therefore stay | |
| 22 | - | /// Regular's size inside a Bold face and read light among its neighbours. | |
| 17 | + | /// Plex Mono redraws its own symbols heavier in Bold rather than merely | |
| 18 | + | /// thickening them in place: `•` goes 244 to 308 units wide, `×` 424 to 460, | |
| 19 | + | /// `↑` 414 to 478. Only the block elements hold still, and those are cell-fill | |
| 20 | + | /// primitives rather than symbols. A mark sized off the band alone would | |
| 21 | + | /// therefore stay Regular's size inside a Bold face and read light among its | |
| 22 | + | /// neighbours. | |
| 23 | 23 | /// | |
| 24 | 24 | /// Written as a bare number when the weight term is zero. | |
| 25 | 25 | #[derive(Debug, Clone, Copy)] |
| @@ -279,10 +279,9 @@ | |||
| 279 | 279 | ); | |
| 280 | 280 | } | |
| 281 | 281 | ||
| 282 | - | /// Atkinson declares no Reserved Font Name, so the gate this file documents | |
| 283 | - | /// has nothing to bite on for the base in the tree today. It is still the | |
| 284 | - | /// gate — a base that declares one is the common case, and Plex was one | |
| 285 | - | /// until 2026-08-17 — so it is exercised against a pin written here rather | |
| 282 | + | /// Atkinson declares no Reserved Font Name, so the gate this file | |
| 283 | + | /// documents has nothing to bite on for the base in the tree today. It is | |
| 284 | + | /// still the gate so it is exercised against a pin written here rather | |
| 286 | 285 | /// than dropped along with the base that made it necessary. | |
| 287 | 286 | #[test] | |
| 288 | 287 | fn the_house_name_clears_the_reserved_font_name() { |
| @@ -1,10 +1,10 @@ | |||
| 1 | 1 | //! Cutting a face from a variable base, which is the capability itself. | |
| 2 | 2 | //! | |
| 3 | 3 | //! The base is Atkinson Hyperlegible Mono: `wght` 200-800 in one file, and the | |
| 4 | - | //! base `quasi-mono` has been cut from since 2026-08-17. What is asserted here | |
| 5 | - | //! is what the axis adds — that it survives the cut, that the marks ride it, | |
| 6 | - | //! and that a mark calibrated at rest is still the right weight at the far end, | |
| 7 | - | //! which is the failure mode a static base could not have. | |
| 4 | + | //! base `quasi-mono` has been cut from since. What is asserted here is what | |
| 5 | + | //! the axis adds — that it survives the cut, that the marks ride it, and that | |
| 6 | + | //! a mark calibrated at rest is still the right weight at the far end, which | |
| 7 | + | //! is the failure mode a static base could not have. | |
| 8 | 8 | //! | |
| 9 | 9 | //! Skipped rather than failed when the base is not cached, same as the static | |
| 10 | 10 | //! cut. `cargo run -- params --base atkinson-mono` once and it runs for good. | |
| @@ -208,18 +208,15 @@ | |||
| 208 | 208 | /// The calibration holds at the far end of the axis, which is where adopting | |
| 209 | 209 | /// this base broke it. | |
| 210 | 210 | /// | |
| 211 | - | /// Both numbers here are read off the base at each end rather than written down | |
| 212 | - | /// as constants, because that is what the coefficients in the manifest are: a | |
| 213 | - | /// relationship to the base, refitted when the base moves. Recalibrated | |
| 214 | - | /// 2026-08-17 when `quasi-mono` moved onto Atkinson, whose stem nearly triples | |
| 215 | - | /// across `wght` 200-800 where Plex's grew 1.8x between two static cuts. | |
| 211 | + | /// Both numbers here are read off the base at each end rather than written | |
| 212 | + | /// down as constants, because that is what the coefficients in the manifest | |
| 213 | + | /// are: a relationship to the base, refitted when the base moves. | |
| 216 | 214 | /// | |
| 217 | - | /// - the ballot X is heavier than the base's own `×` and not far heavier. It | |
| 218 | - | /// came out *lighter* on the first cut from this base, at the coefficient | |
| 219 | - | /// Plex was calibrated against. | |
| 220 | - | /// - the space render keeps its counter open. At the old coefficients its | |
| 221 | - | /// risers took 278 units of a 413-unit box at `wght` 800 and it inked 90.6% | |
| 222 | - | /// of its own bounding box, which rasterises as a blob rather than a box. | |
| 215 | + | /// - the ballot X is heavier than the base's own `×` and not far heavier. A | |
| 216 | + | /// coefficient carried over from another base can leave it *lighter*. | |
| 217 | + | /// - the space render keeps its counter open. A coefficient that is too heavy | |
| 218 | + | /// at `wght` 800 inks most of its own bounding box, which rasterises as a | |
| 219 | + | /// blob rather than a box. | |
| 223 | 220 | #[test] | |
| 224 | 221 | fn the_marks_are_still_calibrated_at_the_heavy_end() { | |
| 225 | 222 | let Some((bytes, _)) = cut() else { return }; | |
| @@ -358,13 +355,9 @@ | |||
| 358 | 355 | assert!(names(10).contains("Atkinson Hyperlegible Mono 2.001")); | |
| 359 | 356 | } | |
| 360 | 357 | ||
| 361 | - | /// The done condition of `872fd945`, stated as a measurement rather than as a | |
| 362 | - | /// note. Atkinson ships no box drawing, no block elements and none of the four | |
| 363 | - | /// arrows; a face cut from it now covers the whole floor a consumer is entitled | |
| 364 | - | /// to assume, which is what unblocks pointing the mono slot at this base. | |
| 365 | - | /// | |
| 366 | - | /// This test used to assert the opposite — that the cut was *not* yet fit — and | |
| 367 | - | /// carried a message saying it would go stale when the gap closed. It has. | |
| 358 | + | /// Atkinson ships no box drawing, no block elements and none of the four | |
| 359 | + | /// arrows; a face cut from it covers the whole floor a consumer is entitled to | |
| 360 | + | /// assume, which is what lets the mono slot point at this base. | |
| 368 | 361 | #[test] | |
| 369 | 362 | fn the_cut_face_is_fit_for_the_mono_slot() { | |
| 370 | 363 | let Some((bytes, specs)) = cut() else { return }; |
| @@ -25,12 +25,11 @@ | |||
| 25 | 25 | //! near the x-height: a rule on the band's centre would run through the middle | |
| 26 | 26 | //! of the text beside it rather than between the lines. | |
| 27 | 27 | //! | |
| 28 | - | //! Worth knowing, because it differs from what the old base did. Plex Mono | |
| 29 | - | //! draws its box glyphs over y -350..950 while its own `hhea` says -275..1025 — | |
| 30 | - | //! the same 1300-unit height, positioned 75 units lower. So Plex's borders do | |
| 31 | - | //! not line up with a cell laid out from its own metrics, which is why | |
| 32 | - | //! terminals so often stretch box-drawing glyphs to the cell. Ours is built on | |
| 33 | - | //! the metrics a terminal actually lays out with, so it needs no stretching. | |
| 28 | + | //! A base need not agree with itself here: a face can draw its box glyphs over | |
| 29 | + | //! one vertical range while its own `hhea` says another, so its borders do not | |
| 30 | + | //! line up with a cell laid out from its metrics, which is why terminals so | |
| 31 | + | //! often stretch box-drawing glyphs to the cell. Ours is built on the metrics a | |
| 32 | + | //! terminal actually lays out with, so it needs no stretching. | |
| 34 | 33 | //! | |
| 35 | 34 | //! ## The stroke vocabulary | |
| 36 | 35 | //! |