diff --git a/FABRIC.md b/FABRIC.md index 6416a52..0319c69 100644 --- a/FABRIC.md +++ b/FABRIC.md @@ -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` diff --git a/include/starkernel/ttf.h b/include/starkernel/ttf.h index 6657a28..82d3606 100644 --- a/include/starkernel/ttf.h +++ b/include/starkernel/ttf.h @@ -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 diff --git a/src/starkernel/hal/ttf.c b/src/starkernel/hal/ttf.c index ae066e2..a9f34f3 100644 --- a/src/starkernel/hal/ttf.c +++ b/src/starkernel/hal/ttf.c @@ -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); } \ No newline at end of file diff --git a/tools/ttftest.c b/tools/ttftest.c index 844ba4c..94198a1 100644 --- a/tools/ttftest.c +++ b/tools/ttftest.c @@ -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) {