#include "ttf_internal.h" #include #include #include // ---- Big-endian readers (TTF is big-endian) ---- static inline SUINT16 rd16(const SUINT8* p) { return ((SUINT16)p[0] << 8) | p[1]; } static inline SSINT16 rd16s(const SUINT8* p) { return (SSINT16)(((SUINT16)p[0] << 8) | p[1]); } static inline SUINT32 rd32(const SUINT8* p) { return ((SUINT32)p[0] << 24) | ((SUINT32)p[1] << 16) | ((SUINT32)p[2] << 8) | (SUINT32)p[3]; } static const SUINT8* find_table(ttf_face_t* face, const char tag[4]) { for (SUINT16 i = 0; i < face->num_tables; i++) { if (face->tables[i].tag[0] == tag[0] && face->tables[i].tag[1] == tag[1] && face->tables[i].tag[2] == tag[2] && face->tables[i].tag[3] == tag[3]) { return face->data + face->tables[i].offset; } } return NULL; } // ---- UTF-8 ---- SSINT32 ttf_utf8_decode(const char** p) { const SUINT8* s = (const SUINT8*)*p; SUINT8 b0 = s[0]; if (b0 < 0x80) { (*p)++; return b0; } if ((b0 & 0xE0) == 0xC0) { (*p) += 2; return ((b0 & 0x1F) << 6) | (s[1] & 0x3F); } if ((b0 & 0xF0) == 0xE0) { (*p) += 3; return ((b0 & 0x0F) << 12) | ((s[1] & 0x3F) << 6) | (s[2] & 0x3F); } if ((b0 & 0xF8) == 0xF0) { (*p) += 4; return ((b0 & 0x07) << 18) | ((s[1] & 0x3F) << 12) | ((s[2] & 0x3F) << 6) | (s[3] & 0x3F); } (*p)++; return -1; } // ---- cmap ---- static const SUINT8* find_cmap_subtable(ttf_face_t* face) { const SUINT8* cmap = face->cmap; SUINT16 num = rd16(cmap + 2); const SUINT8* best = NULL; SSINT32 best_score = -1; for (SUINT16 i = 0; i < num; i++) { const SUINT8* rec = cmap + 4 + i * 8; SUINT16 platform = rd16(rec + 0); SUINT16 encoding = rd16(rec + 2); SUINT32 offset = rd32(rec + 4); SSINT32 score = -1; if (platform == 3 && encoding == 10) score = 30; else if (platform == 3 && encoding == 1) score = 20; else if (platform == 0 && encoding == 4) score = 10; else if (platform == 0 && encoding == 3) score = 5; if (score > best_score) { best_score = score; best = cmap + offset; } } return best; } static SUINT16 cmap4_lookup(const SUINT8* sub, SSINT32 cp) { SUINT16 segCountX2 = rd16(sub + 6); SUINT16 segCount = segCountX2 / 2; const SUINT8* endCode = sub + 14; const SUINT8* startCode = endCode + segCountX2 + 2; const SUINT8* idDelta = startCode + segCountX2; const SUINT8* idRangeOff = idDelta + segCountX2; for (SUINT16 i = 0; i < segCount; i++) { SUINT16 ec = rd16(endCode + i * 2); SUINT16 sc = rd16(startCode + i * 2); if (cp < sc) break; if (cp <= ec) { SUINT16 dro = rd16(idRangeOff + i * 2); if (dro == 0) { return (SUINT16)((SSINT16)cp + (SSINT16)rd16(idDelta + i * 2)); } SUINT16 gidx = rd16(idRangeOff + i * 2 + dro + (cp - sc) * 2); if (gidx == 0) return 0; return (SUINT16)((SSINT16)gidx + (SSINT16)rd16(idDelta + i * 2)); } } return 0; } static SUINT16 cmap12_lookup(const SUINT8* sub, SSINT32 cp) { // Format 12 header: format(2) reserved(2) length(4) language(4) nGroups(4) SUINT32 nGroups = rd32(sub + 12); const SUINT8* g = sub + 16; for (SUINT32 i = 0; i < nGroups; i++) { SUINT32 sc = rd32(g + 0); SUINT32 ec = rd32(g + 4); SUINT32 sG = rd32(g + 8); if (cp < (SSINT32)sc) break; if (cp <= (SSINT32)ec) return (SUINT16)(sG + (cp - sc)); g += 12; } return 0; } SUINT16 ttf_cmap_lookup(ttf_face_t* face, SSINT32 cp) { const SUINT8* sub = find_cmap_subtable(face); if (!sub) return 0; SUINT16 fmt = rd16(sub); if (fmt == 4) return cmap4_lookup(sub, cp); if (fmt == 12) return cmap12_lookup(sub, cp); return 0; } // ---- Glyf decode ---- bool ttf_load_glyph(ttf_face_t* face, SUINT16 glyph_id, SUINT32 pixel_size_px, ttf_outline_t* out) { mem_set(out, 0, sizeof(*out)); if (glyph_id >= face->num_glyphs) return false; // Loca SUINT32 off0, off1; if (face->index_to_loc_format == 0) { off0 = ((SUINT32)rd16(face->loca + glyph_id * 2)) * 2; off1 = ((SUINT32)rd16(face->loca + (glyph_id + 1) * 2)) * 2; } else { off0 = rd32(face->loca + glyph_id * 4); off1 = rd32(face->loca + (glyph_id + 1) * 4); } if (off0 >= face->glyf_len) return false; // Advance width (always readable from hmtx regardless of glyf presence) { SUINT16 aw; if (glyph_id < face->num_long_hor_metrics) { aw = rd16(face->hmtx + glyph_id * 4); } else { aw = rd16(face->hmtx + (face->num_long_hor_metrics - 1) * 4); } out->advance = (f26_6)(((SUINT64)aw * (SUINT64)pixel_size_px * 64) / face->units_per_em); } if (off0 == off1) { // Empty glyph (e.g. space) return true; } const SUINT8* g = face->glyf + off0; SSINT16 numContours = rd16s(g + 0); if (numContours < 0) { // Composite glyph — parse component records and merge outlines const SUINT8* cp = g + 10; SSINT32 all_xmin = 0x7FFFFFFF, all_ymin = 0x7FFFFFFF; SSINT32 all_xmax = -0x7FFFFFFF, all_ymax = -0x7FFFFFFF; // Composite glyph flags (OpenType spec): // 0x0001 = ARG_1_AND_2_ARE_WORDS (16-bit args; else 8-bit) // 0x0002 = ARGS_ARE_XY_VALUES (offsets; else point indices) // 0x0008 = WE_HAVE_A_SCALE // 0x0040 = WE_HAVE_A_2x2 // 0x0080 = WE_HAVE_AN_X_AND_Y_SCALE // 0x0020 = MORE_COMPONENTS for (;;) { SUINT16 comp_flags = rd16(cp); cp += 2; SUINT16 comp_glyph = rd16(cp); cp += 2; // Read arguments (size depends on ARG_1_AND_2_ARE_WORDS) SSINT32 arg1, arg2; if (comp_flags & 0x0001) { // 16-bit signed words arg1 = rd16s(cp); cp += 2; arg2 = rd16s(cp); cp += 2; } else { // 8-bit signed bytes arg1 = (SSINT8)cp[0]; arg2 = (SSINT8)cp[1]; cp += 2; } // Read transform scale if present f26_6 scale = F26_ONE; if (comp_flags & 0x0008) { // WE_HAVE_A_SCALE: 16.16 fixed-point SSINT16 s16 = rd16s(cp); cp += 2; scale = (f26_6)s16; // already in f26.6 from 16.16 } // Read x/y scale if present f26_6 scaleX = F26_ONE, scaleY = F26_ONE; if (comp_flags & 0x0080) { // WE_HAVE_AN_X_AND_Y_SCALE: two 16.16 values SSINT16 sx16 = rd16s(cp); cp += 2; SSINT16 sy16 = rd16s(cp); cp += 2; scaleX = (f26_6)sx16; scaleY = (f26_6)sy16; } // Read 2x2 matrix if present f26_6 m00 = F26_ONE, m01 = 0, m10 = 0, m11 = F26_ONE; if (comp_flags & 0x0040) { // WE_HAVE_A_2x2: four 2.14 values SSINT16 a = rd16s(cp); cp += 2; SSINT16 b = rd16s(cp); cp += 2; SSINT16 c = rd16s(cp); cp += 2; SSINT16 d = rd16s(cp); cp += 2; m00 = (f26_6)(a >> 8); // 2.14 -> 26.6: shift right 8 m01 = (f26_6)(b >> 8); m10 = (f26_6)(c >> 8); m11 = (f26_6)(d >> 8); } // Load component glyph ttf_outline_t comp; if (!ttf_load_glyph(face, comp_glyph, pixel_size_px, &comp)) return false; // Determine if args are offsets (XY values) or point indices SSINT32 offset_x = 0, offset_y = 0; if (comp_flags & 0x0002) { // ARGS_ARE_XY_VALUES: args are pixel offsets (already scaled) // Scale from font units to pixel space like simple glyphs offset_x = (SSINT32)(((SSINT64)arg1 * (SSINT64)pixel_size_px * 64 / face->units_per_em)); offset_y = (SSINT32)(((SSINT64)arg2 * (SSINT64)pixel_size_px * 64 / face->units_per_em)); } // Transform and merge component points for (SUINT16 i = 0; i < comp.num_points; i++) { f26_6 fx = comp.x[i]; f26_6 fy = comp.y[i]; f26_6 tx, ty; if (comp_flags & 0x0040) { // 2x2 matrix transform tx = f26_mul(m00, fx) + f26_mul(m01, fy); ty = f26_mul(m10, fx) + f26_mul(m11, fy); } else if (comp_flags & 0x0008) { // Uniform scale tx = f26_mul(scale, fx); ty = f26_mul(scale, fy); } else if (comp_flags & 0x0080) { // X/Y scale tx = f26_mul(scaleX, fx); ty = f26_mul(scaleY, fy); } else { tx = fx; ty = fy; } f26_6 final_x = tx + offset_x; f26_6 final_y = ty + offset_y; out->x[out->num_points] = final_x; out->y[out->num_points] = final_y; out->on_curve[out->num_points] = comp.on_curve[i]; out->num_points++; if (out->num_points > 1024) return false; SSINT32 xi = F26_FLOOR(final_x); SSINT32 yi = F26_FLOOR(final_y); if (xi < all_xmin) all_xmin = xi; if (xi > all_xmax) all_xmax = xi; if (yi < all_ymin) all_ymin = yi; if (yi > all_ymax) all_ymax = yi; } for (SUINT16 i = 0; i < comp.num_contours; i++) { if (out->num_contours >= 64) return false; out->first[out->num_contours] = (SUINT16)(out->num_points - comp.num_points + comp.first[i]); out->last[out->num_contours] = (SUINT16)(out->num_points - comp.num_points + comp.last[i]); out->num_contours++; } if (!(comp_flags & 0x0020)) break; // no more components } if (out->num_points == 0) return true; out->xmin = all_xmin; out->ymin = all_ymin; out->xmax = all_xmax; out->ymax = all_ymax; return true; } if (numContours == 0) return true; if (numContours > 64) return false; SSINT16 gxMin = rd16s(g + 2); SSINT16 gyMin = rd16s(g + 4); SSINT16 gxMax = rd16s(g + 6); SSINT16 gyMax = rd16s(g + 8); const SUINT8* p = g + 10; SUINT16 endPts[64]; for (SSINT16 i = 0; i < numContours; i++) { endPts[i] = rd16(p); p += 2; } SUINT16 numPoints = (SUINT16)(endPts[numContours - 1] + 1); if (numPoints > 1024) return false; SUINT16 instrLen = rd16(p); p += 2; p += instrLen; // Decode flags (with REPEAT) SUINT8 flags[1024]; SUINT16 pi = 0; while (pi < numPoints) { SUINT8 f = *p++; flags[pi++] = f; if (f & 0x08) { SUINT8 rep = *p++; for (SUINT16 k = 1; k <= rep && pi < numPoints; k++) flags[pi++] = f; } } // Decode x-coords into x_raw[] SSINT32 x_raw[1024]; { SSINT32 x = 0; for (SUINT16 i = 0; i < numPoints; i++) { SUINT8 f = flags[i]; if (f & 0x02) { SUINT8 b = *p++; x += (f & 0x10) ? b : -(SSINT32)b; } else if (!(f & 0x10)) { x += (SSINT16)((SUINT16)p[0] << 8 | p[1]); p += 2; } x_raw[i] = x; } } // Decode y-coords into y_raw[] SSINT32 y_raw[1024]; { SSINT32 y = 0; for (SUINT16 i = 0; i < numPoints; i++) { SUINT8 f = flags[i]; if (f & 0x04) { SUINT8 b = *p++; y += (f & 0x20) ? b : -(SSINT32)b; } else if (!(f & 0x20)) { y += (SSINT16)((SUINT16)p[0] << 8 | p[1]); p += 2; } y_raw[i] = y; } } // Scale to pixel space (f26_6) SUINT64 scale_num = (SUINT64)pixel_size_px * 64; for (SUINT16 i = 0; i < numPoints; i++) { out->x[i] = (f26_6)(((SSINT64)x_raw[i] * (SSINT64)scale_num) / face->units_per_em); out->y[i] = (f26_6)(((SSINT64)y_raw[i] * (SSINT64)scale_num) / face->units_per_em); out->on_curve[i] = (flags[i] & 0x01) ? 1 : 0; } out->num_contours = (SUINT16)numContours; out->num_points = numPoints; SUINT16 start = 0; for (SSINT16 i = 0; i < numContours; i++) { out->first[i] = start; out->last[i] = endPts[i]; start = endPts[i] + 1; } out->xmin = (SSINT32)(((SSINT64)gxMin * (SSINT64)scale_num) / face->units_per_em); out->ymin = (SSINT32)(((SSINT64)gyMin * (SSINT64)scale_num) / face->units_per_em); out->xmax = (SSINT32)(((SSINT64)gxMax * (SSINT64)scale_num) / face->units_per_em); out->ymax = (SSINT32)(((SSINT64)gyMax * (SSINT64)scale_num) / face->units_per_em); return true; } // ---- Open / close ---- ttf_face_t* ttf_open(const void* data, UINTN size) { if (!data || size < 12) return NULL; const SUINT8* d = (const SUINT8*)data; SUINT32 sfVersion = rd32(d); if (sfVersion != 0x00010000 && sfVersion != 0x74727565) { serial_write("ttf: bad sfVersion\n"); return NULL; } SUINT16 numTables = rd16(d + 4); if (numTables == 0 || numTables > 32) return NULL; ttf_face_t* face = (ttf_face_t*)kcalloc(1, sizeof(ttf_face_t)); if (!face) return NULL; face->data = d; face->size = size; face->num_tables = numTables; for (SUINT16 i = 0; i < numTables; i++) { const SUINT8* r = d + 12 + i * 16; face->tables[i].tag[0] = r[0]; face->tables[i].tag[1] = r[1]; face->tables[i].tag[2] = r[2]; face->tables[i].tag[3] = r[3]; face->tables[i].offset = rd32(r + 8); face->tables[i].length = rd32(r + 12); } const SUINT8* head = find_table(face, "head"); if (!head) { kfree(face); return NULL; } face->units_per_em = rd16(head + 18); face->index_to_loc_format = rd16s(head + 50); const SUINT8* hhea = find_table(face, "hhea"); if (!hhea) { kfree(face); return NULL; } face->hhea_ascender = rd16s(hhea + 4); face->hhea_descender = rd16s(hhea + 6); face->hhea_line_gap = rd16s(hhea + 8); face->num_long_hor_metrics = rd16(hhea + 34); const SUINT8* maxp = find_table(face, "maxp"); if (!maxp) { kfree(face); return NULL; } face->num_glyphs = rd16(maxp + 4); face->max_points = rd16(maxp + 6); face->max_contours = rd16(maxp + 8); const SUINT8* os2 = find_table(face, "OS/2"); if (os2) { face->os2_ascender = rd16s(os2 + 68); face->os2_descender = rd16s(os2 + 70); face->os2_line_gap = rd16s(os2 + 72); } else { face->os2_ascender = face->hhea_ascender; face->os2_descender = face->hhea_descender; face->os2_line_gap = face->hhea_line_gap; } const SUINT8* loca = find_table(face, "loca"); const SUINT8* glyf = find_table(face, "glyf"); const SUINT8* hmtx = find_table(face, "hmtx"); if (!loca || !glyf || !hmtx) { kfree(face); return NULL; } face->loca = loca; face->glyf = glyf; face->hmtx = hmtx; for (SUINT16 i = 0; i < numTables; i++) { if (face->tables[i].tag[0]=='g'&&face->tables[i].tag[1]=='l'&& face->tables[i].tag[2]=='y'&&face->tables[i].tag[3]=='f') face->glyf_len = face->tables[i].length; if (face->tables[i].tag[0]=='h'&&face->tables[i].tag[1]=='m'&& face->tables[i].tag[2]=='t'&&face->tables[i].tag[3]=='x') face->hmtx_len = face->tables[i].length; } face->cmap = find_table(face, "cmap"); if (!face->cmap) { kfree(face); return NULL; } return face; } void ttf_close(ttf_face_t* face) { if (face) kfree(face); } // ---- Metrics (scaled to pixel_size, returned as 26.6 fp) ---- SSINT32 ttf_ascender (ttf_face_t* face, SUINT32 px) { return (SSINT32)(((SSINT64)face->os2_ascender * (SSINT64)px * 64) / face->units_per_em); } SSINT32 ttf_descender(ttf_face_t* face, SUINT32 px) { return (SSINT32)(((SSINT64)face->os2_descender * (SSINT64)px * 64) / face->units_per_em); } SSINT32 ttf_line_gap (ttf_face_t* face, SUINT32 px) { return (SSINT32)(((SSINT64)face->os2_line_gap * (SSINT64)px * 64) / face->units_per_em); }