/* * meow_ui_ft.c — SDK 矢量字(FreeType)封装。 * * 上收阅读器 R1/R2 已验证的 FreeType 管线(READER_ENGINE_NOTES §5),让任何 App 用上 * 无级字号 + CJK 全字形(解决大字库豆腐类问题的根)。与位图字库**并存**:App 显式调用 * meow_ft_* 才用矢量;既有 meow_ui_text 路径零改动。 * * 内存纪律(对齐 READER_ENGINE_NOTES §5.1/§5.4): * - FT_New_Face(path) 按需读文件(stream->base=NULL,不整文件载入 → 零大块 anon; * 文件是 disk-backed,可被内核回收重读)。不要 mmap 后 FT_New_Memory_Face。 * - 灰阶 FT_LOAD_RENDER | FT_LOAD_NO_HINTING | FT_LOAD_TARGET_LIGHT(平滑矢量)。 * - glyph LRU 缓存:key=(px<<32)|cp;默认 512(~0.46MB,28px CJK 约 900B/字), * 可配 256 省内存 / 1024 提命中。目标单 App 增量 ≤1.5MB(FreeType 本体按需拉取)。 * - 测宽(meow_ft_advance)只 FT_Load_Glyph 不 RENDER,不占 LRU 槽。 * * 本 TU 只含 FreeType 封装逻辑(load/glyph/advance/度量/cache),渲染进 back_buf 的 * meow_ft_text / meow_ft_width 在 meow_ui_gfx.c(需访问 back_buf/clip/put_px_blend)。 * 无 gfx 依赖 —— 纯宿主编译 + libfreetype.a 即可单测(见 test_meow_ui_ft.c)。 */ #include "meow_ui.h" /* 公共原型(meow_ui_font.h 含 meow_ft_*) */ #include "meow_ui_internal.h" /* meow_ft_glyph/advance 内部签名 */ #include #include FT_FREETYPE_H #include #include #include typedef struct { uint64_t key; /* (px<<32)|cp */ uint64_t stamp; /* LRU 单调戳 */ int adv; /* 水平 advance(px) */ uint8_t *bmp; /* 灰阶位图(紧凑,每行 bw 字节;bw==0 时 NULL) */ int bw, bh, left, top; } mft_glyph; typedef struct { FT_Library lib; FT_Face face; int cur_px; /* 当前 FT_Set_Pixel_Sizes 字号(0=未设) */ mft_glyph *slots; int nslots; int used; uint64_t clock; } mft; static mft *g_mft = NULL; /* App 单进程单例(主线程渲染,无并发) */ /* 惰性初始化 FreeType 运行时(幂等)。 */ static mft *mft_get(void) { if (g_mft) return g_mft; mft *x = (mft *)calloc(1, sizeof *x); if (!x) return NULL; if (FT_Init_FreeType(&x->lib) != 0) { free(x); return NULL; } x->nslots = 512; /* 默认 */ x->slots = (mft_glyph *)calloc((size_t)x->nslots, sizeof(mft_glyph)); if (!x->slots) { FT_Done_FreeType(x->lib); free(x); return NULL; } x->used = 0; x->clock = 1; g_mft = x; return x; } /* 清空 glyph 缓存并释放各槽位图(换字体/换字号/换容量时)。 */ static void mft_cache_flush(mft *x) { for (int i = 0; i < x->nslots; i++) free(x->slots[i].bmp); memset(x->slots, 0, (size_t)x->nslots * sizeof(mft_glyph)); x->used = 0; } int meow_ft_load(const char *path) { if (!path) return -1; mft *x = mft_get(); if (!x) return -3; if (x->face) { FT_Done_Face(x->face); x->face = NULL; } mft_cache_flush(x); x->cur_px = 0; if (FT_New_Face(x->lib, path, 0, &x->face) != 0) return -1; /* 无字体 → loaded()==0 → 降级位图 */ return 0; } void meow_ft_unload(void) { if (!g_mft) return; if (g_mft->face) { FT_Done_Face(g_mft->face); g_mft->face = NULL; } mft_cache_flush(g_mft); g_mft->cur_px = 0; } int meow_ft_loaded(void) { return (g_mft && g_mft->face) ? 1 : 0; } /* 定像素字号(仅当与当前不同才调 FT_Set_Pixel_Sizes)。 * 不在此清缓存:glyph key 已含字号,不同字号位图可共存于 LRU(同 face 的 size 切换只影响 * 后续光栅化的 26.6 度量,已缓存的位图是独立拷贝,仍有效)。排序器/同页多字号不互相驱逐。 */ static int mft_setpx(mft *x, int px) { if (!x->face || px <= 0) return -1; if (x->cur_px != px) { FT_Set_Pixel_Sizes(x->face, 0, (FT_UInt)px); x->cur_px = px; } return 0; } static mft_glyph *mft_lru_find(mft *x, uint64_t key) { for (int i = 0; i < x->nslots; i++) if (x->slots[i].key == key && x->slots[i].stamp) return &x->slots[i]; return NULL; } /* 取一个槽位:优先空槽(stamp==0),否则逐出最久未用;返回槽指针。 */ static mft_glyph *mft_lru_evict(mft *x) { mft_glyph *oldest = NULL; uint64_t mins = UINT64_MAX; for (int i = 0; i < x->nslots; i++) { if (!x->slots[i].stamp) { x->used++; return &x->slots[i]; } if (x->slots[i].stamp < mins) { mins = x->slots[i].stamp; oldest = &x->slots[i]; } } free(oldest->bmp); oldest->bmp = NULL; oldest->key = 0; oldest->stamp = 0; return oldest; } int meow_ft_glyph(int px, uint32_t cp, int *adv_x, const uint8_t **bmp, int *bw, int *bh, int *left, int *top) { if (!adv_x || !bmp) return -1; mft *x = g_mft; if (!x || !x->face) return -1; /* 无已载字体 → 调用方降级位图 */ mft_setpx(x, px); if (x->clock == UINT64_MAX) x->clock = 1; /* 时钟回绕(罕见) */ uint64_t key = ((uint64_t)(uint32_t)px << 32) | cp; mft_glyph *s = mft_lru_find(x, key); if (s) { s->stamp = x->clock++; *adv_x = s->adv; *bmp = s->bmp; *bw = s->bw; *bh = s->bh; *left = s->left; *top = s->top; return 0; } FT_UInt gi = FT_Get_Char_Index(x->face, cp); if (FT_Load_Glyph(x->face, gi, FT_LOAD_RENDER | FT_LOAD_NO_HINTING | FT_LOAD_TARGET_LIGHT) != 0) { *adv_x = (int)(x->face->glyph->advance.x >> 6); *bmp = NULL; *bw = *bh = *left = *top = 0; return 0; } FT_GlyphSlot g = x->face->glyph; int a = (int)((g->advance.x >= 0 ? g->advance.x + 0x20 : g->advance.x - 0x20) >> 6); if (a < 0) a = 0; s = mft_lru_evict(x); s->key = key; s->stamp = x->clock++; s->adv = a; s->bmp = NULL; s->bw = s->bh = s->left = s->top = 0; s->left = g->bitmap_left; s->top = g->bitmap_top; if (g->bitmap.buffer && g->bitmap.width > 0 && g->bitmap.rows > 0) { int w = (int)g->bitmap.width, rows = (int)g->bitmap.rows, pitch = (int)g->bitmap.pitch; uint8_t *b = (uint8_t *)malloc((size_t)w * (size_t)rows); if (b) { for (int rr = 0; rr < rows; rr++) memcpy(b + (size_t)rr * (size_t)w, g->bitmap.buffer + (size_t)rr * (size_t)pitch, (size_t)w); s->bmp = b; s->bw = w; s->bh = rows; /* 紧凑:每行 bw 字节 */ } } *adv_x = s->adv; *bmp = s->bmp; *bw = s->bw; *bh = s->bh; *left = s->left; *top = s->top; return 0; } int meow_ft_advance(int px, uint32_t cp, int *adv_x) { if (!adv_x) return -1; mft *x = g_mft; if (!x || !x->face) return -1; /* 无已载字体 → 调用方降级位图 */ mft_setpx(x, px); FT_UInt gi = FT_Get_Char_Index(x->face, cp); if (FT_Load_Glyph(x->face, gi, FT_LOAD_NO_HINTING | FT_LOAD_TARGET_LIGHT) != 0) { *adv_x = (int)(x->face->glyph->advance.x >> 6); return 0; } int a = (int)((x->face->glyph->advance.x >= 0 ? x->face->glyph->advance.x + 0x20 : x->face->glyph->advance.x - 0x20) >> 6); *adv_x = a < 0 ? 0 : a; return 0; } int meow_ft_ascender(int px) { mft *x = g_mft; if (!x || !x->face) return px > 0 ? px : 0; /* 无字体:px 近似(降级) */ mft_setpx(x, px); return (int)(x->face->size->metrics.ascender >> 6); } int meow_ft_descender(int px) { mft *x = g_mft; if (!x || !x->face) return 0; mft_setpx(x, px); return (int)(x->face->size->metrics.descender >> 6); } int meow_ft_line_height(int px) { return meow_ft_ascender(px) - meow_ft_descender(px) + 1; } int meow_ft_cache_set_slots(int slots) { mft *x = mft_get(); if (!x) return 0; if (slots < 1) slots = 1; if (slots > 4096) slots = 4096; if (slots == x->nslots) return x->nslots; mft_glyph *na = (mft_glyph *)calloc((size_t)slots, sizeof(mft_glyph)); if (!na) return x->nslots; for (int i = 0; i < x->nslots; i++) free(x->slots[i].bmp); free(x->slots); x->slots = na; x->nslots = slots; x->used = 0; return x->nslots; } int meow_ft_cache_slots(void) { return g_mft ? g_mft->nslots : 0; } int meow_ft_cache_used(void) { return g_mft ? g_mft->used : 0; } /* ---------------- UTF-8 解码(渲染/测宽逐码点) ---------------- */ static int ft_utf8_next(const unsigned char **pp) { const unsigned char *s = *pp; unsigned char c = s[0]; if (c < 0x80) { *pp += 1; return c; } int n; uint32_t cp; if ((c & 0xE0) == 0xC0) { n = 2; cp = c & 0x1F; } else if ((c & 0xF0) == 0xE0) { n = 3; cp = c & 0x0F; } else if ((c & 0xF8) == 0xF0) { n = 4; cp = c & 0x07; } else { *pp += 1; return -1; } for (int i = 1; i < n; i++) { if ((s[i] & 0xC0) != 0x80) { *pp += 1; return -1; } cp = (cp << 6) | (s[i] & 0x3F); } *pp += n; return (int)cp; } int meow_ft_text(int x, int y, const char *utf8, int px, uint32_t color) { if (!utf8) return -2; if (!meow_ft_loaded()) { meow_ui_text(x, y, utf8, px, color); /* 降级位图字库(meow_font/5x7 兜底),不出豆腐 */ return -1; } int baseline = y + meow_ft_ascender(px); int pen = 0; const unsigned char *p = (const unsigned char *)utf8; int cp; while (*p && (cp = ft_utf8_next(&p)) > 0) { int adv = 0, bw = 0, bh = 0, left = 0, top = 0; const uint8_t *bmp = NULL; if (meow_ft_glyph(px, (uint32_t)cp, &adv, &bmp, &bw, &bh, &left, &top) != 0) break; if (bmp && bw > 0 && bh > 0) { int bx = x + pen + left; int ty = baseline - top; for (int row = 0; row < bh; row++) { const uint8_t *src = bmp + (size_t)row * (size_t)bw; int py = ty + row; for (int col = 0; col < bw; col++) if (src[col]) meow_gfx_ft_blend_px(bx + col, py, color, (int)src[col]); } } pen += adv; } if (pen > 0) meow_gfx_ft_dirty(x, y, pen, meow_ft_line_height(px)); return 0; } int meow_ft_width(const char *utf8, int px) { if (!utf8) return 0; if (!meow_ft_loaded()) return meow_ui_text_width(utf8, px); /* 降级位图字库 */ int total = 0; const unsigned char *p = (const unsigned char *)utf8; int cp; while (*p && (cp = ft_utf8_next(&p)) > 0) { int a = 0; if (meow_ft_advance(px, (uint32_t)cp, &a) != 0) continue; total += a; } return total; }