diff --git a/FABRIC.md b/FABRIC.md index 950297e..d80232f 100644 --- a/FABRIC.md +++ b/FABRIC.md @@ -4388,52 +4388,68 @@ document and committing that amendment as its own item.* > `#ifdef __STARKERNEL__`, or elsewhere is an open question for that later item, not > decided here. -- [ ] **4.3.6 — Em-square glyph coordinate convention.** Define the normalized coordinate - space glyph strokes are authored in and the scale/translate step into `CART-PLOT` screen - coordinates, parallel to 4.3.3's `PROJECT`. Prerequisite for every glyph-drawing word that - follows. *Done when:* a normalized test shape scales/translates correctly onto raster - coordinates at two different requested sizes, verified live via screendump against - hand-calculated pixel positions. *Refs:* §27.6. +- [ ] **4.3.6 — Em-square glyph coordinate convention.** `EM-UNITS 1000` (baseline Y=0, + x-height ≈500, cap-height ≈700, ascender ≈750, descender ≈-250), plus `EM-X`/`EM-Y`/ + `G-LINE` scaling/translating em-square strokes into `CART-PLOT` screen coordinates via + `*/`, parallel to 4.3.3's `PROJECT`. Prerequisite for every glyph-drawing word that follows. + *Done when:* a normalized test shape scales/translates correctly onto raster coordinates at + two different requested sizes, verified live via screendump against hand-calculated pixel + positions. *Refs:* §27.6.1. -- [ ] **4.3.6a — UTF-8 decoder.** A FORTH word (or small wordset) walking a byte string and - yielding one Unicode codepoint at a time, composed in FORTH per the compose-in-FORTH-first - rule — no existing UTF-8 decoding anywhere in this tree (checked). *Done when:* correctly - decodes a test string mixing 1-byte ASCII and 2-byte Latin-1 sequences (e.g. `é` U+00E9), - verified live against hand-computed expected codepoints. *Refs:* §27.6. +- [ ] **4.3.6a — UTF-8 decoder.** `DECODE-UTF8`, composed in FORTH per the + compose-in-FORTH-first rule — no existing UTF-8 decoding anywhere in this tree (checked). + Must handle 1-byte and 2-byte sequences (ASCII, Latin-1) *and* 3-byte sequences (General + Punctuation — confirmed load-bearing for v1, not future-i18n-only, since smart + quotes/dashes/ellipsis live outside Latin-1). *Done when:* correctly decodes a test string + mixing 1-byte ASCII, 2-byte Latin-1 (e.g. `°` U+00B0), and 3-byte General Punctuation (e.g. + `—` U+2014) sequences, verified live against hand-computed expected codepoints. *Refs:* + §27.6.2. -- [ ] **4.3.6b — Codepoint → glyph dispatch.** Lookup mapping a decoded codepoint to its - stroke-drawing word within the active font-set capsule. *Done when:* dispatch correctly - invokes the right stroke word for at least 3 codepoints spanning ASCII and Latin-1, and - fails safely (defined, non-crashing behavior) for a codepoint outside the v1 repertoire. - *Refs:* §27.6. +- [ ] **4.3.6b — Codepoint → glyph dispatch.** Bucketed `CASE`/`OF`/`ENDOF` chain + (`DISPATCH-DIGIT`/`-UPPER`/`-LOWER`/`-ASCII-PUNCT`/`-LATIN1`/`-GENPUNCT`) routed by + `DISPATCH-GLYPH` via `WITHIN` range checks, per §27.6.3 — chosen over a flat xt-table + despite the table composing more cleanly with override (§27.6.5's finding). Every glyph + word follows the `( -- em-advance )` contract; unmatched codepoints fall to `TOFU`. *Done + when:* dispatch correctly invokes the right stroke word and returns its advance for at + least one codepoint from each of the six buckets, and falls through to `TOFU` (not a crash) + for a codepoint outside all six ranges. *Refs:* §27.6.3. -- [ ] **4.3.6c — Default system font-set capsule.** ASCII printable + the agreed Latin-1 - typographic set, each glyph a stroke-drawing word in the 4.3.6 em-square convention, one - capsule. *Done when:* every glyph in the v1 repertoire is defined, lint-clean - (`mkcapsule --lint`), individually verified to render a recognizable shape via screendump. - *Refs:* §27.6. +- [ ] **4.3.6c — Default system font-set capsule.** The confirmed 113-glyph v1 repertoire + (95 ASCII printable + 11 Latin-1 Supplement + 7 General Punctuation, itemized in §27.6.4), + each glyph a stroke-drawing word in the 4.3.6 em-square convention returning its own + `em-advance`, one capsule. *Done when:* every glyph in the 113-glyph v1 repertoire is + defined, lint-clean (`mkcapsule --lint`), individually verified to render a recognizable + shape via screendump. *Refs:* §27.6.4. - [ ] **4.3.6d — Boot-time loading.** Wire `fabric.4th` and the 4.3.6c font capsule into `init.4th`'s boot chain. *Done when:* standard three-arch QEMU boot shows both capsules `EXEC`'d cleanly as part of the normal boot sequence (not manual injection), no errors, logs committed. *Refs:* §27.6. -- [ ] **4.3.6e — User font override.** Mechanism to load and activate a replacement font-set - capsule over the default without redefining every caller. *Done when:* TBD — scoped in - detail once 4.3.6c exists (a second font-set is needed to test "override" against). - *Refs:* §27.6. +- [ ] **4.3.6e — User font override.** Mechanism resolved 2026-08-09 (§27.6.5): override + replaces a whole `DISPATCH-*` bucket word, not individual glyphs — `CASE`'s early binding + means redefining a single glyph word (e.g. `G-A`) alone does not change what an + already-compiled `DISPATCH-UPPER` calls, so the override capsule must redefine the entire + bucket it wants to change. *Done when:* a second, user-supplied capsule redefines one + `DISPATCH-*` bucket word (e.g. `DISPATCH-UPPER`), and after loading it, drawing a codepoint + from that bucket renders the overridden glyph, not the default — verified live via + screendump, both before and after the override loads. *Refs:* §27.6.5. -- [ ] **4.3.6f — TEXT-drawing entry point.** Decode a UTF-8 string, dispatch each codepoint - through 4.3.6b, draw via its stroke word, advance the cursor by glyph width. *Done when:* - a string containing both ASCII and at least one Latin-1 character renders correctly on the - CANVAS in one call, verified via screendump, no overlapping/misspaced glyphs. *Refs:* §27.6. +- [ ] **4.3.6f — `TEXT` entry point.** `TEXT ( c-addr u x y size color -- )`: decode UTF-8 via + 4.3.6a, dispatch each codepoint through 4.3.6b/`DRAW-GLYPH`, draw via its stroke word, scale + its returned `em-advance` to pixels (`GSIZE @ EM-UNITS */`) and accumulate into the cursor + X, per §27.6.6. *Done when:* a string containing ASCII, at least one Latin-1 character, and + at least one General Punctuation character (e.g. an em dash) renders correctly on the + CANVAS in one call, verified via screendump, with correct proportional spacing (no + overlapping/misspaced glyphs, visibly different advance widths for e.g. `i` vs `M`). + *Refs:* §27.6.6. - [ ] **4.3.6g — Checkpoint: render every v1 glyph on the CANVAS.** Same posture as 4.3.4/4.3.5f — stop and review here before scoping REPL wiring (M8). *Done when:* a single - screendump shows every glyph in the v1 repertoire rendered legibly and correctly - positioned, on all three architectures, matching the 4.3.3b/4.3.4 completion pattern - (screendump-verified, not just non-crashing). Three-arch acceptance boot clean, logs - committed. *Refs:* §27.6. + screendump shows all 113 glyphs in the v1 repertoire (§27.6.4) rendered legibly and + correctly positioned, on all three architectures, matching the 4.3.3b/4.3.4 completion + pattern (screendump-verified, not just non-crashing). Three-arch acceptance boot clean, + logs committed. *Refs:* §27.6. ⋯ *(4.3.x is open-ended — more items get appended here as Console work is scoped item by* *item, developed on the fly per §25.0. 4.4 below is unaffected by anything added above* @@ -4977,4 +4993,188 @@ still open, not blocking anything below. `capsules/BLOCK_MAP.md`); 4916+ in that family is free. Whether glyph work extends `fabric.4th` or lands in a new capsule file is an open call for whichever item creates it. +Detailed derivation follows, §27.6.1–§27.6.6, worked out 2026-08-09 against confirmed +primitives only (`cell_t = int64_t` — `include/vm.h:71`; `*/`, `WITHIN`, `CASE`/`OF`/ +`ENDOF`/`ENDCASE`, `CREATE`, `EXECUTE`, `'`, `LSHIFT`/`RSHIFT`/`AND`/`OR` all confirmed +registered in `src/word_source/*.c` — no word or syntax below was assumed without checking). +No `$`/`0x` hex-literal syntax was found anywhere in this tree, so every formula below uses +decimal literals only. + +### 27.6.1 Em-square coordinate convention (4.3.6) + +`PROJECT`/`TO-RASTER` take plain Cartesian pixel-space integers, not Q48.16 — confirmed by +reading `fabric.4th`: `Z->DELTA` is the only place Q48.16 conversion happens, and it is +already integer-in/integer-out at the `PROJECT` boundary. So a glyph stroke needs one +scale-and-translate step from a normalized em square into that same pixel space, then hands +straight off to the existing `LINE`. + +**`EM-UNITS 1000`**, standard Type1/OpenType proportions: baseline `Y=0`, x-height `≈500`, +cap-height `≈700`, ascender `≈750`, descender `≈-250`. + +Using `*/ ( n1 n2 n3 -- n4 )` = `n1*n2/n3` (confirmed C source, `arithmetic_words.c:212-234`: +`int64_t intermediate = n1 * n2; result = intermediate / n3`). Worth noting: the "64-bit +intermediate to avoid overflow" comment there is vestigial at this cell width — `cell_t` is +*already* `int64_t` (`include/vm.h:71`), so the intermediate buys nothing over plain `* /` +that classic 16-bit-cell FORTH-79 needed `*/` for. Not a bug, just an observation; `*/` is +still used below for its self-documenting "scale by a ratio" idiom. + +```forth +VARIABLE GOX VARIABLE GOY VARIABLE GSIZE VARIABLE GCOLOR +: EM-X ( em-x -- cart-x ) GSIZE @ EM-UNITS */ GOX @ + ; +: EM-Y ( em-y -- cart-y ) GSIZE @ EM-UNITS */ GOY @ + ; + +VARIABLE GX1 VARIABLE GY1 VARIABLE GX2 VARIABLE GY2 +: G-LINE ( gx1 gy1 gx2 gy2 -- ) + GY2 ! GX2 ! GY1 ! GX1 ! + GX1 @ EM-X GY1 @ EM-Y 0 + GX2 @ EM-X GY2 @ EM-Y 0 + GCOLOR @ + LINE ; +``` + +Store-then-fetch via `VARIABLE`s rather than deep stack-juggling four values — matches the +house style already used throughout `CIRCLE`/`ARC`/`ELLIPSE`/`EDGE`, not a new pattern. +`GOX`/`GOY` are the glyph's screen-space baseline-left anchor; `GSIZE` the requested pixel +size; both set by `DRAW-GLYPH` (§27.6.3) before a glyph word runs. + +**Precision note, stated not silently assumed:** `*/`'s C division truncates toward zero, +not floor. For negative em-y values (descenders, below baseline) this loses sub-pixel +precision (e.g. `-1 16 1000 */` = `0`, not `-1`) — not a bug, but on record given this +project's history with exactly this class of truncation issue (`Q.TO-INT`, 4.3.3b). + +### 27.6.2 UTF-8 decoder (4.3.6a) + +Standard, unambiguous algorithm — not a design choice, just correct implementation. Lead-byte +classification by bit pattern: + +```forth +: UTF8-SEQ-LEN ( lead -- n ) \ 0 = invalid lead byte + DUP 128 < IF DROP 1 EXIT THEN + DUP 224 AND 192 = IF DROP 2 EXIT THEN + DUP 240 AND 224 = IF DROP 3 EXIT THEN + DUP 248 AND 240 = IF DROP 4 EXIT THEN + DROP 0 ; + +: UTF8-CONT? ( byte -- flag ) \ true if 10xxxxxx continuation byte + 192 AND 128 = ; +``` + +Codepoint assembly formulas (the specification `DECODE-UTF8` must compute — the exact FORTH +stack mechanics for consuming 1-4 bytes from a buffer and returning both the codepoint and +the advanced pointer are left to 4.3.6a's own implementation and live testing, per this +project's demonstrated history of subtle stack/precision bugs surfacing only under real +testing, not design review — presenting untested stack-juggling code here as settled would +be exactly the kind of confidently-wrong mistake §25.0 rule 4 warns against): + +- 1-byte: `codepoint = lead` +- 2-byte: `codepoint = ((lead AND 31) LSHIFT 6) OR (cont1 AND 63)` +- 3-byte: `((lead AND 15) LSHIFT 12) OR ((cont1 AND 63) LSHIFT 6) OR (cont2 AND 63)` +- 4-byte: `((lead AND 7) LSHIFT 18) OR ((cont1 AND 63) LSHIFT 12) OR ((cont2 AND 63) LSHIFT 6) + OR (cont3 AND 63)` + +**Correction found while scoping the character list (§27.6.4): the 3-byte path is v1-required, +not future-i18n-only.** Smart quotes/en-dash/em-dash/ellipsis are Unicode General Punctuation +(U+2000–U+206F), which is *not* Latin-1 Supplement despite being commonly lumped in with +"Latin typography" — it encodes as 3-byte UTF-8. Originally assumed 3/4-byte decoding was +pure future-proofing; it is not, once those specific characters are in scope. + +### 27.6.3 Codepoint → glyph dispatch (4.3.6b) + +`WITHIN`'s exact semantics confirmed against `logical_words.c:370-384`, not assumed from the +ANS spec: `( n low high -- flag )` = `low <= n < high` (inclusive-low, exclusive-high). + +Dispatch is a bucketed `CASE`/`OF`/`ENDOF` chain (not the flat 256-entry execution-token +table originally proposed — deliberately chosen over the table despite the table composing +more cleanly with override, see §27.6.5). `CASE` bodies cannot span block boundaries (4.3.3b +finding), so ~113 glyphs are split into range buckets, each its own word: + +```forth +: DISPATCH-DIGIT ( codepoint -- em-advance ) \ 48-57 +: DISPATCH-UPPER ( codepoint -- em-advance ) \ 65-90 +: DISPATCH-LOWER ( codepoint -- em-advance ) \ 97-122 +: DISPATCH-ASCII-PUNCT ( codepoint -- em-advance ) \ scattered 32-47,58-64,91-96,123-126 +: DISPATCH-LATIN1 ( codepoint -- em-advance ) \ Latin-1 Supplement subset, §27.6.4 +: DISPATCH-GENPUNCT ( codepoint -- em-advance ) \ General Punctuation subset, §27.6.4 + +: DISPATCH-GLYPH ( codepoint -- em-advance ) + DUP 48 58 WITHIN IF DISPATCH-DIGIT EXIT THEN + DUP 65 91 WITHIN IF DISPATCH-UPPER EXIT THEN + DUP 97 123 WITHIN IF DISPATCH-LOWER EXIT THEN + DUP 32 127 WITHIN IF DISPATCH-ASCII-PUNCT EXIT THEN + DUP 160 256 WITHIN IF DISPATCH-LATIN1 EXIT THEN + DUP 8192 8304 WITHIN IF DISPATCH-GENPUNCT EXIT THEN + DROP TOFU ; + +: DRAW-GLYPH ( codepoint x y size color -- em-advance ) + GCOLOR ! GSIZE ! GOY ! GOX ! + DISPATCH-GLYPH ; +``` + +Each bucket word ends with the same default-clause pattern already used by `doe.4th`'s +`WL-HI` (`DROP` the unmatched selector, then the default expression) — not a new idiom: + +```forth +: DISPATCH-DIGIT ( codepoint -- em-advance ) + CASE + 48 OF G-0 ENDOF 49 OF G-1 ENDOF ( ... ) 57 OF G-9 ENDOF + DROP TOFU + ENDCASE ; +``` + +**Glyph-word contract: `( -- em-advance )`.** Every glyph word (`G-A`, `G-0`, …) draws itself +via `G-LINE` using the `GOX`/`GOY`/`GSIZE`/`GCOLOR` context `DRAW-GLYPH` already set, then +leaves its own advance width in em-units on the stack (per the proportional-width decision, +§27.6.4). `TOFU ( -- em-advance )` draws an empty box roughly cap-height tall and returns a +fixed default advance — proposing `500` (half an em); flag if a different default is wanted. + +**Block budget, concretely estimated.** `WL-HI` (8 entries, one line each) fits in one block +with room to spare. At a conservative ~15 entries/block: `DISPATCH-UPPER`/`DISPATCH-LOWER` +(26 each) need ~2 blocks apiece, `DISPATCH-DIGIT` (10) needs 1, `DISPATCH-ASCII-PUNCT` (~32) +needs ~2-3, `DISPATCH-LATIN1`+`DISPATCH-GENPUNCT` (18 combined, §27.6.4) need ~1-2. **~8-10 +blocks for dispatch alone**, before any glyph's actual stroke data — on record so 4.3.6c +isn't scoped against a fantasy budget. + +### 27.6.4 Character list, confirmed 2026-08-09 (4.3.6c) + +**Correction to the original "ASCII + Latin-1 typographic set" framing:** curly quotes, +en/em dash, and ellipsis are *not* Latin-1 Supplement (U+0080–U+00FF) — they're Unicode +General Punctuation (U+2000–U+206F), a separate, non-contiguous block. The confirmed v1 +repertoire spans both, correctly bucketed: + +- **ASCII printable** (32–126): all 95, `DISPATCH-DIGIT`/`DISPATCH-UPPER`/`DISPATCH-LOWER`/ + `DISPATCH-ASCII-PUNCT`. +- **Latin-1 Supplement** (11): `°` `©` `®` `±` `×` `÷` `¢` `£` `§` `¶`, plus non-breaking + space (U+00A0 — non-printing, advance-only glyph, no visible stroke). +- **General Punctuation** (7): `'` `'` (U+2018/2019), `"` `"` (U+201C/201D), `–` `—` + (U+2013/2014), `…` (U+2026). + +**113 glyphs total for v1** (95 + 11 + 7), each needing its own stroke-drawing word plus a +`CASE OF...ENDOF` entry in its bucket. + +### 27.6.5 User font override (4.3.6e) + +**Real constraint, found while deriving, not designed around silently.** `CASE`/`OF`/`ENDOF` +compiles an early-bound call — `DISPATCH-UPPER`'s `CASE` body bakes in a call to `G-A`'s +address at the point `DISPATCH-UPPER` is *compiled* (during the default font capsule's +load), the same way 4.3.3b found `CART-PLOT`-style redefinition only affects code compiled +*after* the redefinition. A user-font capsule that later redefines `G-A` alone does **not** +change what `DISPATCH-UPPER` calls — the override silently does nothing. This is the direct +cost of choosing `CASE` dispatch over the flat xt-table (which would have made override a +trivial table-slot overwrite, checked live via `@` on every dispatch). + +**Resolved 2026-08-09: override replaces the whole bucket word, not individual glyphs.** A +user-font capsule that wants to change even one letter must redefine the entire relevant +`DISPATCH-*` word (e.g. all of `DISPATCH-UPPER` to change one uppercase letter) — no new data +structure, pure `CASE`, at the cost of override granularity. Worth stating in the open item +itself, not discovered fresh when 4.3.6e is implemented. + +### 27.6.6 `TEXT` entry point, algorithm-level (4.3.6f) + +`TEXT ( c-addr u x y size color -- )` walks the UTF-8 byte string left-to-right: decode one +codepoint via `DECODE-UTF8` (§27.6.2), call `DRAW-GLYPH` with the running cursor position to +draw it and get back its `em-advance`, scale that advance to pixels via `GSIZE @ EM-UNITS +*/ ` (same idiom as `EM-X`/`EM-Y`), accumulate into the cursor X, repeat until the buffer is +exhausted. Presented at the algorithm level, same caveat as `DECODE-UTF8` — the exact loop +construction is 4.3.6f's own implementation work, not settled here. + *Refs:* §25.5 (punch list).