/* StarForth — Steady-State Virtual Machine Runtime Copyright (c) 2023–2025 Robert A. James All rights reserved. This file is part of the StarForth project. Licensed under the StarForth License, Version 1.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at: https://github.com/star.4th@proton.me/StarForth/LICENSE.txt This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and noninfringement. See the License for the specific language governing permissions and limitations under the License. */ /** * ttf.h - TrueType font parser core (Freestanding) * * FABRIC.md item 4.3.7. Reads a TTF's sfnt directory plus head/maxp/loca/ * glyf/cmap tables, resolving a Unicode codepoint to a glyph index and its * outline header (contour count, bounding box). Does NOT extract outline * points or rasterize — that is 4.3.7a/4.3.7c. No floating point; all * fields read here are raw integers straight from the font's own * big-endian on-disk format (see FABRIC.md §27.7 decision #2 for why the * Q48.16-vs-float call was made, and why it doesn't bind this file, which * never scales anything). * * cmap: only format 4 (Windows/Unicode BMP) subtables are resolved. This * covers ASCII and all of the BMP, which is what the v1 glyph repertoire * (§27.6.4) needs. Format 12 (supplementary planes) is deferred — no * v1 glyph requires it. */ #ifndef STARKERNEL_TTF_H #define STARKERNEL_TTF_H #include #include #include "q48_16.h" #ifdef __cplusplus extern "C" { #endif #define TTF_OK 0 #define TTF_ERR_BAD_SFNT -1 #define TTF_ERR_TABLE_MISSING -2 #define TTF_ERR_BAD_TABLE -3 #define TTF_ERR_BAD_GLYPH_INDEX -4 #define TTF_ERR_OUT_OF_BOUNDS -5 #define TTF_ERR_TOO_MANY_POINTS -6 #define TTF_ERR_TOO_MANY_CONTOURS -7 #define TTF_ERR_TOO_DEEP -8 /* composite glyph nesting exceeded TTF_MAX_COMPOSITE_DEPTH */ #define TTF_ERR_UNSUPPORTED -9 /* e.g. a non-identity composite transform or point-matched * component args — see ttf.c's file header comment; not a * malformed font, just a code path this parser doesn't * implement yet */ /* Recursion guard for nested composite glyphs (a component referencing a * component). The TTF spec doesn't hard-cap this; this is a defensive * limit for freestanding/kernel-stack safety. */ #define TTF_MAX_COMPOSITE_DEPTH 8 /** Glyph index returned for "no mapping" by ttf_codepoint_to_glyph(). */ #define TTF_GLYPH_MISSING 0 /** * Parsed font handle. Borrows the caller's buffer (does not copy or own * it) — the buffer must outlive the ttf_font_t. */ typedef struct { const uint8_t *data; uint32_t size; uint32_t head_off; uint32_t maxp_off; uint32_t loca_off; uint32_t loca_len; uint32_t glyf_off; uint32_t glyf_len; uint16_t units_per_em; int16_t index_to_loc_format; /* 0 = Offset16 (x2), 1 = Offset32 */ uint16_t num_glyphs; /* 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. */ typedef struct { int16_t num_contours; /* >= 0 simple glyph, < 0 composite glyph */ int16_t x_min; int16_t y_min; int16_t x_max; int16_t y_max; uint32_t glyf_offset; /* absolute file offset of this glyph's record */ uint32_t glyf_length; /* bytes; 0 for an empty glyph (e.g. space) */ } ttf_glyph_header_t; /** * ttf_parse - Locate and validate the sfnt directory and the head/maxp/ * loca/glyf tables (cmap is optional; ttf_codepoint_to_glyph() fails * cleanly if absent). Does not copy `data` — `out` borrows it. * * @param data Whole .ttf file contents * @param size Length of data in bytes * @param out Parsed handle to populate * @return TTF_OK, or a TTF_ERR_* code */ int ttf_parse(const uint8_t *data, uint32_t size, ttf_font_t *out); /** * ttf_codepoint_to_glyph - Resolve a Unicode codepoint via the font's * format-4 cmap subtable. * * @return glyph index, or TTF_GLYPH_MISSING if unmapped or no cmap */ uint32_t ttf_codepoint_to_glyph(const ttf_font_t *font, uint32_t codepoint); /** * ttf_glyph_header - Read a glyph's outline header (contour count, * bounding box) via loca + glyf. Does not extract contour points. * * @param glyph_index As returned by ttf_codepoint_to_glyph() * @param out Header to populate * @return TTF_OK, or a TTF_ERR_* code */ 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 * correct for q48_add/q48_sub and for q48_from_u64-style left-shift * conversion; this module never calls q48_mul/q48_div on outline * coordinates (see ttf.c's file header comment for why). */ typedef struct { q48_16_t x, y; uint8_t on_curve; } ttf_point_t; /** Simple- and composite-glyph outline, flattened to one point list plus * per-contour end indices (TrueType convention: contour_ends[c] is the * index of the LAST point of contour c, inclusive; points are shared * across contours only in the sense that contour c+1 starts right after * contour_ends[c]). Caller supplies both backing arrays — this module * never allocates. */ typedef struct { ttf_point_t *points; uint32_t max_points; uint32_t point_count; uint16_t *contour_ends; uint32_t max_contours; uint32_t contour_count; } ttf_outline_t; /** * ttf_glyph_outline - Extract a glyph's outline (simple or composite, * recursively resolving composite components) into caller-supplied * buffers. * * Composite components with a non-identity transform (any scale/rotation/ * skew, i.e. anything but a pure (dx,dy) translation) or with * point-matched (rather than xy-offset) placement args return * TTF_ERR_UNSUPPORTED rather than silently producing a wrong outline — * see ttf.c's file header comment for why, and check that limitation * before relying on this for an arbitrary font. * * @return TTF_OK, or a TTF_ERR_* code (including TTF_ERR_TOO_MANY_POINTS/ * _CONTOURS if a caller buffer is too small, and TTF_ERR_TOO_DEEP * if composite nesting exceeds TTF_MAX_COMPOSITE_DEPTH) */ int ttf_glyph_outline(const ttf_font_t *font, uint32_t glyph_index, ttf_outline_t *out); /** A caller-owned 8-bit-per-pixel bitmap. `ttf_rasterize_glyph()` writes * `fill_value` into covered pixels and leaves everything else untouched * (it does not clear the buffer first — caller's job, so repeated * rasterization into the same bitmap, e.g. for a text run, composites * correctly without an extra clear between glyphs). */ typedef struct { uint8_t *pixels; uint32_t width; uint32_t height; } ttf_bitmap_t; /** * ttf_rasterize_glyph - Flatten a glyph's outline (quadratic Bezier * contours, fixed segment count per curve — see ttf.c's file header * comment) and fill it into `out` using the even-odd rule (FABRIC.md item * 4.3.7c; see that item's completion note for why even-odd rather than * nonzero winding — correct for the v1 glyph repertoire's non-self- * intersecting nested contours, not necessarily for an arbitrary font). * No antialiasing (explicitly deferred, per 4.3.7c's own "done when" * clause). Never allocates — flattening uses a fixed-size local buffer * bounded by TTF_RASTER_MAX_POINTS/TTF_RASTER_MAX_CONTOURS. * * @param scale Font-design-units-to-pixels scale, Q48.16, e.g. * q48_div(q48_from_u64(size_px), q48_from_u64(font->units_per_em)) * @param origin_x Pixel-space X (Q48.16) of the glyph's (0,0) font origin * within `out` * @param origin_y Pixel-space Y (Q48.16) of the glyph's baseline (font * y=0) within `out`; font Y-up is flipped to raster * Y-down internally * @return TTF_OK, or a TTF_ERR_* code (including TTF_ERR_TOO_MANY_POINTS/ * _CONTOURS if the flattened outline exceeds the local buffer) */ int ttf_rasterize_glyph(const ttf_font_t *font, uint32_t glyph_index, q48_16_t scale, q48_16_t origin_x, q48_16_t origin_y, uint8_t fill_value, ttf_bitmap_t *out); /* Glyph raster cache (FABRIC.md item 4.3.7d). Fixed-size, caller-owned * slot array -- no allocation, same convention as the rest of this * module. Every cached bitmap is a fixed TTF_CACHE_BITMAP_DIM square, * rasterized with the fixed origin (TTF_CACHE_MARGIN, * size_px + TTF_CACHE_MARGIN) -- i.e. glyph (0,0)/baseline sits at that * pixel within the bitmap on every cache entry, not just fitted to each * glyph's own bounding box. Callers positioning text (4.3.7e) need to * know this fixed convention. */ #define TTF_CACHE_MAX_SIZE_PX 64 #define TTF_CACHE_MARGIN 8 #define TTF_CACHE_BITMAP_DIM (TTF_CACHE_MAX_SIZE_PX + TTF_CACHE_MARGIN * 2) #define TTF_CACHE_BITMAP_BYTES (TTF_CACHE_BITMAP_DIM * TTF_CACHE_BITMAP_DIM) typedef struct { int valid; const ttf_font_t *font; uint32_t codepoint; uint32_t size_px; uint32_t width, height; uint32_t hits; /* incremented on every cache hit; 4.3.7d's own * "measurable" verification reads this. */ uint8_t pixels[TTF_CACHE_BITMAP_BYTES]; } ttf_raster_cache_slot_t; typedef struct { ttf_raster_cache_slot_t *slots; uint32_t slot_count; uint32_t evict_next; /* round-robin index used once every slot is full */ } ttf_raster_cache_t; /** ttf_raster_cache_init - Bind a caller-supplied slot array to `cache` * and mark every slot empty. */ void ttf_raster_cache_init(ttf_raster_cache_t *cache, ttf_raster_cache_slot_t *slots, uint32_t slot_count); /** * ttf_raster_cache_get - Look up (font, codepoint, size_px); on a miss, * rasterize and insert (evicting round-robin if every slot is full). * `out` borrows the winning slot's buffer directly -- valid until that * slot is evicted by a later call. * * @param was_hit If non-NULL, set to 1 on a cache hit, 0 if this call * rasterized and inserted * @return TTF_OK, TTF_ERR_UNSUPPORTED if size_px > TTF_CACHE_MAX_SIZE_PX, * or a ttf_rasterize_glyph() TTF_ERR_* code on a miss that failed * to rasterize */ int ttf_raster_cache_get(ttf_raster_cache_t *cache, const ttf_font_t *font, uint32_t codepoint, uint32_t size_px, ttf_bitmap_t *out, int *was_hit); #ifdef __STARKERNEL__ #include "capsule.h" /** * ttf_load_from_capsule - Resolve a font capsule by name and parse it, * zero-copy (FABRIC.md item 4.3.7b). `out` borrows the capsule payload * directly from `arena` — no kmalloc, no decode step, since the capsule * is already a raw-byte match of the source `.ttf` (see * capsules/fonts/README.md and FABRIC.md §27.7's 2026-08-10 correction: * capsule storage needs no hex/base64 text-encoding, `tools/mkcapsule.c` * already embeds arbitrary files as raw bytes). Validates the capsule's * content hash (`capsule_validate(..., verify_hash=1)`) before parsing. * * @param capsule_name Colon-separated capsule name, e.g. * "fonts:JetBrainsMono-Regular.ttf" * @return TTF_OK, TTF_ERR_TABLE_MISSING if the capsule isn't found, or a * capsule-validation/ttf_parse TTF_ERR_* code */ int ttf_load_from_capsule( const CapsuleDirHeader *dir, const CapsuleDesc *descs, const CapsuleNameEntry *names, const uint8_t *arena, const char *capsule_name, ttf_font_t *out); #endif /* __STARKERNEL__ */ #ifdef __cplusplus } #endif #endif /* STARKERNEL_TTF_H */