docs: deep-derive glyph rendering design (4.3.6-4.3.6g), FABRIC.md \xc2\xa727.6.1-6

Expands the previously sparse \xc2\xa727.6 to match the derivation depth of
\xc2\xa727.3/\xc2\xa727.5.1/\xc2\xa727.5.2 -- worked line items, not just prose scoping.
Every primitive used (*/, WITHIN, CASE/OF/ENDOF, CREATE, EXECUTE, ',
LSHIFT/RSHIFT/AND/OR) was confirmed against src/word_source/*.c before
being used in a formula; no $/0x hex literal syntax exists in this tree,
so all formulas use decimal only.

Concrete design landed, in order derived:
- EM-UNITS 1000, standard Type1/OpenType em-square proportions; EM-X/
  EM-Y/G-LINE scale-and-translate into CART-PLOT space via */ (noting
  */'s "avoid overflow" rationale is vestigial at this VM's 64-bit
  cell_t width -- not a bug, just an observation)
- UTF-8 decoder: full bit-level 1/2/3/4-byte assembly formulas. Found
  mid-derivation that the 3-byte path is v1-required, not future-i18n-
  only -- smart quotes/dashes/ellipsis are Unicode General Punctuation
  (U+2000-206F), NOT Latin-1 Supplement despite the original framing
  lumping them together
- Dispatch: bucketed CASE/OF/ENDOF chains (DISPATCH-DIGIT/-UPPER/-LOWER/
  -ASCII-PUNCT/-LATIN1/-GENPUNCT) routed by WITHIN range checks, chosen
  over a flat 256-entry xt-table despite the table composing more
  cleanly with override -- explicit tradeoff, not an oversight
  - Glyph-word contract ( -- em-advance ), TOFU placeholder for misses,
    ~8-10 block budget estimate for dispatch alone
- Character list corrected and finalized: 113 glyphs (95 ASCII + 11
  Latin-1 Supplement + 7 General Punctuation), itemized
- User font override: found CASE's early binding means redefining a
  single glyph word does NOT retroactively change an already-compiled
  DISPATCH-* caller (same binding behavior 4.3.3b found for CART-PLOT).
  Resolved: override replaces a whole DISPATCH-* bucket word, not
  individual glyphs -- explicit granularity tradeoff for keeping CASE
- TEXT entry point sketched at algorithm level

DECODE-UTF8's exact byte-consumption stack mechanics and TEXT's loop
are deliberately left as algorithm-level spec, not final FORTH -- this
project's history (VARIABLE alignment, Q.TO-INT sign bug) shows exactly
this class of stack/precision bug surfaces only under live testing, not
design review; presenting untested stack-juggling code as settled would
be the "confidently wrong" mistake \xc2\xa725.0 rule 4 warns against.

