[feat] 低质量ttf render
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
#include <fonts/ttf.h>
|
||||
#include "ttf_internal.h"
|
||||
#include <graphics/draw.h>
|
||||
#include <memory/heap.h>
|
||||
#include <serial.h>
|
||||
|
||||
// Per-glyph scratch — kept static to avoid stack pressure for big Chinese glyphs
|
||||
static ttf_outline_t s_outline;
|
||||
static ttf_seg_t s_segs[4096];
|
||||
static SUINT8 s_coverage[256 * 256];
|
||||
|
||||
// Render one glyph bitmap at screen (px, py) where py is the glyph TOP
|
||||
// (already converted from baseline). (px, py) may be negative — caller
|
||||
// must have clipped into the visible region.
|
||||
static void blit_glyph(SSINT32 px, SSINT32 py, SUINT32 w, SUINT32 h,
|
||||
const SUINT8* coverage, SUINT32 N,
|
||||
EFI_GRAPHICS_OUTPUT_BLT_PIXEL color)
|
||||
{
|
||||
SUINT32 scale = 255 / N;
|
||||
for (SUINT32 y = 0; y < h; y++) {
|
||||
SSINT32 sy = py + (SSINT32)y;
|
||||
if (sy < 0) continue;
|
||||
for (SUINT32 x = 0; x < w; x++) {
|
||||
SSINT32 sx = px + (SSINT32)x;
|
||||
if (sx < 0) continue;
|
||||
SUINT8 c = coverage[y * w + x];
|
||||
if (c == 0) continue;
|
||||
SUINT8 a = (SUINT8)(c * scale);
|
||||
draw_pixel_alpha(sx, sy, color, a);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Render a single codepoint at (x, y) = baseline. Returns advance (26.6).
|
||||
static f26_6 render_codepoint(ttf_face_t* face, SSINT32 cp,
|
||||
SSINT32 x, SSINT32 y, SUINT32 pixel_size,
|
||||
EFI_GRAPHICS_OUTPUT_BLT_PIXEL color)
|
||||
{
|
||||
SUINT16 gid = ttf_cmap_lookup(face, cp);
|
||||
if (gid == 0 && cp != 0) gid = 0; // notdef
|
||||
|
||||
if (!ttf_load_glyph(face, gid, pixel_size, &s_outline)) {
|
||||
return 0; // composite / parse error
|
||||
}
|
||||
|
||||
// Bitmap dims in pixels
|
||||
SSINT32 bbox_w = s_outline.xmax - s_outline.xmin;
|
||||
SSINT32 bbox_h = s_outline.ymax - s_outline.ymin;
|
||||
if (bbox_w <= 0 || bbox_h <= 0) {
|
||||
return s_outline.advance; // whitespace or zero-size
|
||||
}
|
||||
SUINT32 pw = (SUINT32)((bbox_w + 63) >> 6);
|
||||
SUINT32 ph = (SUINT32)((bbox_h + 63) >> 6);
|
||||
if (pw > 256 || ph > 256) {
|
||||
return s_outline.advance; // too big for scratch
|
||||
}
|
||||
|
||||
// Translate outline to bitmap-local: bx = fx - xmin, by = ymax - fy
|
||||
ttf_outline_t local;
|
||||
local.num_points = s_outline.num_points;
|
||||
local.num_contours = s_outline.num_contours;
|
||||
for (SUINT16 i = 0; i < s_outline.num_contours; i++) {
|
||||
local.first[i] = s_outline.first[i];
|
||||
local.last[i] = s_outline.last[i];
|
||||
}
|
||||
for (SUINT16 i = 0; i < s_outline.num_points; i++) {
|
||||
local.x[i] = s_outline.x[i] - s_outline.xmin;
|
||||
local.y[i] = s_outline.ymax - s_outline.y[i];
|
||||
local.on_curve[i] = s_outline.on_curve[i];
|
||||
}
|
||||
local.xmin = 0;
|
||||
local.ymin = 0;
|
||||
local.xmax = bbox_w;
|
||||
local.ymax = bbox_h;
|
||||
|
||||
SUINT32 n_segs = 0;
|
||||
ttf_outline_to_segments(&local, s_segs, &n_segs);
|
||||
|
||||
// Clear coverage
|
||||
for (SUINT32 i = 0; i < pw * ph; i++) s_coverage[i] = 0;
|
||||
|
||||
const SUINT32 N = 5; // subsamples per pixel row
|
||||
ttf_rasterize(s_segs, n_segs, 0, 0, pw, ph, s_coverage, N);
|
||||
|
||||
// Screen origin of bitmap
|
||||
// bitmap (0, 0) is at font (xmin, ymax) which is at screen
|
||||
// (x + xmin/64, y - ymax/64) since screen y is flipped
|
||||
SSINT32 px_screen = x + (s_outline.xmin >> 6);
|
||||
SSINT32 py_screen = y - (s_outline.ymax >> 6);
|
||||
|
||||
blit_glyph(px_screen, py_screen, pw, ph, s_coverage, N, color);
|
||||
|
||||
return s_outline.advance;
|
||||
}
|
||||
|
||||
SSINT32 ttf_draw_text(ttf_face_t* face, const char* utf8,
|
||||
SSINT32 x, SSINT32 y, SUINT32 pixel_size,
|
||||
EFI_GRAPHICS_OUTPUT_BLT_PIXEL color)
|
||||
{
|
||||
if (!face || !utf8) return 0;
|
||||
const char* p = utf8;
|
||||
f26_6 pen = 0;
|
||||
while (*p) {
|
||||
SSINT32 cp = ttf_utf8_decode(&p);
|
||||
if (cp < 0) continue;
|
||||
f26_6 adv = render_codepoint(face, cp,
|
||||
x + (pen >> 6), y, pixel_size, color);
|
||||
pen += adv;
|
||||
}
|
||||
return pen;
|
||||
}
|
||||
|
||||
SSINT32 ttf_text_width(ttf_face_t* face, const char* utf8, SUINT32 pixel_size) {
|
||||
if (!face || !utf8) return 0;
|
||||
const char* p = utf8;
|
||||
f26_6 pen = 0;
|
||||
while (*p) {
|
||||
SSINT32 cp = ttf_utf8_decode(&p);
|
||||
if (cp < 0) continue;
|
||||
SUINT16 gid = ttf_cmap_lookup(face, cp);
|
||||
if (!ttf_load_glyph(face, gid, pixel_size, &s_outline)) continue;
|
||||
pen += s_outline.advance;
|
||||
}
|
||||
return pen;
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
#pragma once
|
||||
|
||||
#include <efi.h>
|
||||
#include <common.h>
|
||||
#include "ttf_math.h"
|
||||
|
||||
typedef struct ttf_face ttf_face_t;
|
||||
|
||||
typedef struct ttf_face {
|
||||
const SUINT8* data;
|
||||
UINTN size;
|
||||
|
||||
// head
|
||||
SUINT32 units_per_em;
|
||||
SSINT16 index_to_loc_format; // 0 = short, 1 = long
|
||||
|
||||
// hhea
|
||||
SSINT16 hhea_ascender;
|
||||
SSINT16 hhea_descender;
|
||||
SSINT16 hhea_line_gap;
|
||||
SUINT16 num_long_hor_metrics;
|
||||
|
||||
// maxp
|
||||
SUINT16 num_glyphs;
|
||||
SUINT16 max_points;
|
||||
SUINT16 max_contours;
|
||||
|
||||
// os/2
|
||||
SSINT16 os2_ascender;
|
||||
SSINT16 os2_descender;
|
||||
SSINT16 os2_line_gap;
|
||||
|
||||
// Table directory (sorted by tag at open time)
|
||||
struct {
|
||||
char tag[4];
|
||||
SUINT32 offset;
|
||||
SUINT32 length;
|
||||
} tables[32];
|
||||
SUINT16 num_tables;
|
||||
|
||||
// Cached pointers into data[]
|
||||
const SUINT8* loca; // size depends on num_glyphs and format
|
||||
const SUINT8* glyf;
|
||||
SUINT32 glyf_len;
|
||||
const SUINT8* hmtx;
|
||||
SUINT32 hmtx_len;
|
||||
const SUINT8* cmap;
|
||||
} ttf_face_t;
|
||||
|
||||
// Decomposed glyph: contours in pixel space (26.6 fp) ready to scan.
|
||||
typedef struct ttf_outline {
|
||||
f26_6 x[1024];
|
||||
f26_6 y[1024];
|
||||
SUINT8 on_curve[1024];
|
||||
SUINT16 first[64];
|
||||
SUINT16 last[64];
|
||||
SUINT16 num_contours;
|
||||
SUINT16 num_points;
|
||||
|
||||
// Bounding box in pixel space (26.6 fp).
|
||||
SSINT32 xmin, ymin, xmax, ymax;
|
||||
|
||||
// Pen advance in pixel space (26.6 fp).
|
||||
f26_6 advance;
|
||||
} ttf_outline_t;
|
||||
|
||||
typedef struct ttf_seg {
|
||||
f26_6 x0, y0, x1, y1; // line / quad end points
|
||||
f26_6 cx, cy; // quadratic control (set to x0/y0 if is_line)
|
||||
SUINT8 is_line; // 1 = line, 0 = quad
|
||||
} ttf_seg_t;
|
||||
|
||||
// Parse the glyf entry for glyph_id. Fills outline already
|
||||
// scaled to pixel_size_px. Returns false on composite / parse error.
|
||||
bool ttf_load_glyph(ttf_face_t* face, SUINT16 glyph_id,
|
||||
SUINT32 pixel_size_px, ttf_outline_t* out);
|
||||
|
||||
// UTF-8 decoder. *p advances past the codepoint. Returns -1 on error.
|
||||
SSINT32 ttf_utf8_decode(const char** p);
|
||||
|
||||
// cmap lookup — returns glyph_id (0 = notdef).
|
||||
SUINT16 ttf_cmap_lookup(ttf_face_t* face, SSINT32 cp);
|
||||
|
||||
// Decompose outline into a flat array of line/quadratic segments.
|
||||
// out must have room for 2*num_points + num_contours entries.
|
||||
void ttf_outline_to_segments(const ttf_outline_t* outline,
|
||||
ttf_seg_t* segs, SUINT32* num_segs);
|
||||
|
||||
// Scanline rasterize segments into per-pixel coverage.
|
||||
// coverage[w*h] gets values in [0, N] where N = supersample count.
|
||||
void ttf_rasterize(const ttf_seg_t* segs, SUINT32 num_segs,
|
||||
SSINT32 x0, SSINT32 y0, SUINT32 w, SUINT32 h,
|
||||
SUINT8* coverage, SUINT32 N);
|
||||
@@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
|
||||
#include <common.h>
|
||||
|
||||
// 26.6 fixed point. 1.0 = 64, -32.0 .. 31.999 range in SSINT32.
|
||||
typedef SSINT32 f26_6;
|
||||
|
||||
#define F26_ONE ((f26_6)64)
|
||||
#define F26_HALF ((f26_6)32)
|
||||
#define F26_FROM_INT(x) ((f26_6)((x) * 64))
|
||||
#define F26_FLOOR(x) ((x) >> 6)
|
||||
#define F26_ROUND(x) (((x) + 32) >> 6)
|
||||
#define F26_FRAC(x) ((x) & 63)
|
||||
|
||||
static inline f26_6 f26_mul(f26_6 a, f26_6 b) {
|
||||
return (f26_6)(((SSINT64)a * b) >> 6);
|
||||
}
|
||||
|
||||
static inline f26_6 f26_div(f26_6 a, f26_6 b) {
|
||||
if (b == 0) return 0;
|
||||
return (f26_6)(((SSINT64)a << 6) / b);
|
||||
}
|
||||
|
||||
// Linear interpolation. t in [0, 64].
|
||||
static inline f26_6 f26_lerp(f26_6 a, f26_6 b, f26_6 t) {
|
||||
return a + f26_mul(b - a, t);
|
||||
}
|
||||
|
||||
// Floor-of-division helper for bezier root solving.
|
||||
static inline SSINT32 isign(SSINT32 x) { return (x > 0) - (x < 0); }
|
||||
@@ -0,0 +1,331 @@
|
||||
#include "ttf_internal.h"
|
||||
#include <string_utils.h>
|
||||
#include <memory/heap.h>
|
||||
#include <serial.h>
|
||||
|
||||
// ---- 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 — unsupported in v1. Keep advance, no outline.
|
||||
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);
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
#include "ttf_internal.h"
|
||||
#include <string_utils.h>
|
||||
|
||||
// ---- Outline -> segments ----
|
||||
//
|
||||
// TrueType contour walk: for each pair of consecutive points, emit either
|
||||
// a line or a quadratic bezier. Two consecutive off-curve points trigger
|
||||
// synthesis of an on-curve midpoint.
|
||||
//
|
||||
// All coordinates remain in 26.6 fp throughout.
|
||||
void ttf_outline_to_segments(const ttf_outline_t* outline,
|
||||
ttf_seg_t* segs, SUINT32* num_segs)
|
||||
{
|
||||
*num_segs = 0;
|
||||
f26_6 syn_x[256];
|
||||
f26_6 syn_y[256];
|
||||
int n_syn = 0;
|
||||
|
||||
for (SUINT16 ci = 0; ci < outline->num_contours; ci++) {
|
||||
SUINT16 first = outline->first[ci];
|
||||
SUINT16 last = outline->last[ci];
|
||||
int n_pts = last - first + 1;
|
||||
if (n_pts < 2) continue;
|
||||
|
||||
f26_6 lx[1024];
|
||||
f26_6 ly[1024];
|
||||
SUINT8 on_c[1024];
|
||||
for (int i = 0; i < n_pts; i++) {
|
||||
lx[i] = outline->x[first + i];
|
||||
ly[i] = outline->y[first + i];
|
||||
on_c[i] = outline->on_curve[first + i];
|
||||
}
|
||||
|
||||
// First on-curve point
|
||||
int start = 0;
|
||||
while (start < n_pts && !on_c[start]) start++;
|
||||
if (start == n_pts) start = 0; // all off-curve (rare) — keep going
|
||||
|
||||
int anchor = start;
|
||||
int pending = -1;
|
||||
|
||||
#define GET_X(i) ((i) < n_pts ? lx[(i)] : syn_x[(i) - n_pts])
|
||||
#define GET_Y(i) ((i) < n_pts ? ly[(i)] : syn_y[(i) - n_pts])
|
||||
#define PUSH_LINE(a, b) do { \
|
||||
ttf_seg_t* __s = &segs[(*num_segs)++]; \
|
||||
__s->x0 = GET_X(a); __s->y0 = GET_Y(a); \
|
||||
__s->x1 = GET_X(b); __s->y1 = GET_Y(b); \
|
||||
__s->cx = __s->x0; __s->cy = __s->y0; __s->is_line = 1; \
|
||||
} while (0)
|
||||
#define PUSH_QUAD(a, c, b) do { \
|
||||
ttf_seg_t* __s = &segs[(*num_segs)++]; \
|
||||
__s->x0 = GET_X(a); __s->y0 = GET_Y(a); \
|
||||
__s->x1 = GET_X(b); __s->y1 = GET_Y(b); \
|
||||
__s->cx = GET_X(c); __s->cy = GET_Y(c); __s->is_line = 0; \
|
||||
} while (0)
|
||||
|
||||
for (int step = 0; step < n_pts; step++) {
|
||||
int cur = (start + step) % n_pts;
|
||||
if (step == 0) { anchor = cur; continue; }
|
||||
if (on_c[cur]) {
|
||||
if (pending < 0) {
|
||||
PUSH_LINE(anchor, cur);
|
||||
} else {
|
||||
PUSH_QUAD(anchor, pending, cur);
|
||||
pending = -1;
|
||||
}
|
||||
anchor = cur;
|
||||
} else {
|
||||
if (pending < 0) {
|
||||
pending = cur;
|
||||
} else {
|
||||
int syn_idx = n_pts + n_syn;
|
||||
syn_x[n_syn] = (GET_X(pending) + GET_X(cur)) >> 1;
|
||||
syn_y[n_syn] = (GET_Y(pending) + GET_Y(cur)) >> 1;
|
||||
n_syn++;
|
||||
PUSH_QUAD(anchor, pending, syn_idx);
|
||||
anchor = syn_idx;
|
||||
pending = cur;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (pending >= 0) PUSH_QUAD(anchor, pending, start);
|
||||
|
||||
#undef GET_X
|
||||
#undef GET_Y
|
||||
#undef PUSH_LINE
|
||||
#undef PUSH_QUAD
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Integer sqrt (Newton) ----
|
||||
static SUINT32 isqrt_u64(SUINT64 n) {
|
||||
if (n == 0) return 0;
|
||||
SUINT32 x = (n > 0xFFFFFFFFu) ? 0xFFFFu : (SUINT32)n;
|
||||
SUINT32 y = (x + 1) >> 1;
|
||||
while (y < x) { x = y; y = (x + (SUINT32)(n / x)) >> 1; }
|
||||
return x;
|
||||
}
|
||||
|
||||
// ---- Scanline fill with subpixel supersampling ----
|
||||
//
|
||||
// For each output row, run N sub-scanlines at offsets (k+0.5)/N. For each
|
||||
// sub-scanline y, collect all x-intersections, sort, fill alternating
|
||||
// x-pairs. Sum over N subsamples yields per-pixel coverage in [0, N].
|
||||
void ttf_rasterize(const ttf_seg_t* segs, SUINT32 num_segs,
|
||||
SSINT32 x0, SSINT32 y0, SUINT32 w, SUINT32 h,
|
||||
SUINT8* coverage, SUINT32 N)
|
||||
{
|
||||
// Clear coverage
|
||||
for (SUINT32 i = 0; i < w * h; i++) coverage[i] = 0;
|
||||
|
||||
// Intersection x-buf (per scanline, max possible = num_segs)
|
||||
f26_6 xs[2048];
|
||||
if (num_segs > 2048) num_segs = 2048;
|
||||
|
||||
for (SUINT32 row = 0; row < h; row++) {
|
||||
for (SUINT32 k = 0; k < N; k++) {
|
||||
// Sub-scanline y, in 26.6, with y = 0 at glyph top
|
||||
f26_6 sy = (f26_6)((row * 64) + ((k * 2 + 1) * 64) / (SSINT32)(N * 2));
|
||||
// ^ y center of subpixel k: (k + 0.5) * 64 / N
|
||||
// = ((2k+1) * 64) / (2N)
|
||||
// For N=5: k=0 -> 6.4, k=1 -> 19.2, etc.
|
||||
|
||||
SUINT32 nxs = 0;
|
||||
for (SUINT32 s = 0; s < num_segs; s++) {
|
||||
const ttf_seg_t* g = &segs[s];
|
||||
if (g->is_line) {
|
||||
f26_6 y0s = g->y0;
|
||||
f26_6 y1s = g->y1;
|
||||
if (y0s == y1s) continue; // horizontal — skip
|
||||
// t = (sy - y0s) / (y1s - y0s) in (0, 1)
|
||||
if (((y0s < sy) && (y1s < sy)) ||
|
||||
((y0s > sy) && (y1s > sy))) continue;
|
||||
f26_6 t = f26_div(sy - y0s, y1s - y0s);
|
||||
if (t <= 0 || t >= F26_ONE) continue;
|
||||
f26_6 x = g->x0 + f26_mul(t, g->x1 - g->x0);
|
||||
xs[nxs++] = x;
|
||||
} else {
|
||||
// Quadratic: y(t) = (1-t)^2 y0 + 2(1-t)t cy + t^2 y1
|
||||
// a t^2 + b t + c = 0
|
||||
// a = y1 - 2 cy + y0
|
||||
// b = 2 (cy - y0)
|
||||
// c = y0 - sy
|
||||
SSINT64 a = (SSINT64)g->y1 - 2 * (SSINT64)g->cy + (SSINT64)g->y0;
|
||||
SSINT64 b = 2 * ((SSINT64)g->cy - (SSINT64)g->y0);
|
||||
SSINT64 c = (SSINT64)g->y0 - (SSINT64)sy;
|
||||
SSINT32 t0_valid = 0, t1_valid = 0;
|
||||
f26_6 t0 = 0, t1 = 0;
|
||||
if (a == 0) {
|
||||
// Linear
|
||||
if (b == 0) continue;
|
||||
f26_6 t = f26_div((f26_6)(-c), (f26_6)b);
|
||||
if (t > 0 && t < F26_ONE) { t0 = t; t0_valid = 1; }
|
||||
} else {
|
||||
SSINT64 disc = b * b - 4 * a * c;
|
||||
if (disc < 0) continue;
|
||||
SUINT32 sq = isqrt_u64((SUINT64)disc);
|
||||
// 26.6 roots: t = (-b ± sqrt(disc)) / (2a)
|
||||
// All in 26.6 fp.
|
||||
// t = (-b ± sq) / (2a); both numerator and denom in 26.6 units
|
||||
// Use f26_div: t_26_6 = ((-b ± sq) << 6) / (2a)
|
||||
SSINT64 denom = 2 * a;
|
||||
if (denom == 0) continue;
|
||||
SSINT64 num0 = -b + (SSINT64)sq;
|
||||
SSINT64 num1 = -b - (SSINT64)sq;
|
||||
t0 = (f26_6)(num0 == 0 ? 0 : (num0 << 6) / denom);
|
||||
t1 = (f26_6)(num1 == 0 ? 0 : (num1 << 6) / denom);
|
||||
if (t0 > 0 && t0 < F26_ONE) t0_valid = 1;
|
||||
if (t1 > 0 && t1 < F26_ONE) t1_valid = 1;
|
||||
}
|
||||
if (t0_valid) {
|
||||
// x(t) = (1-t)^2 x0 + 2(1-t)t cx + t^2 x1
|
||||
f26_6 omt = F26_ONE - t0;
|
||||
f26_6 x = f26_mul(f26_mul(omt, omt), g->x0)
|
||||
+ f26_mul(2 * f26_mul(omt, t0), g->cx)
|
||||
+ f26_mul(f26_mul(t0, t0), g->x1);
|
||||
xs[nxs++] = x;
|
||||
}
|
||||
if (t1_valid) {
|
||||
f26_6 omt = F26_ONE - t1;
|
||||
f26_6 x = f26_mul(f26_mul(omt, omt), g->x0)
|
||||
+ f26_mul(2 * f26_mul(omt, t1), g->cx)
|
||||
+ f26_mul(f26_mul(t1, t1), g->x1);
|
||||
xs[nxs++] = x;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (nxs < 2) continue;
|
||||
// Insertion sort
|
||||
for (SUINT32 i = 1; i < nxs; i++) {
|
||||
f26_6 v = xs[i]; SUINT32 j = i;
|
||||
while (j > 0 && xs[j-1] > v) { xs[j] = xs[j-1]; j--; }
|
||||
xs[j] = v;
|
||||
}
|
||||
// Fill alternating pairs
|
||||
for (SUINT32 i = 0; i + 1 < nxs; i += 2) {
|
||||
f26_6 xa = xs[i];
|
||||
f26_6 xb = xs[i+1];
|
||||
if (xa == xb) continue;
|
||||
SSINT32 c0 = F26_FLOOR(xa);
|
||||
SSINT32 c1 = F26_FLOOR(xb);
|
||||
if (c0 == c1) continue;
|
||||
if (c0 < 0) c0 = 0;
|
||||
if (c1 > (SSINT32)w) c1 = (SSINT32)w;
|
||||
for (SSINT32 x = c0; x < c1; x++) {
|
||||
if (x >= 0 && x < (SSINT32)w) coverage[row * w + x]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// (Alpha conversion happens at the caller via N.)
|
||||
(void)x0; (void)y0;
|
||||
}
|
||||
Reference in New Issue
Block a user