ttf.c: glyph outline extraction, simple and composite (item 4.3.7a)

ttf_glyph_outline() decodes simple-glyph flag/coordinate runs and
recursively resolves composite components into a caller-supplied point/
contour-end buffer, in Q48.16. Composite scale/rotation/skew transforms
are rejected with TTF_ERR_UNSUPPORTED rather than mis-rendered, since the
shared q48_mul/q48_div are unsigned-only; translation-only composites
(the only kind the v1 glyph repertoire uses) apply cleanly via q48_add.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Robert Allan James
2026-08-10 21:45:04 -04:00
co-authored by Claude Sonnet 5
parent 5f6cc054d4
commit 70b6918279
4 changed files with 471 additions and 6 deletions
+36 -1
View File
@@ -4712,11 +4712,46 @@ document and committing that amendment as its own item.*
> compile check above is what stands in for it, per this item's own "done when" clause
> (which never asked for a kernel boot).
- [ ] **4.3.7a — Glyph outline extraction.** Simple and composite glyph outlines from `glyf`
- [x] **4.3.7a — Glyph outline extraction.** Simple and composite glyph outlines from `glyf`
— on-curve/off-curve point lists, quadratic Bézier control points, composite glyph
transforms — in Q48.16. *Done when:* outline point lists for a handful of test glyphs
(including at least one composite, e.g. an accented character if the test font has one)
match reference-tool output within Q48.16 rounding tolerance. *Refs:* §27.7.
> **Done 2026-08-10.** `ttf_glyph_outline()` in `src/starkernel/hal/ttf.c`/`ttf.h`: flags
> run-length decode, delta-decoded x/y coordinates (simple glyphs), and recursive composite
> component resolution, into caller-supplied point/contour-end buffers (no allocation in
> this module). Points are stored in raw font design units shifted into Q48.16
> (`q48_from_i32`, a local two's-complement left-shift — deliberately not routed through
> `q48_mul`/`q48_div`, see below).
>
> **Known gap, reported not fixed, per this repo's rule against modifying a shared/tested
> module without being asked:** `src/starkernel/math/q48_16.c`'s `q48_mul`/`q48_div` are
> unsigned-only (`q48_div` saturates via an unsigned overflow check on `a`; `q48_mul` does a
> plain unsigned widen-multiply) — confirmed by reading the source before writing any of
> this, not assumed. A composite glyph's transform can carry a signed F2Dot14 scale/
> rotation/skew, which needs a *signed* fixed-point multiply that function doesn't provide.
> Resolution taken here: `decode_composite_glyph()` applies (dx,dy) translation only (plain
> `q48_add`, safe under two's-complement regardless of the unsigned typing) and explicitly
> **rejects** any component with a non-identity scale/2×2 transform or point-matched
> (non-xy-offset) args, returning `TTF_ERR_UNSUPPORTED` rather than silently mis-rendering
> it. Checked, not assumed: every composite in `fonts/JetBrainsMono-Regular.ttf` (11 sampled
> accented Latin glyphs: `ÁÉÍÓÚÑéáñüö`) uses identity-scale translation-only components, so
> this doesn't block the v1 repertoire (§27.6.4) — but it's a real limitation for an
> arbitrary future font, and fixing it means either giving `q48_16.c` a signed multiply
> variant or doing the scale math locally the same way translation already is. Whoever picks
> that up next should decide which, not silently patch it in passing.
>
> Verified against `/tmp/.../scratchpad/ttf_outline_ref.py` (not committed, reproducible
> from this note) — a second from-scratch Python decoder sharing no code with `ttf.c`,
> covering the same "no `fonttools` installed" substitution already recorded under 4.3.7.
> Three glyphs checked point-for-point (coordinates, on/off-curve flags, contour-end
> indices) via the extended `tools/ttftest.c`: `.` (12 points, 1 contour, simple), `A` (17
> points, 2 contours, simple), and `á` (43 points, 3 contours, a genuine 2-component
> translation-only composite exercising the recursive path) — all match exactly.
> `gcc -std=c99 -Wall -Wextra -Werror`, zero warnings; single-file freestanding compile
> against the real amd64 `Makefile.starkernel` flags also re-checked clean. Same "not a
> kernel-boot change yet" posture as 4.3.7 — no FORTH/capsule wiring exists for this module,
> so no three-arch QEMU acceptance applies here either.
- [ ] **4.3.7b — Font data ingestion.** `.ttf` bytes encoded (hex or base64 — pick one, record
why) into capsule blocks per the resolved design decision (§27.7), decoded into a `kmalloc`