diff --git a/.gitignore b/.gitignore index 725c738..8ef5da4 100644 --- a/.gitignore +++ b/.gitignore @@ -81,6 +81,11 @@ patch_v2/ncm_app/vendor/* patch_v2/ncm_app/out/ patch_v2/sdk/test_sdk_fixes +# SDK 矢量字(§10.1):FreeType vendor 源码/构建产物(体积大;版本 2.14.3,build_sdk.sh 可复现 +# 拉取+裁剪构建 -DFT_DISABLE_* 静态 musl)与宿主测试二进制 +patch_v2/sdk/vendor/ +patch_v2/sdk/test_meow_ui_ft + # 诊断工具构建产物(源码入库,二进制免) patch_v2/tools/sndprobe patch_v2/tools/tonepcm diff --git a/MEOW_APP_SDK.md b/MEOW_APP_SDK.md index f4e0799..baf7523 100644 --- a/MEOW_APP_SDK.md +++ b/MEOW_APP_SDK.md @@ -27,7 +27,7 @@ | `meow_ui_log.h` | 应用日志 | §9 | | `meow_ui_notify.h` | 应用通知 | §9.1 | | `meow_ui_config.h` | 应用配置读写 | §9.2 | -| `meow_ui_font.h` | 字体 | §5.7 卡片字体列表 + §10 字体分层 | +| `meow_ui_font.h` | 字体 | §5.7 卡片字体列表 + §10 字体分层 + §10.1 矢量字 `meow_ft_*` | | `meow_ui_audio.h` | 独占音频客户端 | §11 | | `meow_ui_str.h` | 字符串资源/i18n | §13 `meow_str`/`meow_strf`/`meow_str_set_locale` | @@ -1210,9 +1210,11 @@ patch_v2/sdk/ # SDK 源码 ├── stb_image.h / stb_image.c # 单头文件(vendor) patch_v2/meow_launcherd/meow_font.a # 复用(静态链接) ``` -构建出 `patch_v2/sdk/meow_ui.a`;应用编译链(zig-mipsel-cc)额外链 `meow_ui.a meow_font.a libz.a -lpthread`。 +构建出 `patch_v2/sdk/meow_ui.a`;应用编译链(zig-mipsel-cc)额外链 `meow_ui.a meow_font.a libz.a -lpthread`(`meow_font.a` 已内联进 `meow_ui.a`,故实际只需 `meow_ui.a libz.a -lpthread`)。 -> **构建耦合(已知设计,非缺陷)**:`build_sdk.sh` 用 `-I../meow_launcherd` 引 `meow_ipc.h`(`meow_notify_ipc.c` 复用共享协议头,同一份、杜绝两端漂移),故 SDK 编译期依赖 launcher 头、**非独立库**;**运行时无碍** —— App 只链 `meow_ui.a`(含 meow_font.o),不链入 launcher 符号,故 app 侧无 launcher 依赖。 +> **构建耦合(已知设计,非缺陷)**:`build_sdk.sh` 用 `-I../meow_launcherd` 引 `meow_ipc.h`(`meow_notify_ipc.c` 复用共享协议头,同一份、杜绝两端漂移),故 SDK 编译期依赖 launcher 头、**非独立库**;**运行时无碍** —— App 只链 `meow_ui.a`(含 meow_font.o + freetype .o + meow_ui_ft.o),不链入 launcher 符号,故 app 侧无 launcher 依赖。 +> +> **矢量字(§10.1)不改变 App 链接命令**:FreeType 裁剪后 .o 编入 `meow_ui.a`(`build_sdk.sh` vendor 拉取 + `-DFT_DISABLE_*` 裁剪,静态 musl);App 不调 `meow_ft_*` → `meow_ui_ft.o` 不拉取 → freetype 零增量;调 `meow_ft_*` → 自动拉入,App 无需额外 `-l` 任何 freetype/zlib/png 库。 **app.json 扩展字段**(向后兼容,缺省即旧行为): ```json @@ -1242,6 +1244,42 @@ patch_v2/meow_launcherd/meow_font.a # 复用(静态链接) - **B2(衔接阶段 D)**:输入/导航后端换成 IPC + M3 导航栈,应用框架字段 `framework=1` 的应用自动升级为"导航栈承载",裸应用保持兼容。 - **依赖**:`meow_present/back_buf/dirty`(M2.16 地基,✅)、`meow_font`(✅)、`blend565`(✅)——B1 全部基于现有能力,**无需新的硬件依赖**;仅新增 stb_image(单头文件)与输入线程。 +### 10.1 矢量字(`meow_ft_*`,2026-09-02 追加) + +**定位(SDK 演进的最后一块)**:SDK 位图字库(`meow_font`)只有**离散字号档位**,且全字形 atlas 稳态 17.3MiB(`FONT_MEMORY_DISCIPLINE.md` 实测)在 27MB 整机装不下 → 大字库/大字号场景必然出豆腐。阅读器 R1/R2 的 FreeType 管线已在真机验证(MiSans OTF 渲染,见 `READER_ENGINE_NOTES §5`)。本组 API 把该管线**上收 SDK**,让任意 App 用上**无级字号 + CJK 全字形**(解决豆腐类问题的根)。 + +**与位图字库并存(关键约定)**:App **显式**调用 `meow_ft_*` 才走矢量;既有 `meow_ui_text` 路径**一字不改**(默认仍是 `meow_font`)。`meow_ft_*` 全组是**追加 API**,不删改任何既有签名。 + +**API(`include/meow_ui_font.h`,公共)**: + +```c +int meow_ft_load(const char *path); /* 载入 TTF/OTF;0=成功;重载清缓存 */ +void meow_ft_unload(void); /* 释放矢量字体(可再 load) */ +int meow_ft_loaded(void); /* 1=已载入;0=text/width 降级位图 */ +int meow_ft_text(int x, int y, const char *utf8, int px, uint32_t color); + /* 矢量渲染;0=矢量;-1=无字体→已降级位图 */ +int meow_ft_width(const char *utf8, int px); /* 矢量宽度(px,advance 累加,不栅格化) */ +int meow_ft_line_height(int px); /* ascender - descender + 1 */ +int meow_ft_ascender(int px); int meow_ft_descender(int px); +int meow_ft_cache_set_slots(int slots); /* glyph LRU 容量(默认 512;256 省内存 / 1024 提命中) */ +int meow_ft_cache_slots(void); int meow_ft_cache_used(void); +``` + +- 坐标约定:`(x,y)=行首升线顶(同 meow_ui_text);baseline = y + meow_ft_ascender(px)`;px=字号(无级);color=24-bit RGB(内部按 back_buf 底色逐像素 blend565 反锯齿)。 +- 渲染/测宽:`meow_ft_text`/`meow_ft_width` 逐码点(UTF-8),从 LRU 缓存取字形(**灰阶 `FT_LOAD_RENDER|FT_LOAD_NO_HINTING|FT_LOAD_TARGET_LIGHT`**,按当前 back_buf 底色 blend565);`meow_ft_width` 用 `FT_Load_Glyph` 不 RENDER(`FT_LOAD_NO_HINTING|FT_LOAD_TARGET_LIGHT`),不占 LRU 槽。 + +**降级回退(绝不豆腐不崩)**:加载失败 / 无卡 / 无字体文件 / 未调用 `meow_ft_load` → `meow_ft_loaded()==0`;此时 `meow_ft_text` **自动改走** `meow_ui_text`(位图字库 → 5x7 兜底),`meow_ft_width/line_height` 降级为位图字库度量,返回 `-1` 供 App 感知。App 若坚持 vector(缺字时)应先用 `meow_ft_loaded()` 判可用性。 + +**内存纪律**(目标单 App **≤1.5MB 增量**): +- `FT_New_Face(path)` **按需读文件**(`stream->base=NULL`,不整文件载入 → 零大块 anon;TTF/OTF disk-backed,可被内核回收重读)。**不做** mmap 后 `FT_New_Memory_Face`(会把整文件映射进地址空间)。 +- anon 只含 FT 结构(~几十 KB)+ glyph LRU 位图。**glyph LRU 512 默认(≈0.46MB:28px CJK 约 900B/字),可配 256/1024**;容量设小/换字号/换字体时清空重建(key=`(px<<32)|cp`,不同字号可共存)。 +- **增量只发生在 App 调用 `meow_ft_*` 时**:`meow_ui_ft.o` 是 meow_ui.a 独立成员,链接器仅在 App 引用 `meow_ft_*` 符号时拉取它及随之的 FreeType .o;不调 `meow_ft_*` 的 App **零增量**(`meow_ui_ft.o`/freetype 不拉入)。已验证:不调 ft 的 App 链接产物无任何 FT 符号。 +- FreeType 走 `sdk/vendor/`(**gitignore**,`build_sdk.sh` 可复现拉取 + 裁剪构建:`-DFT_DISABLE_ZLIB/BZIP2/PNG/HARFBUZZ/BROTLI=TRUE`,静态 musl);无需 zlib/png,App 链接命令**不变**(仍 `meow_ui.a libz.a -lpthread`)。 + +**构建**:`build_sdk.sh` 增加 §0 FreeType vendor 拉取 + 裁剪构建(产物 `sdk/vendor/freetype-build/libfreetype.a`,其 .o 编入 meow_ui.a)。SDK 自身 `-Os -static -Wall -Wextra` 交叉 0 warning(clang 不报既有 gcc 的 misleading-indentation)。 + +**License**:FreeType 2.14.3(FreeType License,BSD 风格宽松许可;静态链入闭源固件无 LGPL 式 relink 义务,与阅读器 vendor 同源同版本)。NOTICE 同 `patch_v2/reader_app/NOTICE` 的 FreeType 条目。 + --- ## 11. 硬件约束(务必让应用作者知道) diff --git a/patch_v2/sdk/build_sdk.sh b/patch_v2/sdk/build_sdk.sh index b1ded10..e900bd4 100755 --- a/patch_v2/sdk/build_sdk.sh +++ b/patch_v2/sdk/build_sdk.sh @@ -1,20 +1,50 @@ #!/bin/bash # build_sdk.sh — 构建 meow_ui.a(mipsel 静态库,含 meow_font.o 内联打包, # 应用只需链 meow_ui.a + libz.a + -lpthread)。 +# +# §10.1 矢量字:FreeType vendor 进 sdk/vendor/(gitignore),本脚本可复现「拉取 + 裁剪构建」: +# 禁 zlib/png/brotli/bzip2/harfbuzz(-DFT_DISABLE_*),静态 musl(zig-mipsel-cc); +# 产物 sdk/vendor/freetype-build/libfreetype.a,其 .o 编入 meow_ui.a —— 应用只链 meow_ui.a +# 即获得矢量字能力;不调 meow_ft_* 的 App 链接器不会拉取 freetype 成员(零增量)。 set -e ROOT="$HOME/Project/R3ProII" cd "$(dirname "$0")" CC="$ROOT/.tools/zig-mipsel-cc" AR="${AR:-/usr/bin/ar}" # 系统 GNU ar 即可归档 mipsel ELF 对象(架构无关) +# ---------- 0) FreeType vendor 拉取 + 裁剪构建(可复现) ---------- +FT_VER="2.14.3" +FT_SRC="vendor/freetype-src" +FT_BUILD="vendor/freetype-build" +fetch_freetype() { + if [ -n "$SDK_FREETYPE_SRC" ] && [ -f "$SDK_FREETYPE_SRC/CMakeLists.txt" ]; then + rm -rf "$FT_SRC"; cp -r "$SDK_FREETYPE_SRC" "$FT_SRC" + elif [ ! -d "$FT_SRC" ]; then + mkdir -p vendor + ( cd vendor && curl -fsSL -o ft.tar.xz \ + "https://download.savannah.gnu.org/releases/freetype/freetype-$FT_VER.tar.xz" \ + && mkdir -p "$FT_SRC" && tar xf ft.tar.xz -C "$FT_SRC" --strip-components=1 && rm ft.tar.xz ) + fi + [ -f "$FT_SRC/CMakeLists.txt" ] || { echo "freetype 源码不可用"; exit 1; } +} +fetch_freetype +if [ ! -f "$FT_BUILD/libfreetype.a" ] || [ -n "$FORCE_FT" ]; then + rm -rf "$FT_BUILD" + cmake -S "$FT_SRC" -B "$FT_BUILD" -DCMAKE_SYSTEM_NAME=Linux -DCMAKE_C_COMPILER="$CC" \ + -DCMAKE_TRY_COMPILE_TARGET_TYPE=STATIC_LIBRARY -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF \ + -DFT_DISABLE_ZLIB=TRUE -DFT_DISABLE_BZIP2=TRUE -DFT_DISABLE_PNG=TRUE -DFT_DISABLE_HARFBUZZ=TRUE -DFT_DISABLE_BROTLI=TRUE >/dev/null + cmake --build "$FT_BUILD" -j"$(nproc)" >/dev/null 2>&1 +fi +FT_INCS="-I$FT_BUILD -I$FT_BUILD/include -I$FT_SRC/include" + rm -f meow_ui.a *.o # -I../meow_launcherd:meow_notify_ipc.c 复用共享协议头 meow_ipc.h(同一份、杜绝两端漂移) -OBJS="meow_ui_gfx meow_ui_log meow_ui_notify meow_notify_ipc meow_ui_img meow_ui_widget meow_ui_input meow_ui_theme meow_ui_ime meow_pinyin meow_ui_sys meow_ui_vol meow_ui_activity meow_ui_global meow_ui_dialog meow_ui_config meow_ui_draw meow_ui_view meow_ui_nav meow_ui_media meow_ui_audio stb_image meow_ui_box meow_ui_anim meow_ui_scroll meow_ui_str" +OBJS="meow_ui_gfx meow_ui_log meow_ui_notify meow_notify_ipc meow_ui_img meow_ui_widget meow_ui_input meow_ui_theme meow_ui_ime meow_pinyin meow_ui_sys meow_ui_vol meow_ui_activity meow_ui_global meow_ui_dialog meow_ui_config meow_ui_draw meow_ui_view meow_ui_nav meow_ui_media meow_ui_audio stb_image meow_ui_box meow_ui_anim meow_ui_scroll meow_ui_str meow_ui_ft" CFLAGS="-Os -static -I$ROOT/patch_v2/meow_launcherd" for f in $OBJS; do echo "== cc $f ==" - "$CC" $CFLAGS -c "$f.c" -o "$f.o" + "$CC" $CFLAGS $FT_INCS -c "$f.c" -o "$f.o" done # meow_font.o(复用 launcher 字库,打进 meow_ui.a → 应用只链一个库) @@ -26,6 +56,14 @@ rm -rf "$TMP" rm -f meow_ui.a for f in $OBJS; do "$AR" rcs meow_ui.a "$f.o"; done "$AR" rcs meow_ui.a meow_font.o + +# FreeType .o 编入 meow_ui.a(App 不调 meow_ft_* → 链接器不拉取,零增量)。 +FT_LIB="$PWD/$FT_BUILD/libfreetype.a" # 绝对路径(待会 cd 进临时目录,相对路径会失效) +FT_O="$(mktemp -d)" +( cd "$FT_O" && "$AR" x "$FT_LIB" && ls *.o >/dev/null ) +for o in "$FT_O"/*.o; do "$AR" rcs meow_ui.a "$o"; done +rm -rf "$FT_O" + echo "== meow_ui.a ==" ls -la meow_ui.a "$AR" t meow_ui.a diff --git a/patch_v2/sdk/include/meow_ui_font.h b/patch_v2/sdk/include/meow_ui_font.h index 87f29fb..63e22c2 100644 --- a/patch_v2/sdk/include/meow_ui_font.h +++ b/patch_v2/sdk/include/meow_ui_font.h @@ -8,6 +8,8 @@ #ifndef MEOW_UI_FONT_H #define MEOW_UI_FONT_H +#include /* uint32_t(meow_ft_text color)—— 子头自洽、可单独 include */ + #ifdef __cplusplus extern "C" { #endif @@ -70,6 +72,46 @@ int meow_font_set_tier(int tier); /* 当前字体档(初始 = MEOW_FONT_TIER_COMMON;meow_ui_init 时读 MEOW_FONT_TIER env 对齐)。 */ int meow_font_tier(void); +/* ============================================================ */ +/* §10.1 矢量字(FreeType,2026-09-02 追加)—— 上收阅读器管线到 SDK */ +/* ============================================================ */ +/* 背景:SDK 位图字库只有离散字号档位(meow_font);阅读器 R1/R2 的 FreeType 管线已在真机 + * 验证(MiSans OTF 渲染)。本组 API 把矢量字能力上收 SDK,任何 App 可用:**无级字号 + CJK + * 全字形**——这是解决大字库豆腐类问题的根(位图全字形 atlas 稳态 17.3MiB 装不下,矢量按需 + * 读文件 + LRU 缓存字形)。与位图字库**并存**:App **显式**调用 meow_ft_* 才走矢量;既有 + * meow_ui_text 路径零改动(默认仍是 meow_font)。 */ +/* 内存纪律(对齐 READER_ENGINE_NOTES §5.1/§5.4): + * - FT_New_Face(path) 按需读文件(不整载 → 零大块 anon;文件 disk-backed 可回收重读)。 + * - glyph LRU 512(默认,~0.46MB:28px CJK 约 900B/字),可配 256 省内存 / 1024 提命中。 + * - 目标单 App 增量 ≤1.5MB(FreeType 本体按需拉入,App 不调 meow_ft_* 则零增量)。 */ + +/* 载入矢量字体文件(TTF/OTF 绝对路径)。0=成功;重载会先释放旧面 + 清空 glyph 缓存。 + * 失败(路径无效/损坏/无 FreeType)返回负值,meow_ft_loaded()==0,后续 text/width 自动降级 + * 位图字库(meow_font),绝不豆腐不崩。 */ +int meow_ft_load(const char *path); +/* 释放矢量字体(保留 FreeType 运行时可再载;再调 meow_ft_load 即可)。 */ +void meow_ft_unload(void); +/* 当前是否已载入矢量字体(0=无 → text/width 走位图字库降级)。 */ +int meow_ft_loaded(void); + +/* 矢量渲染文本:(x,y)=行首升线顶(同 meow_ui_text;baseline = y + meow_ft_ascender(px)); + * 颜色为 24-bit RGB(内部按当前 back_buf 底色逐像素 blend565 反锯齿);px=字号(无级)。 + * 返回 0=矢量渲染成功;-1=无矢量字体 → 已自动降级 meow_ui_text(位图字库,不出豆腐); + * -2=未初始化/空串。 */ +int meow_ft_text(int x, int y, const char *utf8, int px, uint32_t color); +/* 矢量文本宽度(px;advance 累加,不栅格化、不占 LRU 槽);无矢量字体 → 降级 meow_ui_text_width。 */ +int meow_ft_width(const char *utf8, int px); +/* 行高 / 上伸 / 下伸(px;无矢量字体时 line_height 返回近似、ascender=px、descender=0)。 */ +int meow_ft_line_height(int px); +int meow_ft_ascender(int px); +int meow_ft_descender(int px); + +/* glyph LRU 容量:默认 512;设 256 省内存 / 1024 提命中(越界 clamp 1..4096,实际生效)。 */ +int meow_ft_cache_set_slots(int slots); +int meow_ft_cache_slots(void); +/* 当前 LRU 占用条目数(宿主单测 / 诊断用)。 */ +int meow_ft_cache_used(void); + #ifdef __cplusplus } #endif diff --git a/patch_v2/sdk/meow_ui.h b/patch_v2/sdk/meow_ui.h index 0e9cd19..5a9f3ea 100644 --- a/patch_v2/sdk/meow_ui.h +++ b/patch_v2/sdk/meow_ui.h @@ -32,7 +32,7 @@ * include/meow_ui_log.h 应用日志(§9) * include/meow_ui_notify.h 应用通知(§9.1) * include/meow_ui_config.h 应用配置读写(§9.2) - * include/meow_ui_font.h 字体列表(§5.7)+ 字体分层(§10) + * include/meow_ui_font.h 字体列表(§5.7)+ 字体分层(§10)+ 矢量字 meow_ft_*(§10.1) * include/meow_ui_audio.h 独占音频客户端(§11) * include/meow_ui_box.h 线性布局引擎(§12.1:meow_box,固定/weight/wrap/对齐/嵌套) * include/meow_ui_anim.h 动画基座+UI 节拍器(§12.2:tween/easing/池+脏区联动) diff --git a/patch_v2/sdk/meow_ui_ft.c b/patch_v2/sdk/meow_ui_ft.c new file mode 100644 index 0000000..98c9129 --- /dev/null +++ b/patch_v2/sdk/meow_ui_ft.c @@ -0,0 +1,289 @@ +/* + * 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; +} diff --git a/patch_v2/sdk/meow_ui_gfx.c b/patch_v2/sdk/meow_ui_gfx.c index 23e4b33..d805725 100644 --- a/patch_v2/sdk/meow_ui_gfx.c +++ b/patch_v2/sdk/meow_ui_gfx.c @@ -830,6 +830,17 @@ int meow_ui_text_height(int size) return 7 * fallback_scale(size); } +/* ================= 矢量字(FreeType)渲染薄封装(§10.1) ================= + * meow_ft_text 在 meow_ui_ft.c(独立 .o,App 显式调 meow_ft_* 才拉取整条 FreeType 依赖), + * 它经本处两个薄封装写进 back_buf:blend 一个灰度像素(过 clip)、标一块脏区。 + * 这两函数在 gfx.o 里(任何 App 本就拉取 gfx.o),ft.o 引用它们不引入新的未拉入成员, + * 从而保证「不调 meow_ft_* 的 App 零增量」(freetype .o 不会被拉入)。 */ +void meow_gfx_ft_blend_px(int x, int y, uint32_t color, int alpha) +{ put_px_blend(x, y, color, alpha); } + +void meow_gfx_ft_dirty(int x, int y, int w, int h) +{ dirty_add(x, y, w, h); } + /* ================= RGBA blit(图片层用) ================= */ void meow_gfx_blit_rgba(int x, int y, int w, int h, const uint8_t *rgba, int alpha) { diff --git a/patch_v2/sdk/meow_ui_internal.h b/patch_v2/sdk/meow_ui_internal.h index 57d3dc6..04468fd 100644 --- a/patch_v2/sdk/meow_ui_internal.h +++ b/patch_v2/sdk/meow_ui_internal.h @@ -159,6 +159,20 @@ unsigned long long meow_gfx_overlay_suppressed_count(void); /* 中文/日文字库(mfont)是否已加载(状态栏「蓝牙」CJK 与回退 "BT" 二选一)。 */ int meow_gfx_mfont_ready(void); +/* ---- 矢量字(FreeType)渲染薄封装(meow_ui_gfx.c 提供 / meow_ui_ft.c 使用)---- + * meow_ui_ft.o 是独立成员:App 显式调 meow_ft_* 才被拉取,随之才拉入 freetype .o。 + * 为让「不调 meow_ft_* 的 App 零增量」(gfx.o 不引用 meow_ui_ft.o / FT 符号),gfx.o 只 + * 提供下面两个绘制原语薄封装(blend 一个灰度像素进 back_buf、标一块脏区)。 */ +void meow_gfx_ft_blend_px(int x, int y, uint32_t color, int alpha); +void meow_gfx_ft_dirty(int x, int y, int w, int h); + +/* ---- 矢量字(FreeType)字形访问(meow_ui_ft.c 提供;宿主单测直接调)---- + * 返回 0=成功;-1=无已载字体(调用方应降级位图字库)。位图归 LRU 缓存所有,本次用完前 + * 勿再调 meow_ft_glyph(同字号新光栅化可能逐出旧槽)。 */ +int meow_ft_glyph(int px, uint32_t cp, int *adv_x, + const uint8_t **bmp, int *bw, int *bh, int *left, int *top); +int meow_ft_advance(int px, uint32_t cp, int *adv_x); /* 仅测宽不栅格化、不占 LRU 槽 */ + /* ---- 输入层(meow_ui_input.c)---- */ /* 语义事件:由输入线程解析后压入队列,run/IME 循环弹出。 */ #define MEOW_EV_KEY 1 /* a=keycode, b=1 按下 / 0 抬起 */ diff --git a/patch_v2/sdk/test_meow_ui_ft.c b/patch_v2/sdk/test_meow_ui_ft.c new file mode 100644 index 0000000..363d2f7 --- /dev/null +++ b/patch_v2/sdk/test_meow_ui_ft.c @@ -0,0 +1,99 @@ +/* test_meow_ui_ft.c — SDK 矢量字(meow_ft_*,§10.1)宿主单测。 + * 覆盖任务要求的五项:加载 / 渲染像素非空 / 宽度测量 / LRU 淘汰 / 降级回退。 + * + * 编译(宿主 gcc,链真实 gfx/sys/theme/vol/log/notify_ipc + 打桩 font/global + meow_ui_ft + 宿主 freetype): + * gcc -o test_meow_ui_ft test_meow_ui_ft.c test_host_stub.c \ + * meow_ui_gfx.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c meow_notify_ipc.c \ + * meow_ui_ft.c \ + * -I. -Iinclude -I../meow_launcherd -DMEOW_UI_HOST_TEST -pthread vendor/freetype-build-host/libfreetype.a + * 用法:./test_meow_ui_ft [font.otf] (缺省用仓库 font/src/MiSans-Regular.otf) + */ +#include "meow_ui.h" +#include "meow_ui_internal.h" /* meow_ft_glyph(LRU 淘汰测试直接用底层光栅化) */ +#include +#include +#include + +static int fails = 0; +#define CHECK(c, m) do { if (c) printf(" ok %s\n", m); else { printf("FAIL %s\n", m); fails++; } } while (0) + +/* 统计 back_buf 中 != bg 的像素数(RGB565 host 16bpp)。 */ +static int count_changed(unsigned char *fb, int pitch, int bg565) +{ + int n = 0; + for (int y = 0; y < 720; y++) { + uint16_t *row = (uint16_t *)(fb + (size_t)y * (size_t)pitch); + for (int x = 0; x < 480; x++) if (row[x] != (uint16_t)bg565) n++; + } + return n; +} + +int main(int argc, char **argv) +{ + const char *font = (argc > 1) ? argv[1] : "/home/Aska/Project/R3ProII/font/src/MiSans-Regular.otf"; + meow_ui_opts o = { 480, 720 }; + CHECK(meow_ui_init(&o) == 0, "meow_ui_init(480x720)"); + int pitch = meow_ui_back_buf_pitch(); + CHECK(pitch == 480 * 2, "back_buf pitch=960"); + CHECK(meow_ui_back_buf_bpp() == 16, "bpp=16"); + unsigned char *fb = meow_ui_back_buf(); + CHECK(fb != NULL, "back_buf non-null"); + + /* ---- 1) 加载 ---- */ + CHECK(!meow_ft_loaded(), "no font loaded initially"); + CHECK(meow_ft_load("nonexistent-font.otf") != 0, "load(bad path) fails"); + CHECK(!meow_ft_loaded(), "bad load leaves unloaded"); + CHECK(meow_ft_load(font) == 0, "meow_ft_load(MiSans)"); + CHECK(meow_ft_loaded(), "meow_ft_loaded after load"); + CHECK(meow_ft_cache_slots() == 512, "default LRU slots=512"); + + /* ---- 2) 渲染像素非空(矢量) ---- */ + meow_ui_clear(0xFFFFFF); /* 白底 */ + int r = meow_ft_text(10, 50, "你好A", 20, 0x000000); + CHECK(r == 0, "meow_ft_text(vec) returns 0"); + int vec_dark = count_changed(fb, pitch, 0xFFFF); + printf(" vec dark=%d\n", vec_dark); + CHECK(vec_dark > 50, "vector render produced non-empty pixels"); + + /* ---- 3) 宽度测量 ---- */ + int w20 = meow_ft_width("你好A", 20); + int w10 = meow_ft_width("你好A", 10); + printf(" width(你好A,20)=%d width(...,10)=%d\n", w20, w10); + CHECK(w20 > 0, "vec width(20)>0"); + CHECK(w20 > w10, "larger px -> larger width"); + CHECK(meow_ft_width("", 20) == 0, "empty string width=0"); + CHECK(meow_ft_width("你", 20) > 0, "CJK width>0"); + /* 单 CJK 字 width ≈ 字号(MiSans CJK 满宽 advance) */ + CHECK(meow_ft_width("你", 20) >= 17 && meow_ft_width("你", 20) <= 23, "CJK adv ~20"); + + /* ---- 4) LRU 淘汰 ---- */ + int got = meow_ft_cache_set_slots(256); + CHECK(got == 256, "set LRU slots=256"); + CHECK(meow_ft_cache_slots() == 256, "LRU slots now 256"); + for (int i = 0; i < 300; i++) { + int adv = 0, bw = 0, bh = 0, left = 0, top = 0; + const uint8_t *bmp = NULL; + meow_ft_glyph(20, 0x4E00u + (uint32_t)i, &adv, &bmp, &bw, &bh, &left, &top); + } + int used = meow_ft_cache_used(); + printf(" lru used=%d slots=256\n", used); + CHECK(used <= 256, "lru used <= 256 after 300 requests"); + CHECK(used >= 1, "lru used >= 1"); + meow_ft_cache_set_slots(512); /* 还原默认,供后续 */ + + /* ---- 5) 降级回退(无矢量字体 → 位图字库/5x7,不出豆腐) ---- */ + meow_ft_unload(); + CHECK(!meow_ft_loaded(), "after unload not loaded"); + meow_ui_clear(0xFFFFFF); + r = meow_ft_text(10, 50, "X", 20, 0x000000); + CHECK(r == -1, "meow_ft_text degrades (returns -1)"); + int deg_dark = count_changed(fb, pitch, 0xFFFF); + printf(" degrade dark=%d\n", deg_dark); + CHECK(deg_dark > 0, "degrade still draws (5x7/bitmap, no tofu crash)"); + /* 降级宽度 = 位图字库宽度(宿主无位图库 → 5x7 兜底:len*6*scale(20=2)) */ + CHECK(meow_ft_width("X", 20) == 12, "degrade width via bitmap (5x7) = 12"); + + meow_ui_shutdown(); + printf("test_meow_ui_ft: %s\n", fails ? "FAIL" : "PASS"); + return fails ? 1 : 0; +}