Files
AskaEth 54750a63c5 feat(sdk): SDK 矢量字(meow_ft_*,§10.1)——上收阅读器 FreeType 管线
问题: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 二进制。
2026-09-02 09:51:23 +08:00

290 lines
10 KiB
C
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* 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;
}