ttf.c: rasterization -- Bezier flattening + even-odd scanline fill
Punch list §25 item 4.3.7c complete. ttf_rasterize_glyph() flattens quadratic-Bezier contours (fixed 8-segment subdivision, matching CIRCLE/ELLIPSE's fixed-segment precedent) and fills them into a caller-supplied bitmap via even-odd scanline fill, no AA. A local signed Q48.16 multiply (q48_smul) handles negative outline coordinates, since the shared q48_mul/q48_div are unsigned-only. Verified two ways: tools/ttftest.c's ASCII-art dump + structural checks for 'A'/'.'/'a', all recognizable and passing; and a live amd64 screendump via a throwaway TTF-PROBE word (loaded the font capsule, rasterized 'A', blit via fb_put_pixel), showing a clearly legible 'A' on the CANVAS -- probe reverted immediately after capture, only the permanent ttf.c/ttf.h rasterizer remains. Compile-checked clean on all three architectures (hal/*.c wildcard); this item's own acceptance is the amd64 screendump, not a three-arch boot (that's 4.3.7f). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
2ef8d26c6f
commit
095860251a
@@ -4779,11 +4779,75 @@ document and committing that amendment as its own item.*
|
||||
> capsule-birth/init.4th wiring exists for it; that's 4.3.7e's job once `TTF-TEXT` needs it.
|
||||
> Same "not a kernel-boot change" posture as 4.3.7/4.3.7a; no three-arch QEMU run applies.
|
||||
|
||||
- [ ] **4.3.7c — Rasterization.** Bézier curve flattening to line segments (reusing the
|
||||
- [x] **4.3.7c — Rasterization.** Bézier curve flattening to line segments (reusing the
|
||||
existing `LINE`/Bresenham primitive where practical), then fill. Antialiasing approach is
|
||||
an open question — not decided here, resolve when this item is picked up. *Done when:* a
|
||||
single glyph outline rasterizes to a recognizable filled (or outlined, if AA is deferred)
|
||||
shape on the CANVAS, screendump-verified. *Refs:* §27.7.
|
||||
> **Done 2026-08-10.** `ttf_rasterize_glyph()` in `src/starkernel/hal/ttf.c`/`ttf.h`:
|
||||
> quadratic-Bezier contour flattening (fixed 8-segment subdivision per curve, same
|
||||
> fixed-segment-count approach as `capsules/fabric.4th`'s `CIRCLE`/`ELLIPSE`, 36 segs, and
|
||||
> `ARC`, 18 segs — no adaptive tessellation) followed by an even-odd scanline fill into a
|
||||
> caller-supplied 8-bit-per-pixel bitmap. No antialiasing, per this item's own allowance.
|
||||
>
|
||||
> **Doc error found and reported, not fixed:** §27.7 decision #1 claims 4.3.3b put
|
||||
> `LINE`/`CIRCLE`/`ARC`/`ELLIPSE` "in C rather than FORTH" — 4.3.3b's own completion note
|
||||
> says the opposite: they're FORTH words in `capsules/fabric.4th` (blocks 4903–4912), built
|
||||
> on the C-level `PLOT`/`TO-RASTER` primitives. C is still the right call for this item
|
||||
> (4.3.7's item text says so directly, independent of that reasoning), but "reusing the
|
||||
> existing `LINE`/Bresenham primitive" from a C module can't mean literally calling the
|
||||
> FORTH word — it means the same Bresenham/scanline approach at the C level, which is what
|
||||
> this implementation does (no FORTH call from `ttf.c`).
|
||||
>
|
||||
> **Fill rule: even-odd, not TrueType's native nonzero winding.** Simpler to implement (no
|
||||
> edge-direction bookkeeping) and identical to nonzero winding for the v1 glyph repertoire
|
||||
> (§27.6.4), whose contours are simple and properly nested (outer contour + inner counters,
|
||||
> e.g. `a`/`o`) — the two rules only diverge on self-intersecting outlines, which no v1
|
||||
> glyph has. Not correct in general for an arbitrary font; whoever needs that generality
|
||||
> later should switch to nonzero winding (requires tracking edge direction, not otherwise
|
||||
> hard).
|
||||
>
|
||||
> **Local signed Q48.16 multiply (`q48_smul`), not the shared `q48_mul`.** Rasterization
|
||||
> scales outline coordinates (routinely negative — e.g. `a`'s yMin is −10) by a
|
||||
> non-negative scale/Bezier-blend weight, exactly the signed use case `q48_mul`/`q48_div`
|
||||
> (`src/starkernel/math/q48_16.c`) don't support (see 4.3.7a's completion note). Per this
|
||||
> repo's rule against modifying a shared/tested module without being asked, that gap is
|
||||
> reported there, not patched here; `q48_smul` is local to `ttf.c`, a plain
|
||||
> `((int64_t)a * (int64_t)b) >> 16`, safe because every value in this module (glyph
|
||||
> coordinates scaled to at most a few hundred pixels) stays far inside `int64_t` range.
|
||||
> Scanline edge-intersection division is done as plain `int64_t` arithmetic directly
|
||||
> (`x0 + dx*t_num/dy`), not through any Q48.16 divide helper — C's native signed integer
|
||||
> division already handles the sign correctly there, so no wrapper was needed for that part.
|
||||
>
|
||||
> **Verification.** Host-side (`tools/ttftest.c`, extended): `test_rasterize()` rasterizes
|
||||
> `A`/`.`/`a` at 28px into a small bitmap and prints an ASCII-art dump plus structural
|
||||
> checks (non-empty, not implausibly full, background corner untouched). All three glyphs
|
||||
> render as visually correct, recognizable letterforms; all checks pass. No independent
|
||||
> reference rasterizer exists to diff pixel-for-pixel against (unlike 4.3.7/4.3.7a, which
|
||||
> had a from-scratch Python decoder) — the item's own acceptance bar is "recognizable," not
|
||||
> pixel-exact, so this is judged sufficient.
|
||||
>
|
||||
> **Live screendump, amd64, per this item's explicit requirement (the only 4.3.7–4.3.7b
|
||||
> items so far to need one).** A throwaway registered word, `TTF-PROBE` (added to
|
||||
> `src/word_source/framebuffer_words.c`, `__STARKERNEL__`-gated), loaded the font capsule,
|
||||
> rasterized `'A'` at 48px, and blitted it via `fb_put_pixel()` at screen position
|
||||
> (100,100). Boot: custom one-off script (not a repo file — mirrors
|
||||
> `scripts/qemu_screenshot.sh`'s monitor-socket + `socat` + HMP `screendump` pattern, plus
|
||||
> a serial-socket command injection channel that script doesn't have, same injection
|
||||
> technique used throughout 4.3.x) built the amd64 ISO, booted to `[Hera] ok>`, injected
|
||||
> `TTF-PROBE` over the serial socket, confirmed `TTF-PROBE: rasterized 'A' at (100,100)` in
|
||||
> the serial log, then issued `screendump` over the monitor socket. Result: a clearly
|
||||
> legible white `'A'` at the expected screen position, committed as
|
||||
> `fb/amd64/ttf-rasterize-glyph-A-20260810-221922.png`. Probe reverted immediately after
|
||||
> (`framebuffer_words.c` back to its pre-probe content, confirmed via `git diff`) — per
|
||||
> feedback-revert-probes-after-capture, only the permanent rasterizer in `ttf.c`/`ttf.h`
|
||||
> survives.
|
||||
>
|
||||
> **Compile-checked on all three architectures** (`make -f Makefile.starkernel ARCH=<arch>
|
||||
> clean` then plain build, no `qemu`), zero warnings from `ttf.c` on any — this item's own
|
||||
> "done when" only requires the amd64 screendump, not a three-arch boot (that's 4.3.7f's
|
||||
> job), but `ttf.c` builds into all three via the `hal/*.c` wildcard so a portability
|
||||
> compile check costs little and catches real bugs early.
|
||||
|
||||
- [ ] **4.3.7d — Glyph raster cache.** Rasterizing on every draw call is too slow for
|
||||
repeated text; cache rasterized bitmaps keyed by (font, codepoint, size). *Done when:*
|
||||
|
||||
Reference in New Issue
Block a user