Punch-list items 4.3.6-4.3.6g (\xc2\xa725.5) updated to match: concrete Done-
when criteria replacing several that were previously vague or TBD
(4.3.6b now names the six buckets; 4.3.6c names the 113-glyph list;
4.3.6e goes from "TBD" to a real, testable override-mechanism
criterion; 4.3.6f requires proportional-width verification). Still no
implementation -- scoping and derivation only, per \xc2\xa725.0 rule 2.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Robert Allan James
2026-08-09 18:14:48 -04:00
co-authored by Claude Sonnet 5
parent b69dcfd050
commit 0a03ba7871
+233 -33
View File
@@ -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 > `#ifdef __STARKERNEL__`, or elsewhere is an open question for that later item, not
> decided here. > decided here.
- [ ] **4.3.6 — Em-square glyph coordinate convention.** Define the normalized coordinate - [ ] **4.3.6 — Em-square glyph coordinate convention.** `EM-UNITS 1000` (baseline Y=0,
space glyph strokes are authored in and the scale/translate step into `CART-PLOT` screen x-height ≈500, cap-height ≈700, ascender ≈750, descender ≈-250), plus `EM-X`/`EM-Y`/
coordinates, parallel to 4.3.3's `PROJECT`. Prerequisite for every glyph-drawing word that `G-LINE` scaling/translating em-square strokes into `CART-PLOT` screen coordinates via
follows. *Done when:* a normalized test shape scales/translates correctly onto raster `*/`, parallel to 4.3.3's `PROJECT`. Prerequisite for every glyph-drawing word that follows.
coordinates at two different requested sizes, verified live via screendump against *Done when:* a normalized test shape scales/translates correctly onto raster coordinates at
hand-calculated pixel positions. *Refs:* §27.6. 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 - [ ] **4.3.6a — UTF-8 decoder.** `DECODE-UTF8`, composed in FORTH per the
yielding one Unicode codepoint at a time, composed in FORTH per the compose-in-FORTH-first compose-in-FORTH-first rule — no existing UTF-8 decoding anywhere in this tree (checked).
rule — no existing UTF-8 decoding anywhere in this tree (checked). *Done when:* correctly Must handle 1-byte and 2-byte sequences (ASCII, Latin-1) *and* 3-byte sequences (General
decodes a test string mixing 1-byte ASCII and 2-byte Latin-1 sequences (e.g. `é` U+00E9), Punctuation — confirmed load-bearing for v1, not future-i18n-only, since smart
verified live against hand-computed expected codepoints. *Refs:* §27.6. 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 - [ ] **4.3.6b — Codepoint → glyph dispatch.** Bucketed `CASE`/`OF`/`ENDOF` chain
stroke-drawing word within the active font-set capsule. *Done when:* dispatch correctly (`DISPATCH-DIGIT`/`-UPPER`/`-LOWER`/`-ASCII-PUNCT`/`-LATIN1`/`-GENPUNCT`) routed by
invokes the right stroke word for at least 3 codepoints spanning ASCII and Latin-1, and `DISPATCH-GLYPH` via `WITHIN` range checks, per §27.6.3 — chosen over a flat xt-table
fails safely (defined, non-crashing behavior) for a codepoint outside the v1 repertoire. despite the table composing more cleanly with override (§27.6.5's finding). Every glyph
*Refs:* §27.6. 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 - [ ] **4.3.6c — Default system font-set capsule.** The confirmed 113-glyph v1 repertoire
typographic set, each glyph a stroke-drawing word in the 4.3.6 em-square convention, one (95 ASCII printable + 11 Latin-1 Supplement + 7 General Punctuation, itemized in §27.6.4),
capsule. *Done when:* every glyph in the v1 repertoire is defined, lint-clean each glyph a stroke-drawing word in the 4.3.6 em-square convention returning its own
(`mkcapsule --lint`), individually verified to render a recognizable shape via screendump. `em-advance`, one capsule. *Done when:* every glyph in the 113-glyph v1 repertoire is
*Refs:* §27.6. 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 - [ ] **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 `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, `EXEC`'d cleanly as part of the normal boot sequence (not manual injection), no errors,
logs committed. *Refs:* §27.6. logs committed. *Refs:* §27.6.
- [ ] **4.3.6e — User font override.** Mechanism to load and activate a replacement font-set - [ ] **4.3.6e — User font override.** Mechanism resolved 2026-08-09 (§27.6.5): override
capsule over the default without redefining every caller. *Done when:* TBD — scoped in replaces a whole `DISPATCH-*` bucket word, not individual glyphs — `CASE`'s early binding
detail once 4.3.6c exists (a second font-set is needed to test "override" against). means redefining a single glyph word (e.g. `G-A`) alone does not change what an
*Refs:* §27.6. 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 - [ ] **4.3.6f — `TEXT` entry point.** `TEXT ( c-addr u x y size color -- )`: decode UTF-8 via
through 4.3.6b, draw via its stroke word, advance the cursor by glyph width. *Done when:* 4.3.6a, dispatch each codepoint through 4.3.6b/`DRAW-GLYPH`, draw via its stroke word, scale
a string containing both ASCII and at least one Latin-1 character renders correctly on the its returned `em-advance` to pixels (`GSIZE @ EM-UNITS */`) and accumulate into the cursor
CANVAS in one call, verified via screendump, no overlapping/misspaced glyphs. *Refs:* §27.6. 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.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 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 screendump shows all 113 glyphs in the v1 repertoire (§27.6.4) rendered legibly and
positioned, on all three architectures, matching the 4.3.3b/4.3.4 completion pattern correctly positioned, on all three architectures, matching the 4.3.3b/4.3.4 completion
(screendump-verified, not just non-crashing). Three-arch acceptance boot clean, logs pattern (screendump-verified, not just non-crashing). Three-arch acceptance boot clean,
committed. *Refs:* §27.6. logs committed. *Refs:* §27.6.
⋯ *(4.3.x is open-ended — more items get appended here as Console work is scoped item by* ⋯ *(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* *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 `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. `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+2000U+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+0080U+00FF) — they're Unicode
General Punctuation (U+2000U+206F), a separate, non-contiguous block. The confirmed v1
repertoire spans both, correctly bucketed:
- **ASCII printable** (32126): 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). *Refs:* §25.5 (punch list).