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
> 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+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).