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
+28
View File
@@ -131,6 +131,24 @@ int ttf_parse(const uint8_t *data, uint32_t size, ttf_font_t *out) {
if (rc != TTF_OK) return rc;
if (!in_bounds(out, out->glyf_off, out->glyf_len)) return TTF_ERR_OUT_OF_BOUNDS;
/* hhea (for numberOfHMetrics) + hmtx (advance widths) -- mandatory,
* needed for ttf_glyph_advance_width() (4.3.7e's proportional
* spacing). numberOfHMetrics is hhea's 18th field, a fixed 36-byte
* table per spec. */
{
uint32_t hhea_off, hhea_len_unused, hmtx_len;
rc = find_table(data, size, num_tables, TAG('h', 'h', 'e', 'a'), &hhea_off, &hhea_len_unused);
if (rc != TTF_OK) return rc;
if (!in_bounds(out, hhea_off, 36)) return TTF_ERR_BAD_TABLE;
out->num_h_metrics = rd_u16(data, hhea_off + 34);
rc = find_table(data, size, num_tables, TAG('h', 'm', 't', 'x'), &out->hmtx_off, &hmtx_len);
if (rc != TTF_OK) return rc;
if (out->num_h_metrics == 0
|| !in_bounds(out, out->hmtx_off, (uint32_t) out->num_h_metrics * 4))
return TTF_ERR_BAD_TABLE;
}
/* cmap is optional to overall parse success; codepoint lookup just
* fails cleanly (TTF_GLYPH_MISSING) if it's absent or unrecognized. */
{
@@ -262,6 +280,16 @@ int ttf_glyph_header(const ttf_font_t *font, uint32_t glyph_index, ttf_glyph_hea
return TTF_OK;
}
uint16_t ttf_glyph_advance_width(const ttf_font_t *font, uint32_t glyph_index) {
uint32_t idx, off;
if (!font || font->num_h_metrics == 0) return 0;
idx = glyph_index < font->num_h_metrics ? glyph_index : (uint32_t) font->num_h_metrics - 1;
off = font->hmtx_off + idx * 4;
if (!in_bounds(font, off, 2)) return 0;
return rd_u16(font->data, off);
}
/* ===========================================================================
* 4.3.7a — glyph outline extraction
* ===========================================================================