| 1 |
# The house glyph set: the durable artifact this pipeline exists to carry. |
| 2 |
# |
| 3 |
# Every entry is a mark rather than a letter. Letters, digits and punctuation are |
| 4 |
# the base face's job and are meant to differ per slot; marks carry meaning, and |
| 5 |
# the meaning does not change between a terminal, a webview and an egui panel, so |
| 6 |
# neither should the drawing. |
| 7 |
# |
| 8 |
# Sizes are never absolute units, so a glyph is refitted to the next base rather |
| 9 |
# than redrawn for it. Every dimension is |
| 10 |
# |
| 11 |
# band extent x band + base stroke x weight |
| 12 |
# |
| 13 |
# and both terms are measured off the base itself (see `quasi-type params`): |
| 14 |
# |
| 15 |
# band the symbol band, read off `+`: the extent the base fits its own |
| 16 |
# symbols into, horizontally and vertically. |
| 17 |
# stroke `-`'s bbox height, the base's horizontal stroke weight. |
| 18 |
# stem `|`'s bbox width, the base's vertical stroke weight. |
| 19 |
# advance the cell width. Marks centre on advance/2, not on the band. |
| 20 |
# |
| 21 |
# A dimension with no weight term is written as a bare number. |
| 22 |
# |
| 23 |
# WHY THE WEIGHT TERM EXISTS, measured 2026-08-16 across Plex Mono's two weights. |
| 24 |
# The base redraws its symbols heavier in Bold rather than only thickening them |
| 25 |
# in place: `•` runs 244 to 308 units wide, `×` 424 to 460, `↑` 414 to 478, and |
| 26 |
# `✓`, `❌` and `◊` all grow too. What holds still is `█` and `░`, and those are |
| 27 |
# cell-fill primitives rather than symbols. `+` is the other pattern: identical |
| 28 |
# bbox in both weights, heavier bars inside it. |
| 29 |
# |
| 30 |
# So the set follows the base in both, and which one a mark follows is a property |
| 31 |
# of the mark rather than a setting: |
| 32 |
# |
| 33 |
# solid marks (the triangles) have no stroke to thicken, so they grow. |
| 34 |
# stroked marks (the X, the box, the return arrow) grow a little and thicken, |
| 35 |
# which is the pattern `x` and `+` follow in the base. |
| 36 |
# |
| 37 |
# Sizing off the band alone would leave a mark at Regular's size inside a Bold |
| 38 |
# face, reading light among its neighbours. That was this file's first rule and |
| 39 |
# it was wrong: it generalised from the block elements, which are the one thing |
| 40 |
# in the base that does not answer weight at all. |
| 41 |
# |
| 42 |
# ON A VARIABLE BASE, the weight term stops being sampled at two points and |
| 43 |
# becomes the axis itself. A cut from a variable base keeps that axis, so every |
| 44 |
# mark is drawn once per master — the axis default and each end — and the |
| 45 |
# differences ship as `gvar` deltas. Nothing here changes: the recipe is already |
| 46 |
# parametric in the base's measurements, and a master is the same recipe read at |
| 47 |
# another location. |
| 48 |
# |
| 49 |
# What it puts on every recipe, including the generators the box-drawing set will |
| 50 |
# need, is one rule: **a mark's point count and contour order cannot depend on |
| 51 |
# the measurements**. Deltas are per point, so two masters of one glyph have to |
| 52 |
# be the same polygon at different sizes. A recipe that grew a contour past some |
| 53 |
# threshold would interpolate into a different shape halfway along the axis. The |
| 54 |
# pipeline refuses that case rather than shipping it, and a generator that emits |
| 55 |
# a fixed topology never meets it. |
| 56 |
# |
| 57 |
# Adding a glyph is an edit here plus a re-run against every face that consumes |
| 58 |
# the set. Nothing downstream needs touching. |
| 59 |
|
| 60 |
[set] |
| 61 |
# Bumped to 2 on 2026-08-17: `←` and four more triangles. A face's identity is |
| 62 |
# (base, set version), so a face carrying a set that grew is a different face |
| 63 |
# and says so in its own name table. |
| 64 |
version = 2 |
| 65 |
name = "quasi house glyph set" |
| 66 |
|
| 67 |
# --------------------------------------------------------------------------- |
| 68 |
# Sort carets. Decision 591fca15 unified the three renderers on the triangles; |
| 69 |
# until this set shipped, a webview drawing one fell back per glyph to whatever |
| 70 |
# the OS ordered first, so the "unified" caret was three platforms' opinions. |
| 71 |
# --------------------------------------------------------------------------- |
| 72 |
|
| 73 |
[[glyph]] |
| 74 |
codepoint = 0x25B2 |
| 75 |
name = "uni25B2" |
| 76 |
role = "sort caret, ascending" |
| 77 |
source = "decision 591fca15" |
| 78 |
shape = "triangle" |
| 79 |
direction = "up" |
| 80 |
# Across the base edge, off the band's width. |
| 81 |
span = { band = 0.75, weight = 0.62 } |
| 82 |
# Apex distance as a ratio of span. 0.866 is equilateral, at every weight. |
| 83 |
depth = 0.866 |
| 84 |
anchor = "band-center" |
| 85 |
|
| 86 |
[[glyph]] |
| 87 |
codepoint = 0x25BC |
| 88 |
name = "uni25BC" |
| 89 |
role = "sort caret, descending" |
| 90 |
source = "decision 591fca15" |
| 91 |
shape = "triangle" |
| 92 |
direction = "down" |
| 93 |
span = { band = 0.75, weight = 0.62 } |
| 94 |
depth = 0.866 |
| 95 |
anchor = "band-center" |
| 96 |
|
| 97 |
# The rest of the triangle run, on the same footing as `←` below: nothing in |
| 98 |
# Alloy emits them, and ratatui's scroll and selection markers reach for them, |
| 99 |
# so a face drawing two of the run and leaving four to a fallback is a row of |
| 100 |
# markers at two weights. Same recipe as the carets above and the same |
| 101 |
# proportions, rotated; `▾` takes the small separators'. |
| 102 |
# |
| 103 |
# `▶` and `▸` differ in size and `▶` and `►` do not differ at all here. Unicode |
| 104 |
# separates TRIANGLE from POINTER by intent rather than by shape, and a house set |
| 105 |
# that drew them differently would be inventing a distinction no consumer makes. |
| 106 |
# --------------------------------------------------------------------------- |
| 107 |
|
| 108 |
[[glyph]] |
| 109 |
codepoint = 0x25B6 |
| 110 |
name = "uni25B6" |
| 111 |
role = "scroll marker, right" |
| 112 |
source = "alloy widget ceiling, measured 2026-08-17" |
| 113 |
purpose = "coverage" |
| 114 |
shape = "triangle" |
| 115 |
direction = "right" |
| 116 |
span = { band = 0.75, weight = 0.62 } |
| 117 |
depth = 0.866 |
| 118 |
anchor = "band-center" |
| 119 |
|
| 120 |
[[glyph]] |
| 121 |
codepoint = 0x25C4 |
| 122 |
name = "uni25C4" |
| 123 |
role = "scroll marker, left" |
| 124 |
source = "alloy widget ceiling, measured 2026-08-17" |
| 125 |
purpose = "coverage" |
| 126 |
shape = "triangle" |
| 127 |
direction = "left" |
| 128 |
span = { band = 0.75, weight = 0.62 } |
| 129 |
depth = 0.866 |
| 130 |
anchor = "band-center" |
| 131 |
|
| 132 |
[[glyph]] |
| 133 |
codepoint = 0x25BA |
| 134 |
name = "uni25BA" |
| 135 |
role = "scroll marker, right pointer" |
| 136 |
source = "alloy widget ceiling, measured 2026-08-17" |
| 137 |
purpose = "coverage" |
| 138 |
shape = "triangle" |
| 139 |
direction = "right" |
| 140 |
span = { band = 0.75, weight = 0.62 } |
| 141 |
depth = 0.866 |
| 142 |
anchor = "band-center" |
| 143 |
|
| 144 |
[[glyph]] |
| 145 |
codepoint = 0x25BE |
| 146 |
name = "uni25BE" |
| 147 |
role = "scroll marker, down small" |
| 148 |
source = "alloy widget ceiling, measured 2026-08-17" |
| 149 |
purpose = "coverage" |
| 150 |
shape = "triangle" |
| 151 |
direction = "down" |
| 152 |
span = { band = 0.50, weight = 0.50 } |
| 153 |
depth = 0.75 |
| 154 |
anchor = "band-center" |
| 155 |
|
| 156 |
# --------------------------------------------------------------------------- |
| 157 |
# Starship segment separators and error state. |
| 158 |
# --------------------------------------------------------------------------- |
| 159 |
|
| 160 |
[[glyph]] |
| 161 |
codepoint = 0x25B8 |
| 162 |
name = "uni25B8" |
| 163 |
role = "starship, segment separator" |
| 164 |
shape = "triangle" |
| 165 |
direction = "right" |
| 166 |
# Unicode calls these SMALL, and they sit inline in a prompt. Span is the |
| 167 |
# vertical extent for a horizontal mark, so it comes off the band's height. |
| 168 |
span = { band = 0.50, weight = 0.50 } |
| 169 |
depth = 0.75 |
| 170 |
anchor = "band-center" |
| 171 |
|
| 172 |
[[glyph]] |
| 173 |
codepoint = 0x25C2 |
| 174 |
name = "uni25C2" |
| 175 |
role = "starship, segment separator" |
| 176 |
shape = "triangle" |
| 177 |
direction = "left" |
| 178 |
span = { band = 0.50, weight = 0.50 } |
| 179 |
depth = 0.75 |
| 180 |
anchor = "band-center" |
| 181 |
|
| 182 |
[[glyph]] |
| 183 |
codepoint = 0x2718 |
| 184 |
name = "uni2718" |
| 185 |
role = "starship, error state" |
| 186 |
shape = "cross" |
| 187 |
width = { band = 0.70, weight = 0.50 } |
| 188 |
height = { band = 0.70, weight = 0.50 } |
| 189 |
# Calibrated against the base's own cross rather than by eye: HEAVY BALLOT X is |
| 190 |
# heavier than `×` and not far heavier, so the number is read off the base each |
| 191 |
# time rather than carried over. A flat 1.5x read as a blob. |
| 192 |
# |
| 193 |
# RECALIBRATED 2026-08-17, adopting Atkinson, and this is the coefficient that |
| 194 |
# does not survive a base change. It was 0.80 against Plex Mono, whose `×` |
| 195 |
# (U+00D7) fills 38.2% of its own box in Regular and 53.4% in Bold, and ours |
| 196 |
# filled 40.7% and 55.5% — the margin the rule asks for. Atkinson's bar is |
| 197 |
# thinner against its own cross, so 0.80 came out *under* the base at every |
| 198 |
# weight (29.0% against 30.0%, 53.6% against 56.9%), which is a ballot X lighter |
| 199 |
# than the multiplication sign beside it. At 0.95 of the base's bar it fills |
| 200 |
# 33.7% and 59.1% against the base's 30.0% and 56.9%. |
| 201 |
stroke = 0.95 |
| 202 |
|
| 203 |
# --------------------------------------------------------------------------- |
| 204 |
# Helix whitespace renders. Both sit low in the cell, next to lowercase. |
| 205 |
# --------------------------------------------------------------------------- |
| 206 |
|
| 207 |
[[glyph]] |
| 208 |
codepoint = 0x2423 |
| 209 |
name = "uni2423" |
| 210 |
role = "helix, space render" |
| 211 |
shape = "open-box" |
| 212 |
# RECALIBRATED 2026-08-17, adopting Atkinson. The weight terms were 0.30 and the |
| 213 |
# box closed up: Atkinson's stem runs 54 units to 158 across `wght` 200-800 |
| 214 |
# against Plex's 1.8x from Regular to Bold, so at the heavy end two risers of |
| 215 |
# the base's own stroke took 278 of a 413-unit box and left a 135-unit counter. |
| 216 |
# Measured 0.906 fill of its own bounding box, which rasterises as a blob rather |
| 217 |
# than as a box, and the proof sheet is where it was seen (`quasi-type proof`). |
| 218 |
# |
| 219 |
# The rule the numbers now hold, and the one to keep when the next base arrives: |
| 220 |
# AN OPEN BOX HAS TO STAY OPEN. Its counter is what makes it read as a box, so |
| 221 |
# the box grows with the base's stroke rather than the stroke eating the box. |
| 222 |
# The aperture cannot be held exactly constant — the band term does not scale |
| 223 |
# with the stroke — so it is fitted at the heavy end, where it is scarce: the |
| 224 |
# counter is 50% of the box's width at `wght` 800 and 74% at 200. |
| 225 |
width = { band = 0.72, weight = 1.35 } |
| 226 |
height = { band = 0.38, weight = 0.60 } |
| 227 |
# Above the baseline, as a ratio of the band's height. |
| 228 |
bottom = 0.04 |
| 229 |
# A stroked mark: its bars are the base's own, unmultiplied. |
| 230 |
stroke = 1.0 |
| 231 |
|
| 232 |
[[glyph]] |
| 233 |
codepoint = 0x23CE |
| 234 |
name = "uni23CE" |
| 235 |
role = "helix, newline render" |
| 236 |
shape = "return-arrow" |
| 237 |
# Height of the riser's flat cap, as a ratio of the band's height. |
| 238 |
top = 0.92 |
| 239 |
# Centre of the shaft, as a ratio of the band's height above its floor. |
| 240 |
shaft = 0.26 |
| 241 |
# The head is solid, so it takes a weight term. Its coefficient is larger than |
| 242 |
# the triangles' because the head is measured against the shaft it sits on |
| 243 |
# rather than against the band: at { 0.36, 0.30 } the Bold head cleared its own |
| 244 |
# stroke by 38 units and read as a nub rather than an arrow. |
| 245 |
head_span = { band = 0.20, weight = 1.35 } |
| 246 |
head_depth = { band = 0.26, weight = 0.20 } |
| 247 |
stroke = 1.0 |
| 248 |
|
| 249 |
# --------------------------------------------------------------------------- |
| 250 |
# The arrows. Alloy's own surfaces emit four of these (docs/FONTS.md, measured |
| 251 |
# against the built image), and a base can ship a full text face without one: |
| 252 |
# Plex Mono draws all four, Atkinson Hyperlegible Mono draws none. |
| 253 |
# |
| 254 |
# One recipe, five entries. The head is the same drawing as the sort caret two |
| 255 |
# sections up, which is the point of a house set: a triangle in a status line |
| 256 |
# and a triangle on an arrow should not be two people's triangles. |
| 257 |
# |
| 258 |
# `←` is here on a different footing from the other four, and it is worth saying |
| 259 |
# which: nothing in Alloy emits it, but ratatui draws scrollbar ends with the |
| 260 |
# whole U+2190-U+2193 run, so a face carrying three of the four leaves the |
| 261 |
# fourth to a fallback — one arrow in a row of four at another face's weight and |
| 262 |
# another face's baseline. Measured 2026-08-17 against Alloy's widget ceiling |
| 263 |
# while adopting Atkinson; the old base drew all four and hid it. |
| 264 |
# --------------------------------------------------------------------------- |
| 265 |
|
| 266 |
[[glyph]] |
| 267 |
codepoint = 0x2190 |
| 268 |
name = "uni2190" |
| 269 |
role = "leftwards arrow" |
| 270 |
source = "alloy widget ceiling, measured 2026-08-17" |
| 271 |
purpose = "coverage" |
| 272 |
shape = "arrow" |
| 273 |
direction = "left" |
| 274 |
length = 0.92 |
| 275 |
head_span = 0.42 |
| 276 |
head_depth = 0.9 |
| 277 |
stroke = 1.0 |
| 278 |
|
| 279 |
[[glyph]] |
| 280 |
codepoint = 0x2191 |
| 281 |
name = "uni2191" |
| 282 |
role = "upwards arrow" |
| 283 |
source = "alloy docs/FONTS.md, the measured surface" |
| 284 |
purpose = "coverage" |
| 285 |
shape = "arrow" |
| 286 |
direction = "up" |
| 287 |
length = 0.92 |
| 288 |
head_span = 0.42 |
| 289 |
head_depth = 0.9 |
| 290 |
stroke = 1.0 |
| 291 |
|
| 292 |
[[glyph]] |
| 293 |
codepoint = 0x2193 |
| 294 |
name = "uni2193" |
| 295 |
role = "downwards arrow" |
| 296 |
source = "alloy docs/FONTS.md, the measured surface" |
| 297 |
purpose = "coverage" |
| 298 |
shape = "arrow" |
| 299 |
direction = "down" |
| 300 |
length = 0.92 |
| 301 |
head_span = 0.42 |
| 302 |
head_depth = 0.9 |
| 303 |
stroke = 1.0 |
| 304 |
|
| 305 |
[[glyph]] |
| 306 |
codepoint = 0x2192 |
| 307 |
name = "uni2192" |
| 308 |
role = "rightwards arrow" |
| 309 |
source = "alloy docs/FONTS.md, the measured surface" |
| 310 |
purpose = "coverage" |
| 311 |
shape = "arrow" |
| 312 |
direction = "right" |
| 313 |
length = 0.92 |
| 314 |
head_span = 0.42 |
| 315 |
head_depth = 0.9 |
| 316 |
stroke = 1.0 |
| 317 |
|
| 318 |
# Two heads on one shaft. The only member of the set that is not a single |
| 319 |
# gesture, and the reason `both_ends` exists rather than a second shape. |
| 320 |
# |
| 321 |
# Its head is smaller than the single-ended arrows above, and that is the |
| 322 |
# double end paying for itself rather than a taste call: at their proportions |
| 323 |
# two heads take 82% of the length and meet in the middle, so the mark reads as |
| 324 |
# a bowtie and not as an arrow with a shaft. Measured off the drawing, not |
| 325 |
# guessed — see `an_arrow_with_two_heads_still_has_a_shaft`. |
| 326 |
[[glyph]] |
| 327 |
codepoint = 0x2195 |
| 328 |
name = "uni2195" |
| 329 |
role = "up-down arrow" |
| 330 |
source = "alloy docs/FONTS.md, the measured surface" |
| 331 |
purpose = "coverage" |
| 332 |
shape = "arrow" |
| 333 |
direction = "up" |
| 334 |
both_ends = true |
| 335 |
length = 0.98 |
| 336 |
head_span = 0.34 |
| 337 |
head_depth = 0.75 |
| 338 |
stroke = 1.0 |
| 339 |
|
| 340 |
# --------------------------------------------------------------------------- |
| 341 |
# The cell tier. Box drawing and block elements, generated rather than authored: |
| 342 |
# their 160 recipes are already written down in Unicode's own character names, |
| 343 |
# and they must be cell-exact to tile, which is arithmetic rather than drawing. |
| 344 |
# See `src/cells` for the geometry and `scripts/derive-cell-table.py` for how |
| 345 |
# the table is derived. Asked for by block so this file stays a record of what |
| 346 |
# somebody chose rather than 160 stanzas of transcription. |
| 347 |
# |
| 348 |
# They are here because a base can be chosen for the quality of its text and |
| 349 |
# ship neither block: Atkinson Hyperlegible Mono has 0/128 box drawing and 0/32 |
| 350 |
# block elements, measured. Fallback cannot cover the gap, because a terminal |
| 351 |
# takes its baseline from the primary face alone, so borders arriving from a |
| 352 |
# fallback face with unrelated cell metrics draw off the line — broken borders |
| 353 |
# rather than tofu. |
| 354 |
# --------------------------------------------------------------------------- |
| 355 |
|
| 356 |
[[generated]] |
| 357 |
block = "box-drawing" |
| 358 |
|
| 359 |
[[generated]] |
| 360 |
block = "block-elements" |
| 361 |
|