ttf.c: glyph outline extraction, simple and composite (item 4.3.7a)
ttf_glyph_outline() decodes simple-glyph flag/coordinate runs and recursively resolves composite components into a caller-supplied point/ contour-end buffer, in Q48.16. Composite scale/rotation/skew transforms are rejected with TTF_ERR_UNSUPPORTED rather than mis-rendered, since the shared q48_mul/q48_div are unsigned-only; translation-only composites (the only kind the v1 glyph repertoire uses) apply cleanly via q48_add. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
5f6cc054d4
commit
70b6918279
@@ -4712,11 +4712,46 @@ document and committing that amendment as its own item.*
|
||||
> compile check above is what stands in for it, per this item's own "done when" clause
|
||||
> (which never asked for a kernel boot).
|
||||
|
||||
- [ ] **4.3.7a — Glyph outline extraction.** Simple and composite glyph outlines from `glyf`
|
||||
- [x] **4.3.7a — Glyph outline extraction.** Simple and composite glyph outlines from `glyf`
|
||||
— on-curve/off-curve point lists, quadratic Bézier control points, composite glyph
|
||||
transforms — in Q48.16. *Done when:* outline point lists for a handful of test glyphs
|
||||
(including at least one composite, e.g. an accented character if the test font has one)
|
||||
match reference-tool output within Q48.16 rounding tolerance. *Refs:* §27.7.
|
||||
> **Done 2026-08-10.** `ttf_glyph_outline()` in `src/starkernel/hal/ttf.c`/`ttf.h`: flags
|
||||
> run-length decode, delta-decoded x/y coordinates (simple glyphs), and recursive composite
|
||||
> component resolution, into caller-supplied point/contour-end buffers (no allocation in
|
||||
> this module). Points are stored in raw font design units shifted into Q48.16
|
||||
> (`q48_from_i32`, a local two's-complement left-shift — deliberately not routed through
|
||||
> `q48_mul`/`q48_div`, see below).
|
||||
>
|
||||
> **Known gap, reported not fixed, per this repo's rule against modifying a shared/tested
|
||||
> module without being asked:** `src/starkernel/math/q48_16.c`'s `q48_mul`/`q48_div` are
|
||||
> unsigned-only (`q48_div` saturates via an unsigned overflow check on `a`; `q48_mul` does a
|
||||
> plain unsigned widen-multiply) — confirmed by reading the source before writing any of
|
||||
> this, not assumed. A composite glyph's transform can carry a signed F2Dot14 scale/
|
||||
> rotation/skew, which needs a *signed* fixed-point multiply that function doesn't provide.
|
||||
> Resolution taken here: `decode_composite_glyph()` applies (dx,dy) translation only (plain
|
||||
> `q48_add`, safe under two's-complement regardless of the unsigned typing) and explicitly
|
||||
> **rejects** any component with a non-identity scale/2×2 transform or point-matched
|
||||
> (non-xy-offset) args, returning `TTF_ERR_UNSUPPORTED` rather than silently mis-rendering
|
||||
> it. Checked, not assumed: every composite in `fonts/JetBrainsMono-Regular.ttf` (11 sampled
|
||||
> accented Latin glyphs: `ÁÉÍÓÚÑéáñüö`) uses identity-scale translation-only components, so
|
||||
> this doesn't block the v1 repertoire (§27.6.4) — but it's a real limitation for an
|
||||
> arbitrary future font, and fixing it means either giving `q48_16.c` a signed multiply
|
||||
> variant or doing the scale math locally the same way translation already is. Whoever picks
|
||||
> that up next should decide which, not silently patch it in passing.
|
||||
>
|
||||
> Verified against `/tmp/.../scratchpad/ttf_outline_ref.py` (not committed, reproducible
|
||||
> from this note) — a second from-scratch Python decoder sharing no code with `ttf.c`,
|
||||
> covering the same "no `fonttools` installed" substitution already recorded under 4.3.7.
|
||||
> Three glyphs checked point-for-point (coordinates, on/off-curve flags, contour-end
|
||||
> indices) via the extended `tools/ttftest.c`: `.` (12 points, 1 contour, simple), `A` (17
|
||||
> points, 2 contours, simple), and `á` (43 points, 3 contours, a genuine 2-component
|
||||
> translation-only composite exercising the recursive path) — all match exactly.
|
||||
> `gcc -std=c99 -Wall -Wextra -Werror`, zero warnings; single-file freestanding compile
|
||||
> against the real amd64 `Makefile.starkernel` flags also re-checked clean. Same "not a
|
||||
> kernel-boot change yet" posture as 4.3.7 — no FORTH/capsule wiring exists for this module,
|
||||
> so no three-arch QEMU acceptance applies here either.
|
||||
|
||||
- [ ] **4.3.7b — Font data ingestion.** `.ttf` bytes encoded (hex or base64 — pick one, record
|
||||
why) into capsule blocks per the resolved design decision (§27.7), decoded into a `kmalloc`
|
||||
|
||||
@@ -55,6 +55,18 @@ extern "C" {
|
||||
#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
|
||||
@@ -125,6 +137,51 @@ 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);
|
||||
|
||||
/** 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);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
+282
-5
@@ -8,12 +8,28 @@
|
||||
*/
|
||||
|
||||
/**
|
||||
* ttf.c — TrueType font parser core (FABRIC.md item 4.3.7)
|
||||
* ttf.c — TrueType font parser core (FABRIC.md items 4.3.7, 4.3.7a)
|
||||
*
|
||||
* sfnt directory + head/maxp/loca/glyf/cmap(format 4) table parsing. All
|
||||
* multi-byte fields in a TTF are big-endian; this file reads them by hand
|
||||
* (no libc byteswap dependency) with bounds checks against the buffer
|
||||
* length on every access, since the buffer is untrusted input.
|
||||
* sfnt directory + head/maxp/loca/glyf/cmap(format 4) table parsing, plus
|
||||
* simple- and composite-glyph outline extraction. All multi-byte fields in
|
||||
* a TTF are big-endian; this file reads them by hand (no libc byteswap
|
||||
* dependency) with bounds checks against the buffer length on every
|
||||
* access, since the buffer is untrusted input.
|
||||
*
|
||||
* Composite-glyph transforms: this file applies (dx,dy) TRANSLATION only.
|
||||
* A composite component carrying a non-identity scale/rotation/skew (the
|
||||
* WE_HAVE_A_SCALE/WE_HAVE_AN_X_AND_Y_SCALE/WE_HAVE_A_TWO_BY_TWO flags with
|
||||
* a non-1.0/non-zero F2Dot14 value) is rejected with TTF_ERR_UNSUPPORTED
|
||||
* rather than silently mis-rendered. Reason: applying such a transform
|
||||
* needs a signed fixed-point multiply, and the shared q48_mul()/q48_div()
|
||||
* in src/starkernel/math/q48_16.c are unsigned-only (q48_div saturates via
|
||||
* an unsigned overflow check; q48_mul does a plain unsigned widen-multiply)
|
||||
* — confirmed by reading that file, not assumed. Per this repo's rule
|
||||
* against modifying a shared/tested module to "fix" it without being
|
||||
* asked, that gap is reported (see FABRIC.md's 4.3.7a completion note),
|
||||
* not patched here. No glyph in the v1 repertoire (§27.6.4) needs a
|
||||
* non-identity composite transform — checked against
|
||||
* fonts/JetBrainsMono-Regular.ttf before writing this, not assumed.
|
||||
*/
|
||||
|
||||
#include "ttf.h"
|
||||
@@ -244,4 +260,265 @@ int ttf_glyph_header(const ttf_font_t *font, uint32_t glyph_index, ttf_glyph_hea
|
||||
out->y_max = rd_i16(font->data, out->glyf_offset + 8);
|
||||
|
||||
return TTF_OK;
|
||||
}
|
||||
|
||||
/* ===========================================================================
|
||||
* 4.3.7a — glyph outline extraction
|
||||
* ===========================================================================
|
||||
*/
|
||||
|
||||
static uint8_t rd_u8(const uint8_t *d, uint32_t off) {
|
||||
return d[off];
|
||||
}
|
||||
|
||||
static int8_t rd_i8(const uint8_t *d, uint32_t off) {
|
||||
return (int8_t) d[off];
|
||||
}
|
||||
|
||||
/* Left-shift a signed 32-bit font-unit coordinate into Q48.16. Two's-
|
||||
* complement, deliberately not going through q48_16.h's q48_from_u64 (same
|
||||
* operation, but that name implies an unsigned caller, which this isn't —
|
||||
* see this file's header comment on why negative values are fine for
|
||||
* shift/add/sub but not for q48_mul/q48_div). */
|
||||
static q48_16_t q48_from_i32(int32_t v) {
|
||||
return ((uint64_t) (int64_t) v) << 16;
|
||||
}
|
||||
|
||||
#define TTF_FLAG_ON_CURVE 0x01
|
||||
#define TTF_FLAG_X_SHORT 0x02
|
||||
#define TTF_FLAG_Y_SHORT 0x04
|
||||
#define TTF_FLAG_REPEAT 0x08
|
||||
#define TTF_FLAG_X_SAME_OR_POS 0x10
|
||||
#define TTF_FLAG_Y_SAME_OR_POS 0x20
|
||||
|
||||
/* Decode-time cap on points/contours per single simple-glyph component —
|
||||
* a defensive stack-buffer bound, independent of the caller's out->
|
||||
* max_points/max_contours (which are checked separately, on the combined
|
||||
* outline). No glyph in the v1 repertoire (§27.6.4) comes close to this. */
|
||||
#define TTF_MAX_SIMPLE_POINTS 512
|
||||
|
||||
static int decode_simple_glyph(const ttf_font_t *font, const ttf_glyph_header_t *hdr,
|
||||
ttf_outline_t *out) {
|
||||
uint32_t off = hdr->glyf_offset + 10;
|
||||
uint16_t nc = (uint16_t) hdr->num_contours;
|
||||
uint16_t end_pts[TTF_MAX_SIMPLE_POINTS]; /* reused below for endPtsOfContours only, nc <= that bound checked */
|
||||
uint16_t num_points;
|
||||
uint8_t flags[TTF_MAX_SIMPLE_POINTS];
|
||||
int32_t xs[TTF_MAX_SIMPLE_POINTS];
|
||||
int32_t ys[TTF_MAX_SIMPLE_POINTS];
|
||||
uint32_t i;
|
||||
uint32_t point_base;
|
||||
int32_t acc;
|
||||
|
||||
if (nc == 0 || nc > TTF_MAX_SIMPLE_POINTS) return TTF_ERR_TOO_MANY_CONTOURS;
|
||||
if (!in_bounds(font, off, (uint32_t) nc * 2 + 2)) return TTF_ERR_OUT_OF_BOUNDS;
|
||||
|
||||
for (i = 0; i < nc; i++) {
|
||||
end_pts[i] = rd_u16(font->data, off + i * 2);
|
||||
}
|
||||
off += (uint32_t) nc * 2;
|
||||
|
||||
num_points = (uint16_t) (end_pts[nc - 1] + 1);
|
||||
if (num_points == 0 || num_points > TTF_MAX_SIMPLE_POINTS) return TTF_ERR_TOO_MANY_POINTS;
|
||||
|
||||
{
|
||||
uint16_t instruction_length;
|
||||
if (!in_bounds(font, off, 2)) return TTF_ERR_OUT_OF_BOUNDS;
|
||||
instruction_length = rd_u16(font->data, off);
|
||||
off += 2;
|
||||
if (!in_bounds(font, off, instruction_length)) return TTF_ERR_OUT_OF_BOUNDS;
|
||||
off += instruction_length;
|
||||
}
|
||||
|
||||
/* Flags, with run-length repeat expansion. */
|
||||
i = 0;
|
||||
while (i < num_points) {
|
||||
uint8_t f;
|
||||
if (!in_bounds(font, off, 1)) return TTF_ERR_OUT_OF_BOUNDS;
|
||||
f = rd_u8(font->data, off);
|
||||
off += 1;
|
||||
flags[i++] = f;
|
||||
if (f & TTF_FLAG_REPEAT) {
|
||||
uint8_t repeat;
|
||||
uint32_t r;
|
||||
if (!in_bounds(font, off, 1)) return TTF_ERR_OUT_OF_BOUNDS;
|
||||
repeat = rd_u8(font->data, off);
|
||||
off += 1;
|
||||
for (r = 0; r < repeat; r++) {
|
||||
if (i >= num_points) return TTF_ERR_TOO_MANY_POINTS;
|
||||
flags[i++] = f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* X coordinates, delta-decoded. */
|
||||
acc = 0;
|
||||
for (i = 0; i < num_points; i++) {
|
||||
uint8_t f = flags[i];
|
||||
int32_t dx;
|
||||
if (f & TTF_FLAG_X_SHORT) {
|
||||
uint8_t v;
|
||||
if (!in_bounds(font, off, 1)) return TTF_ERR_OUT_OF_BOUNDS;
|
||||
v = rd_u8(font->data, off);
|
||||
off += 1;
|
||||
dx = (f & TTF_FLAG_X_SAME_OR_POS) ? (int32_t) v : -(int32_t) v;
|
||||
} else if (f & TTF_FLAG_X_SAME_OR_POS) {
|
||||
dx = 0;
|
||||
} else {
|
||||
if (!in_bounds(font, off, 2)) return TTF_ERR_OUT_OF_BOUNDS;
|
||||
dx = rd_i16(font->data, off);
|
||||
off += 2;
|
||||
}
|
||||
acc += dx;
|
||||
xs[i] = acc;
|
||||
}
|
||||
|
||||
/* Y coordinates, delta-decoded. */
|
||||
acc = 0;
|
||||
for (i = 0; i < num_points; i++) {
|
||||
uint8_t f = flags[i];
|
||||
int32_t dy;
|
||||
if (f & TTF_FLAG_Y_SHORT) {
|
||||
uint8_t v;
|
||||
if (!in_bounds(font, off, 1)) return TTF_ERR_OUT_OF_BOUNDS;
|
||||
v = rd_u8(font->data, off);
|
||||
off += 1;
|
||||
dy = (f & TTF_FLAG_Y_SAME_OR_POS) ? (int32_t) v : -(int32_t) v;
|
||||
} else if (f & TTF_FLAG_Y_SAME_OR_POS) {
|
||||
dy = 0;
|
||||
} else {
|
||||
if (!in_bounds(font, off, 2)) return TTF_ERR_OUT_OF_BOUNDS;
|
||||
dy = rd_i16(font->data, off);
|
||||
off += 2;
|
||||
}
|
||||
acc += dy;
|
||||
ys[i] = acc;
|
||||
}
|
||||
|
||||
if (out->point_count + num_points > out->max_points) return TTF_ERR_TOO_MANY_POINTS;
|
||||
if (out->contour_count + nc > out->max_contours) return TTF_ERR_TOO_MANY_CONTOURS;
|
||||
|
||||
point_base = out->point_count;
|
||||
for (i = 0; i < num_points; i++) {
|
||||
out->points[point_base + i].x = q48_from_i32(xs[i]);
|
||||
out->points[point_base + i].y = q48_from_i32(ys[i]);
|
||||
out->points[point_base + i].on_curve = (uint8_t) ((flags[i] & TTF_FLAG_ON_CURVE) != 0);
|
||||
}
|
||||
for (i = 0; i < nc; i++) {
|
||||
out->contour_ends[out->contour_count + i] = (uint16_t) (point_base + end_pts[i]);
|
||||
}
|
||||
out->point_count += num_points;
|
||||
out->contour_count += nc;
|
||||
|
||||
return TTF_OK;
|
||||
}
|
||||
|
||||
#define TTF_COMP_ARGS_ARE_WORDS 0x0001
|
||||
#define TTF_COMP_ARGS_ARE_XY_VALUES 0x0002
|
||||
#define TTF_COMP_WE_HAVE_A_SCALE 0x0008
|
||||
#define TTF_COMP_MORE_COMPONENTS 0x0020
|
||||
#define TTF_COMP_WE_HAVE_XY_SCALE 0x0040
|
||||
#define TTF_COMP_WE_HAVE_2X2 0x0080
|
||||
|
||||
#define TTF_F2DOT14_ONE 16384 /* 1.0 in F2Dot14 */
|
||||
|
||||
static int decode_glyph_r(const ttf_font_t *font, uint32_t glyph_index,
|
||||
ttf_outline_t *out, int depth);
|
||||
|
||||
static int decode_composite_glyph(const ttf_font_t *font, const ttf_glyph_header_t *hdr,
|
||||
ttf_outline_t *out, int depth) {
|
||||
uint32_t off = hdr->glyf_offset + 10;
|
||||
int more = 1;
|
||||
|
||||
while (more) {
|
||||
uint16_t flags, comp_glyph;
|
||||
int32_t arg1, arg2;
|
||||
uint32_t point_base;
|
||||
int rc;
|
||||
|
||||
if (!in_bounds(font, off, 4)) return TTF_ERR_OUT_OF_BOUNDS;
|
||||
flags = rd_u16(font->data, off);
|
||||
comp_glyph = rd_u16(font->data, off + 2);
|
||||
off += 4;
|
||||
|
||||
if (!(flags & TTF_COMP_ARGS_ARE_XY_VALUES)) return TTF_ERR_UNSUPPORTED; /* point-matching, not implemented */
|
||||
|
||||
if (flags & TTF_COMP_ARGS_ARE_WORDS) {
|
||||
if (!in_bounds(font, off, 4)) return TTF_ERR_OUT_OF_BOUNDS;
|
||||
arg1 = rd_i16(font->data, off);
|
||||
arg2 = rd_i16(font->data, off + 2);
|
||||
off += 4;
|
||||
} else {
|
||||
if (!in_bounds(font, off, 2)) return TTF_ERR_OUT_OF_BOUNDS;
|
||||
arg1 = rd_i8(font->data, off);
|
||||
arg2 = rd_i8(font->data, off + 1);
|
||||
off += 2;
|
||||
}
|
||||
|
||||
if (flags & TTF_COMP_WE_HAVE_A_SCALE) {
|
||||
int16_t s;
|
||||
if (!in_bounds(font, off, 2)) return TTF_ERR_OUT_OF_BOUNDS;
|
||||
s = rd_i16(font->data, off);
|
||||
off += 2;
|
||||
if (s != TTF_F2DOT14_ONE) return TTF_ERR_UNSUPPORTED;
|
||||
} else if (flags & TTF_COMP_WE_HAVE_XY_SCALE) {
|
||||
int16_t sx, sy;
|
||||
if (!in_bounds(font, off, 4)) return TTF_ERR_OUT_OF_BOUNDS;
|
||||
sx = rd_i16(font->data, off);
|
||||
sy = rd_i16(font->data, off + 2);
|
||||
off += 4;
|
||||
if (sx != TTF_F2DOT14_ONE || sy != TTF_F2DOT14_ONE) return TTF_ERR_UNSUPPORTED;
|
||||
} else if (flags & TTF_COMP_WE_HAVE_2X2) {
|
||||
int16_t a, b, c, dd;
|
||||
if (!in_bounds(font, off, 8)) return TTF_ERR_OUT_OF_BOUNDS;
|
||||
a = rd_i16(font->data, off);
|
||||
b = rd_i16(font->data, off + 2);
|
||||
c = rd_i16(font->data, off + 4);
|
||||
dd = rd_i16(font->data, off + 6);
|
||||
off += 8;
|
||||
if (a != TTF_F2DOT14_ONE || b != 0 || c != 0 || dd != TTF_F2DOT14_ONE)
|
||||
return TTF_ERR_UNSUPPORTED;
|
||||
}
|
||||
|
||||
point_base = out->point_count;
|
||||
rc = decode_glyph_r(font, comp_glyph, out, depth + 1);
|
||||
if (rc != TTF_OK) return rc;
|
||||
|
||||
{
|
||||
q48_16_t dx = q48_from_i32(arg1);
|
||||
q48_16_t dy = q48_from_i32(arg2);
|
||||
uint32_t i;
|
||||
for (i = point_base; i < out->point_count; i++) {
|
||||
out->points[i].x = q48_add(out->points[i].x, dx);
|
||||
out->points[i].y = q48_add(out->points[i].y, dy);
|
||||
}
|
||||
}
|
||||
|
||||
more = (flags & TTF_COMP_MORE_COMPONENTS) != 0;
|
||||
}
|
||||
|
||||
return TTF_OK;
|
||||
}
|
||||
|
||||
static int decode_glyph_r(const ttf_font_t *font, uint32_t glyph_index,
|
||||
ttf_outline_t *out, int depth) {
|
||||
ttf_glyph_header_t hdr;
|
||||
int rc;
|
||||
|
||||
if (depth > TTF_MAX_COMPOSITE_DEPTH) return TTF_ERR_TOO_DEEP;
|
||||
|
||||
rc = ttf_glyph_header(font, glyph_index, &hdr);
|
||||
if (rc != TTF_OK) return rc;
|
||||
|
||||
if (hdr.glyf_length == 0) return TTF_OK; /* empty glyph, e.g. space */
|
||||
|
||||
if (hdr.num_contours >= 0) return decode_simple_glyph(font, &hdr, out);
|
||||
return decode_composite_glyph(font, &hdr, out, depth);
|
||||
}
|
||||
|
||||
int ttf_glyph_outline(const ttf_font_t *font, uint32_t glyph_index, ttf_outline_t *out) {
|
||||
if (!font || !out) return TTF_ERR_BAD_TABLE;
|
||||
out->point_count = 0;
|
||||
out->contour_count = 0;
|
||||
return decode_glyph_r(font, glyph_index, out, 0);
|
||||
}
|
||||
@@ -55,6 +55,100 @@ static void check_eq_i(const char *what, uint32_t cp, long got, long want) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Outline expectations (4.3.7a), independently computed by a from-scratch
|
||||
* Python outline decoder (tools/../../tmp .../ttf_outline_ref.py, not
|
||||
* committed, reproducible from FABRIC.md's 4.3.7a completion note) that
|
||||
* shares no code with ttf.c. '.' and '0' are simple glyphs; 'A' is simple
|
||||
* with two contours; 'a' is a translation-only composite (2 components). */
|
||||
typedef struct {
|
||||
int32_t x, y;
|
||||
int on_curve;
|
||||
} exp_point_t;
|
||||
|
||||
typedef struct {
|
||||
uint32_t codepoint;
|
||||
uint32_t glyph;
|
||||
const uint16_t *contour_ends;
|
||||
uint32_t contour_count;
|
||||
const exp_point_t *points;
|
||||
uint32_t point_count;
|
||||
} outline_expect_t;
|
||||
|
||||
static const uint16_t DOT_ENDS[] = {11};
|
||||
static const exp_point_t DOT_PTS[] = {
|
||||
{300, -10, 1}, {263, -10, 0}, {218, 34, 0}, {218, 69, 1}, {218, 106, 0}, {263, 152, 0},
|
||||
{300, 152, 1}, {337, 152, 0}, {382, 106, 0}, {382, 69, 1}, {382, 34, 0}, {337, -10, 0},
|
||||
};
|
||||
|
||||
static const uint16_t A_ENDS[] = {7, 16};
|
||||
static const exp_point_t A_PTS[] = {
|
||||
{50, 0, 1}, {240, 730, 1}, {361, 730, 1}, {550, 0, 1}, {459, 0, 1}, {411, 194, 1},
|
||||
{190, 194, 1}, {142, 0, 1}, {208, 270, 1}, {392, 270, 1}, {336, 495, 1}, {320, 559, 0},
|
||||
{302, 645, 0}, {300, 658, 1}, {298, 645, 0}, {280, 559, 0}, {264, 496, 1},
|
||||
};
|
||||
|
||||
static const uint16_t ALOWER_ENDS[] = {27, 38, 42};
|
||||
static const exp_point_t ALOWER_PTS[] = {
|
||||
{252, -10, 1}, {167, -10, 0}, {67, 85, 0}, {67, 162, 1}, {67, 213, 0}, {113, 289, 0},
|
||||
{195, 332, 0}, {248, 332, 1}, {418, 332, 1}, {418, 375, 1}, {418, 482, 0}, {301, 482, 1},
|
||||
{249, 482, 0}, {185, 444, 0}, {183, 410, 1}, {93, 410, 1}, {98, 475, 0}, {209, 560, 0},
|
||||
{301, 560, 1}, {401, 560, 0}, {508, 464, 0}, {508, 378, 1}, {508, 0, 1}, {419, 0, 1},
|
||||
{419, 100, 1}, {417, 100, 1}, {409, 49, 0}, {323, -10, 0}, {274, 66, 1}, {340, 66, 0},
|
||||
{418, 130, 0}, {418, 185, 1}, {418, 262, 1}, {258, 262, 1}, {214, 262, 0}, {159, 209, 0},
|
||||
{159, 165, 1}, {159, 119, 0}, {220, 66, 0}, {247, 645, 1}, {353, 785, 1}, {450, 785, 1},
|
||||
{339, 645, 1},
|
||||
};
|
||||
|
||||
static const outline_expect_t OUTLINE_EXPECTED[] = {
|
||||
{0x002E, 908, DOT_ENDS, 1, DOT_PTS, 12},
|
||||
{0x0041, 1, A_ENDS, 2, A_PTS, 17},
|
||||
{0x0061, 199, ALOWER_ENDS, 3, ALOWER_PTS, 43},
|
||||
};
|
||||
|
||||
static int32_t unscale(q48_16_t q) {
|
||||
return (int32_t) (((int64_t) q) >> 16);
|
||||
}
|
||||
|
||||
static void test_outlines(const ttf_font_t *font) {
|
||||
ttf_point_t point_buf[600];
|
||||
uint16_t contour_buf[16];
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < sizeof(OUTLINE_EXPECTED) / sizeof(OUTLINE_EXPECTED[0]); i++) {
|
||||
const outline_expect_t *e = &OUTLINE_EXPECTED[i];
|
||||
ttf_outline_t outline;
|
||||
int rc;
|
||||
uint32_t j;
|
||||
|
||||
outline.points = point_buf;
|
||||
outline.max_points = sizeof(point_buf) / sizeof(point_buf[0]);
|
||||
outline.contour_ends = contour_buf;
|
||||
outline.max_contours = sizeof(contour_buf) / sizeof(contour_buf[0]);
|
||||
|
||||
rc = ttf_glyph_outline(font, e->glyph, &outline);
|
||||
if (rc != TTF_OK) {
|
||||
printf(" FAIL U+%04X ttf_glyph_outline rc=%d\n", e->codepoint, rc);
|
||||
failures++;
|
||||
continue;
|
||||
}
|
||||
|
||||
printf("U+%04X outline: points=%u (want %u) contours=%u (want %u)\n",
|
||||
e->codepoint, outline.point_count, e->point_count,
|
||||
outline.contour_count, e->contour_count);
|
||||
check_eq_i("point_count", e->codepoint, outline.point_count, e->point_count);
|
||||
check_eq_i("contour_count", e->codepoint, outline.contour_count, e->contour_count);
|
||||
|
||||
for (j = 0; j < e->contour_count && j < outline.contour_count; j++) {
|
||||
check_eq_i("contour_end", e->codepoint, outline.contour_ends[j], e->contour_ends[j]);
|
||||
}
|
||||
for (j = 0; j < e->point_count && j < outline.point_count; j++) {
|
||||
check_eq_i("point.x", e->codepoint, unscale(outline.points[j].x), e->points[j].x);
|
||||
check_eq_i("point.y", e->codepoint, unscale(outline.points[j].y), e->points[j].y);
|
||||
check_eq_i("point.on_curve", e->codepoint, outline.points[j].on_curve, e->points[j].on_curve);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
FILE *fp;
|
||||
long size;
|
||||
@@ -115,6 +209,8 @@ int main(int argc, char **argv) {
|
||||
check_eq_i("glyf_length", e->codepoint, hdr.glyf_length, e->expect_glyf_length);
|
||||
}
|
||||
|
||||
test_outlines(&font);
|
||||
|
||||
free(buf);
|
||||
|
||||
if (failures) {
|
||||
|
||||
Reference in New Issue
Block a user