ttf_words.c: TTF-TEXT entry point, hmtx advance widths (item 4.3.7e)

Punch list §25 item 4.3.7e complete. New src/word_source/ttf_words.c
registers TTF-TEXT ( c-addr u x y size color -- ): lazily loads the v1
default font capsule + raster cache once, decodes UTF-8 (C
reimplementation mirroring capsules/fabric.4th's DECODE-UTF8 exactly),
looks up each glyph's cached bitmap, blits via fb_put_pixel, advances
the pen by the glyph's real hmtx advance width scaled to pixels.

Necessary plumbing: ttf_parse() now also locates hhea/hmtx, and
ttf_glyph_advance_width() reads a glyph's advance width -- required for
this item's own "proportional spacing correct" acceptance clause, no
advance-width data existed anywhere else in the parser. Verified in
tools/ttftest.c: A/a/0/space all read advance_width=600, correctly
uniform since JetBrainsMono-Regular.ttf is monospace.

(x,y) is raster pixel space (top-left origin, Y-down), deliberately not
the stroke font TEXT's Cartesian Y-up convention -- recorded explicitly
in ttf_words.h, not conflated.

Verified live, amd64, screendump: injected
S" Hi 4.3.7e!" 200 200 28 16777215 TTF-TEXT over a serial socket after
boot, no error, captured a screendump showing the string rendered
legibly with correct mixed-case/digit/punctuation glyphs and even
spacing. TTF-TEXT is this item's permanent deliverable, not a
throwaway probe. Compile-checked clean on all three architectures.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Robert Allan James
2026-08-10 23:11:40 -04:00
co-authored by Claude Sonnet 5
parent 2db4603d04
commit 715ebcc57e
8 changed files with 319 additions and 2 deletions
+19
View File
@@ -93,6 +93,12 @@ typedef struct {
/* Selected cmap subtable (format 4 only, see file header comment). */
uint32_t cmap_subtable_off;
int has_cmap;
/* hhea/hmtx, for ttf_glyph_advance_width() -- proportional spacing
* (FABRIC.md item 4.3.7e). Mandatory tables per the TrueType spec,
* so their absence fails ttf_parse() same as head/maxp/loca/glyf. */
uint32_t hmtx_off;
uint16_t num_h_metrics;
} ttf_font_t;
/** Raw glyf record header, per §27.6/4.3.7's "done when" clause. */
@@ -137,6 +143,19 @@ uint32_t ttf_codepoint_to_glyph(const ttf_font_t *font, uint32_t codepoint);
int ttf_glyph_header(const ttf_font_t *font, uint32_t glyph_index,
ttf_glyph_header_t *out);
/**
* ttf_glyph_advance_width - Horizontal advance width via hmtx, in raw
* font design units (NOT scaled by unitsPerEm -- caller's job, e.g.
* `q48_mul(q48_from_u64(width), scale)`; always non-negative, so the
* shared q48_mul is fine here unlike the signed cases documented in
* ttf.c's rasterizer). Glyphs past hmtx's numberOfHMetrics entries share
* the last entry's width, per spec.
*
* @param glyph_index As returned by ttf_codepoint_to_glyph()
* @return advance width, or 0 if glyph_index is out of range
*/
uint16_t ttf_glyph_advance_width(const ttf_font_t *font, uint32_t glyph_index);
/** One outline point, in raw font design units (NOT scaled by unitsPerEm —
* that's the caller's job, same convention as ttf_glyph_header_t's bbox),
* expressed in Q48.16. Two's-complement negative values are expected and