Punch list §25 item 4.3.7d complete. ttf_raster_cache_get() looks up (font, codepoint, size_px) in a caller-owned fixed slot array, evicting round-robin once full, rasterizing into a slot on a miss. Verified live in tools/ttftest.c: an identical (font, 'A', 24px) call made twice returns was_hit=0 then was_hit=1, and the slot's own hits counter reads exactly 1 afterward -- checked programmatically. A different-codepoint call misses again, proving the key actually discriminates. Wall-clock timing (miss 0.040ms vs hit 0.001ms) is printed as informational corroboration only, not the load-bearing check. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
303 lines
12 KiB
C
303 lines
12 KiB
C
/*
|
||
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 <stdint.h>
|
||
#include <stddef.h>
|
||
#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;
|
||
} 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);
|
||
|
||
/** 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 */ |