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 二进制。
This commit is contained in:
1 parent
121d30ec95
commit
54750a63c5
9 files changed
+542
-6
No files matched your search
@@ -81,6 +81,11 @@ patch_v2/ncm_app/vendor/*
|
|||||||
patch_v2/ncm_app/out/
|
patch_v2/ncm_app/out/
|
||||||
patch_v2/sdk/test_sdk_fixes
|
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/sndprobe
|
||||||
patch_v2/tools/tonepcm
|
patch_v2/tools/tonepcm
|
||||||
|
|||||||
+41
-3
@@ -27,7 +27,7 @@
|
|||||||
| `meow_ui_log.h` | 应用日志 | §9 |
|
| `meow_ui_log.h` | 应用日志 | §9 |
|
||||||
| `meow_ui_notify.h` | 应用通知 | §9.1 |
|
| `meow_ui_notify.h` | 应用通知 | §9.1 |
|
||||||
| `meow_ui_config.h` | 应用配置读写 | §9.2 |
|
| `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_audio.h` | 独占音频客户端 | §11 |
|
||||||
| `meow_ui_str.h` | 字符串资源/i18n | §13 `meow_str`/`meow_strf`/`meow_str_set_locale` |
|
| `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)
|
├── stb_image.h / stb_image.c # 单头文件(vendor)
|
||||||
patch_v2/meow_launcherd/meow_font.a # 复用(静态链接)
|
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 扩展字段**(向后兼容,缺省即旧行为):
|
**app.json 扩展字段**(向后兼容,缺省即旧行为):
|
||||||
```json
|
```json
|
||||||
@@ -1242,6 +1244,42 @@ patch_v2/meow_launcherd/meow_font.a # 复用(静态链接)
|
|||||||
- **B2(衔接阶段 D)**:输入/导航后端换成 IPC + M3 导航栈,应用框架字段 `framework=1` 的应用自动升级为"导航栈承载",裸应用保持兼容。
|
- **B2(衔接阶段 D)**:输入/导航后端换成 IPC + M3 导航栈,应用框架字段 `framework=1` 的应用自动升级为"导航栈承载",裸应用保持兼容。
|
||||||
- **依赖**:`meow_present/back_buf/dirty`(M2.16 地基,✅)、`meow_font`(✅)、`blend565`(✅)——B1 全部基于现有能力,**无需新的硬件依赖**;仅新增 stb_image(单头文件)与输入线程。
|
- **依赖**:`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. 硬件约束(务必让应用作者知道)
|
## 11. 硬件约束(务必让应用作者知道)
|
||||||
|
|||||||
@@ -1,20 +1,50 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# build_sdk.sh — 构建 meow_ui.a(mipsel 静态库,含 meow_font.o 内联打包,
|
# build_sdk.sh — 构建 meow_ui.a(mipsel 静态库,含 meow_font.o 内联打包,
|
||||||
# 应用只需链 meow_ui.a + libz.a + -lpthread)。
|
# 应用只需链 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
|
set -e
|
||||||
ROOT="$HOME/Project/R3ProII"
|
ROOT="$HOME/Project/R3ProII"
|
||||||
cd "$(dirname "$0")"
|
cd "$(dirname "$0")"
|
||||||
CC="$ROOT/.tools/zig-mipsel-cc"
|
CC="$ROOT/.tools/zig-mipsel-cc"
|
||||||
AR="${AR:-/usr/bin/ar}" # 系统 GNU ar 即可归档 mipsel ELF 对象(架构无关)
|
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
|
rm -f meow_ui.a *.o
|
||||||
|
|
||||||
# -I../meow_launcherd:meow_notify_ipc.c 复用共享协议头 meow_ipc.h(同一份、杜绝两端漂移)
|
# -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"
|
CFLAGS="-Os -static -I$ROOT/patch_v2/meow_launcherd"
|
||||||
for f in $OBJS; do
|
for f in $OBJS; do
|
||||||
echo "== cc $f =="
|
echo "== cc $f =="
|
||||||
"$CC" $CFLAGS -c "$f.c" -o "$f.o"
|
"$CC" $CFLAGS $FT_INCS -c "$f.c" -o "$f.o"
|
||||||
done
|
done
|
||||||
|
|
||||||
# meow_font.o(复用 launcher 字库,打进 meow_ui.a → 应用只链一个库)
|
# meow_font.o(复用 launcher 字库,打进 meow_ui.a → 应用只链一个库)
|
||||||
@@ -26,6 +56,14 @@ rm -rf "$TMP"
|
|||||||
rm -f meow_ui.a
|
rm -f meow_ui.a
|
||||||
for f in $OBJS; do "$AR" rcs meow_ui.a "$f.o"; done
|
for f in $OBJS; do "$AR" rcs meow_ui.a "$f.o"; done
|
||||||
"$AR" rcs meow_ui.a meow_font.o
|
"$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 =="
|
echo "== meow_ui.a =="
|
||||||
ls -la meow_ui.a
|
ls -la meow_ui.a
|
||||||
"$AR" t meow_ui.a
|
"$AR" t meow_ui.a
|
||||||
@@ -8,6 +8,8 @@
|
|||||||
#ifndef MEOW_UI_FONT_H
|
#ifndef MEOW_UI_FONT_H
|
||||||
#define MEOW_UI_FONT_H
|
#define MEOW_UI_FONT_H
|
||||||
|
|
||||||
|
#include <stdint.h> /* uint32_t(meow_ft_text color)—— 子头自洽、可单独 include */
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
@@ -70,6 +72,46 @@ int meow_font_set_tier(int tier);
|
|||||||
/* 当前字体档(初始 = MEOW_FONT_TIER_COMMON;meow_ui_init 时读 MEOW_FONT_TIER env 对齐)。 */
|
/* 当前字体档(初始 = MEOW_FONT_TIER_COMMON;meow_ui_init 时读 MEOW_FONT_TIER env 对齐)。 */
|
||||||
int meow_font_tier(void);
|
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
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -32,7 +32,7 @@
|
|||||||
* include/meow_ui_log.h 应用日志(§9)
|
* include/meow_ui_log.h 应用日志(§9)
|
||||||
* include/meow_ui_notify.h 应用通知(§9.1)
|
* include/meow_ui_notify.h 应用通知(§9.1)
|
||||||
* include/meow_ui_config.h 应用配置读写(§9.2)
|
* 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_audio.h 独占音频客户端(§11)
|
||||||
* include/meow_ui_box.h 线性布局引擎(§12.1:meow_box,固定/weight/wrap/对齐/嵌套)
|
* include/meow_ui_box.h 线性布局引擎(§12.1:meow_box,固定/weight/wrap/对齐/嵌套)
|
||||||
* include/meow_ui_anim.h 动画基座+UI 节拍器(§12.2:tween/easing/池+脏区联动)
|
* include/meow_ui_anim.h 动画基座+UI 节拍器(§12.2:tween/easing/池+脏区联动)
|
||||||
|
|||||||
@@ -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 <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;
|
||||||
|
}
|
||||||
@@ -830,6 +830,17 @@ int meow_ui_text_height(int size)
|
|||||||
return 7 * fallback_scale(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(图片层用) ================= */
|
/* ================= RGBA blit(图片层用) ================= */
|
||||||
void meow_gfx_blit_rgba(int x, int y, int w, int h, const uint8_t *rgba, int alpha)
|
void meow_gfx_blit_rgba(int x, int y, int w, int h, const uint8_t *rgba, int alpha)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -159,6 +159,20 @@ unsigned long long meow_gfx_overlay_suppressed_count(void);
|
|||||||
/* 中文/日文字库(mfont)是否已加载(状态栏「蓝牙」CJK 与回退 "BT" 二选一)。 */
|
/* 中文/日文字库(mfont)是否已加载(状态栏「蓝牙」CJK 与回退 "BT" 二选一)。 */
|
||||||
int meow_gfx_mfont_ready(void);
|
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)---- */
|
/* ---- 输入层(meow_ui_input.c)---- */
|
||||||
/* 语义事件:由输入线程解析后压入队列,run/IME 循环弹出。 */
|
/* 语义事件:由输入线程解析后压入队列,run/IME 循环弹出。 */
|
||||||
#define MEOW_EV_KEY 1 /* a=keycode, b=1 按下 / 0 抬起 */
|
#define MEOW_EV_KEY 1 /* a=keycode, b=1 按下 / 0 抬起 */
|
||||||
|
|||||||
@@ -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 <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
Reference in new issue
Block a user