问题:SDK 位图字库(meow_font)只有离散字号档位,全字形 atlas 稳态 17.3MiB (27MB 整机装不下)→ 大字库/大字号必出豆腐。阅读器 R1/R2 的 FreeType 管线已真机验证 (MiSans OTF),本 commit 将其上收 SDK,让任意 App 用上无级字号 + CJK 全字形。 设计(追加式,与位图字库并存): - meow_ui_ft.c(独立 .o):meow_ft_load/unload/loaded/text/width/line_height/ ascender/descender/cache_set_slots/cache_slots/cache_used。底层按 READER_ENGINE_NOTES §5: FT_New_Face(path) 按需读文件(零整载 anon);灰阶 FT_LOAD_RENDER|NO_HINTING|TARGET_LIGHT; glyph LRU key=(px<<32)|cp,默认 512(~0.46MB),可配 256/1024;测宽不栅格化。 - meow_ui_gfx.c 只加两个薄封装 meow_gfx_ft_blend_px/dirty(写 back_buf + 标脏), 使 meow_ui_ft.o 不引入新依赖——「不调 meow_ft_* 的 App 零增量」(freetype 不拉入,已验证)。 - 降级:加载失败/无卡 → meow_ft_loaded()==0 → meow_ft_text 自动改走 meow_ui_text(位图字库, 5x7 兜底),绝不豆腐不崩;meow_ft_width/line_height 降级位图字库度量。 - FreeType 2.14.3 vendor 进 patch_v2/sdk/vendor/(gitignore)+ build_sdk.sh 可复现拉取+裁剪 构建(-DFT_DISABLE_ZLIB/BZIP2/PNG/HARFBUZZ/BROTLI=TRUE,静态 musl),freetype .o 编入 meow_ui.a,App 链接命令不变(meow_ui.a libz.a -lpthread)。 验证: - 宿主 test_meow_ui_ft 5 项全 PASS(加载/渲染像素非空/宽度测量/LRU 淘汰/降级回退)。 - 全量 SDK 回归:widgets/sdk_fixes/dispatch/notif_gesture/overlay_suppress/listvirt/box/anim/ scroll/ime_fix/pinyin/notif_open/sb_collapse/theme/notify_ipc/audio_host/vol/str/config/ sdk_input_keys 全 PASS。 - 交叉 -Wall -Wextra 全 SDK TU 0 warning;qemu-mipsel 冒烟 PASS(mipsel FreeType 真光栅化)。 - meow_ui.a 新 md5 6ff5be9455f9e197b35fa120be012041(未重链不部署 App,四 App 免重链)。 MEOW_APP_SDK.md 补 §10.1 矢量字一节 + 公共头索引/§9 构建约定更新;.gitignore 忽略 patch_v2/sdk/vendor/ 与 test_meow_ui_ft 二进制。
290 lines
10 KiB
C
290 lines
10 KiB
C
/*
|
||
* 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 <ft2build.h>
|
||
#include FT_FREETYPE_H
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
#include <stdint.h>
|
||
|
||
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;
|
||
}
|