Archilyzer · Source

archilyzer

Archilyzer
git clone https://archilyzer.pages.dev/source/archilyzer.git
Log | Files | Refs | README | LICENSE

commit baaebb8639aac1c7468393298a158f75a30efdd5
parent 69043125ad764394a38aabd66ed3e36dbf78b04c
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date:   Mon, 28 Sep 2026 12:33:39 -0400

umtool: svg-faces and the README stop calling the advance sum conservative and 1.3 em a bound (O5 review L1, L2)

Comments and prose only. IBM Plex Sans has positive kern pairs, so the
plain sum of advances is not the conservative side in general; it holds
empirically (real text's worst net kerning +0.50 px). FALLBACK_EM 1.3
covers emoji, flags, CJK and the common scripts, not every fallback glyph
(U+27F9 1.42 em). The file prints identically with comments stripped
(TypeScript printer, removeComments), so an unbranded render is unchanged
without a render. FACTS: the O5 entry's anchors and its "still say it is".

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

Diffstat:
Mplans/FACTS.md | 11+++++++----
Mumtool/report-to-video/README.md | 22++++++++++++++++++----
Mumtool/report-to-video/svg-faces.mjs | 23+++++++++++++++++------
3 files changed, 42 insertions(+), 14 deletions(-)

diff --git a/plans/FACTS.md b/plans/FACTS.md @@ -7121,8 +7121,9 @@ out. O3's facts are the section just above ("O3 — runner lows"). Anchors are a - **Under `render.brand: "archilyzer-media"` the rail, ledger, scroll and chart SVG text are IBM Plex Sans, fitted by the face's own advances**, not an average. `svg-faces.mjs`: `FIRA_SANS` - (`{family, em: 0.5}`, `:44`), `IBM_PLEX_SANS` (a measured face, `:60`), `textWidth(text, size, face, - {weight, ls})` (`:73`), `FALLBACK_EM = 1.3` for a character Plex does not map (`:41`). **1.3 em + (`{family, em: 0.5}`, `:55`), `IBM_PLEX_SANS` (a measured face, `:71`), `textWidth(text, size, face, + {weight, ls})` (`:84`), `FALLBACK_EM = 1.3` for a character Plex does not map (`:52`; anchors as + of release 13 W2, which lengthened the header comment by eleven lines). **1.3 em covers emoji, CJK and the common scripts, but not every fallback glyph** (O5 review L2, measured through the branded conf: `⟹` 1.42 em, `﷽` 1.93, `Ⅷ` 1.31) — a label made mostly of long arrows would overrun by ~9 %; no real label has one. The table is `face-metrics.mjs`, GENERATED by `fonts/gen-face-metrics.py` (fontTools @@ -7131,8 +7132,10 @@ out. O3's facts are the section just above ("O3 — runner lows"). Anchors are a - `render-cards.mjs` `fit(text, size, maxPx, face = FIRA_SANS, run)` (`:428`) and `wrapPx(…, face)` (`:1010`): an average face (Fira) runs the old code path verbatim; a measured face keeps the longest prefix whose width — ellipsis, weight and letter-spacing included — fits. **Kerning is - ignored, and the plain sum is NOT conservative in general** (O5 review L1; `svg-faces.mjs:32-35` and - the README still say it is): IBM Plex Sans has POSITIVE kerning pairs (412 at wght 400, 348 at 700, + ignored, and the plain sum is NOT conservative in general** (O5 review L1; *amended 2026-09-28, + release 13 slice W2:* `svg-faces.mjs:32-47`, the `FALLBACK_EM` doc comment and the README's "Why a + table, not an average" now say so, and say 1.3 em is not a bound for every glyph — comments and + prose only, no code changed, so no render was needed): IBM Plex Sans has POSITIVE kerning pairs (412 at wght 400, 348 at 700, up to +55 units: `r”`, `y’`, `f”`, `TT`, `AA`; bold `(j` +70), so `T`×60 at 13 px renders 464 px of ink against a table sum of 446.2. Over the 144 real strings and their capitals the worst net kerning is +0.50 px, so it is sub-pixel today. The fix, if it ever matters, is positive-only kern diff --git a/umtool/report-to-video/README.md b/umtool/report-to-video/README.md @@ -897,10 +897,24 @@ An average cannot promise a column. Measured the same way, Plex Sans averages and pushing the manifests' longest real text through each call site, even Fira's 0.50 lets the scroll's bold, letter-spaced column head overrun (152 px rendered in 140). The table does not: over the same runs, every Plex one -renders inside its budget (widest 98.9 %). Kerning is ignored: the plain sum of -advances came out at or above the rendered width of every one of those runs, so -it is the conservative side. A character the face does not map (emoji, CJK, -drawn by fontconfig's fallback) counts 1.3 em. +renders inside its budget (widest 98.9 %). + +Two limits, both measured and both sub-visible in the real manifests: + +- **Kerning is ignored, and the plain sum is not the conservative side in + general.** Plex Sans has positive kern pairs (412 at regular, 348 at bold, up + to +55 units: a closing quote after `r`, `y` or `f`, `TT`, `AA`; bold `(j` + +70), so text made of them renders wider than the table says — `T` × 60 at + 13 px is 464 px of ink against a sum of 446.2. Over the manifests' 144 real + strings, and the same in capitals, the worst net kerning is **+0.50 px**, so a + fitted run can end half a pixel past its budget at most. If that ever + matters, `gen-face-metrics.py` would emit the positive pairs and + `textWidth()` add them (the branded path only). +- **A character the face does not map** (emoji, CJK, drawn by fontconfig's + fallback) counts `FALLBACK_EM`, 1.3 em. That covers emoji (~1.23 em), flags, + CJK (at most 1 em) and the common scripts, but not every fallback glyph: a few + wide symbols exceed it (`⟹` 1.42 em, `﷽` 1.93, `Ⅷ` 1.31), so a label made + mostly of them would overrun by about 9 %. No real label has one. `face-metrics.mjs` is generated: `python3 fonts/gen-face-metrics.py` (fontTools) rewrites it from the vendored font, and a test fails when it is stale. diff --git a/umtool/report-to-video/svg-faces.mjs b/umtool/report-to-video/svg-faces.mjs @@ -29,15 +29,26 @@ // 140). A table does not: fit() keeps the longest prefix whose // measured width, ellipsis included, is inside the budget. // -// The measurement sums advances and ignores kerning: over the manifests' -// longest real text fitted at every call site, the rendered ink width of every -// Plex run came out at or under the sum, so the sum is the conservative side. -// A character the face does not map is drawn by fontconfig's fallback (emoji, -// CJK) and counted at FALLBACK_EM, wider than either. +// The measurement sums advances and ignores kerning, and the sum is NOT the +// conservative side in general: IBM Plex Sans has positive kern pairs (412 at +// wght 400, 348 at 700, up to +55 units -- a closing quote after r, y or f, +// TT, AA; bold "(j" +70), so text made of them renders wider than its sum ("T" +// x60 at 13 px: 464 px of ink against a sum of 446.2). It holds empirically: +// over the manifests' real text (144 strings, and the same in capitals) the +// worst net kerning is +0.50 px, so a fitted run can end up to half a pixel +// past its budget. If that ever matters, gen-face-metrics.py would emit the +// positive kern pairs and textWidth() add them (the branded path only). +// +// A character the face does not map is drawn by fontconfig's fallback and +// counted at FALLBACK_EM. 1.3 em covers emoji (~1.23 em), flags, CJK (at most +// 1 em) and the common scripts, but not every fallback glyph: a few wide +// symbols exceed it (U+27F9 1.42 em, U+FDFD 1.93, U+2167 1.31), so a label made +// mostly of them would overrun by about 9 %. No real label has one; raising it +// would only cut text with unmapped characters sooner. import { IBM_PLEX_SANS_METRICS } from "./face-metrics.mjs"; -/** An unmapped character's advance, in em: wider than a CJK ideograph (1 em) or a colour emoji (~1.25). */ +/** An unmapped character's advance, in em: wider than a CJK ideograph (1 em) or a colour emoji (~1.23), not every fallback glyph (above). */ export const FALLBACK_EM = 1.3; /** The unbranded face, and its average advance per character. */