WIP checkpoint (M2.53→M2.54 工作区): 字体归MeowOS/fonts+SDK跟随+多字体(进行中)+boot板载+状态栏28+亮度/音量修复+文档刷新

This commit is contained in:
MeowOS Dev committed 2026-08-30 03:27:24 +08:00
1 parent 245bee2946
commit 064635b3c4
44 files changed
+2834 -308

No files matched your search

+1
View File
@@ -211,6 +211,7 @@ def write_idx(path, bit_depth, faces, glyph_tabs):
tab_sorted = sorted(tab, key=lambda g: g[0])
buf = bytearray()
for cp, x, y, w, h, adv, dx, dy in tab_sorted:
dy = max(dy, 0) # 少数全角/符号字形墨迹略高于 la 锚点,得到负 dy;u8 仅能表达非负,钳到 0(整体下移 1-3px,肉眼不可见)
buf += struct.pack("<IHHHHHbB", cp, x, y, w, h, adv, dx, dy)
tables.append(buf)
cur += len(buf)
+238 -63
View File
@@ -11,6 +11,11 @@
*
* 绘制模型:drawtext(fb,x,y,utf8,color,size),(x,y)=行首升线顶部;baseline=y+ascent;
* 每字形 blit 于 (x+pen+dx, y+dy);pen 累加 advance。color 为 RGB565(R=bit15..11)。
*
* 多字体(M2.5x):槽位模型。slot 0 = meow_font_load() 装载的全局/默认字库(font.file);
* 额外字库经 meow_font_load_slot() 装载。meow_font_enable_multifont(1) 开启后,
* drawtext/width_text/line_height/... 逐字符 meow_font_class_of() 分类 → 用该类别
* 对应槽的字库取字形与度量;缺该类别/槽未载 → 回退 slot 0。关闭(默认)一律 slot 0。
*/
#include "meow_font.h"
@@ -71,17 +76,23 @@ typedef struct {
meow_glyph *glyphs; /* 按 cp 升序 */
} meow_face;
/* 一个槽 = 一套已装载的字库(一个 PNG plane + 若干 face)。 */
typedef struct {
unsigned fb_w, fb_h, fb_pitch;
size_t plane_rowbytes;
unsigned depth; /* 4 或 8 */
unsigned plane_w, plane_h; /* 画布尺寸 */
unsigned char *plane; /* 打包像素面 */
int face_count;
meow_face *faces;
} meow_state;
int loaded; /* 是否已装载 */
} meow_font_slot;
static meow_state g;
/* framebuffer 几何(全局,作用于所有槽;绘制前 meow_font_set_fb 设置一次)。 */
static unsigned g_fb_w, g_fb_h, g_fb_pitch;
static meow_font_slot g_slots[MEOW_MAX_SLOTS];
static int g_multifont = 0; /* 0=单字体(默认) */
static int g_class_slot[MEOW_FCLS_COUNT]; /* 每字符类 → 槽;默认全 0(跟随全局) */
/* PNG 内部为大端;idx(自定格式)为小端。 */
static uint32_t be32(const unsigned char *p) {
@@ -220,15 +231,16 @@ static unsigned char *png_decode_gray(const unsigned char *png, size_t len,
return out;
}
/* ---------------- idx 解析 ---------------- */
static int load_idx(const unsigned char *buf, size_t len) {
/* ---------------- idx 解析(写入指定槽) ---------------- */
static int load_idx(const unsigned char *buf, size_t len, int slot) {
meow_font_slot *st = &g_slots[slot];
if (len < 16 || memcmp(buf, "MFNI", 4) != 0) return -1;
unsigned depth = buf[6];
int nfaces = buf[7];
unsigned png_w = le32(buf + 8), png_h = le32(buf + 12);
(void)png_w; (void)png_h;
if (nfaces < 1 || nfaces > 32) return -1;
if (depth != g.depth) return -2; /* 位深须与 PNG 一致 */
if (depth != st->depth) return -2; /* 位深须与 PNG 一致 */
size_t face_off = 16;
meow_face *faces = (meow_face *)calloc((size_t)nfaces, sizeof(meow_face));
@@ -271,20 +283,21 @@ static int load_idx(const unsigned char *buf, size_t len) {
return -5;
}
}
g.faces = faces;
g.face_count = nfaces;
st->faces = faces;
st->face_count = nfaces;
return 0;
}
/* ---------------- 面/字形查找 ---------------- */
static meow_face *face_for_size(int size) {
if (g.face_count <= 0) return NULL;
/* ---------------- 面/字形查找(槽感知) ---------------- */
static meow_face *face_for_size(int slot, int size) {
meow_font_slot *st = &g_slots[slot];
if (st->face_count <= 0) return NULL;
meow_face *best = NULL;
int best_delta = -1;
for (int i = 0; i < g.face_count; i++) {
int d = g.faces[i].px - size;
for (int i = 0; i < st->face_count; i++) {
int d = st->faces[i].px - size;
if (d < 0) d = -d;
if (best_delta < 0 || d < best_delta) { best_delta = d; best = &g.faces[i]; }
if (best_delta < 0 || d < best_delta) { best_delta = d; best = &st->faces[i]; }
}
return best;
}
@@ -300,11 +313,12 @@ static meow_glyph *find_glyph(meow_face *f, uint32_t cp) {
return NULL;
}
/* ---------------- 像素采样(depth 感知) ---------------- */
static inline unsigned sample_px(unsigned x, unsigned y) {
if (x >= g.plane_w || y >= g.plane_h) return 0;
if (g.depth == 8) return g.plane[(size_t)y * g.plane_w + x];
unsigned char b = g.plane[((size_t)y * g.plane_w + x) >> 1];
/* ---------------- 像素采样(depth 感知,槽感知) ---------------- */
static inline unsigned sample_px(int slot, unsigned x, unsigned y) {
meow_font_slot *st = &g_slots[slot];
if (x >= st->plane_w || y >= st->plane_h) return 0;
if (st->depth == 8) return st->plane[(size_t)y * st->plane_w + x];
unsigned char b = st->plane[((size_t)y * st->plane_w + x) >> 1];
unsigned nh = (x & 1) ? (b & 0x0F) : (b >> 4);
return nh * 17;
}
@@ -320,10 +334,10 @@ static inline uint16_t blend565(uint16_t bg, uint16_t fg, unsigned a) {
return (uint16_t)((r << 11) | (gc << 5) | b);
}
/* ---------------- 字形 blit(含缩放、裁剪、alpha 混合) ---------------- */
static void blit_glyph(uint16_t *fb, meow_face *face, const meow_glyph *gr,
/* ---------------- 字形 blit(含缩放、裁剪、alpha 混合;槽感知) ---------------- */
static void blit_glyph(uint16_t *fb, int slot, meow_face *face, const meow_glyph *gr,
int pen_x, int y, uint16_t color, unsigned scaleq) {
int stride = (int)(g.fb_pitch / 2); /* uint16 元素每行数 */
int stride = (int)(g_fb_pitch / 2); /* uint16 元素每行数 */
/* 目标尺寸 = 源尺寸 * scaleq / 4096 */
unsigned gw, gh;
int dx, dy;
@@ -343,15 +357,15 @@ static void blit_glyph(uint16_t *fb, meow_face *face, const meow_glyph *gr,
for (unsigned oy = 0; oy < gh; oy++) {
int ay = py + (int)oy;
if (ay < 0) continue;
if (ay >= (int)g.fb_h) break;
if (ay >= (int)g_fb_h) break;
int sy = (int)((uint64_t)oy * sh / gh);
int base = face->plane_x + gr->x;
for (unsigned ox = 0; ox < gw; ox++) {
int ax = px + (int)ox;
if (ax < 0) continue;
if (ax >= (int)g.fb_w) break;
if (ax >= (int)g_fb_w) break;
int sx = (int)((uint64_t)ox * sw / gw);
unsigned a = sample_px((unsigned)(base + sx),
unsigned a = sample_px(slot, (unsigned)(base + sx),
(unsigned)(face->plane_y + gr->y + sy));
if (!a) continue;
size_t idx = (size_t)ay * (size_t)stride + (size_t)ax;
@@ -386,13 +400,13 @@ static uint32_t advance_for_missing(meow_face *f) {
}
static void draw_tofu(uint16_t *fb, int x, int y, int w, int h, uint16_t color) {
int stride = (int)(g.fb_pitch / 2);
int stride = (int)(g_fb_pitch / 2);
for (int oy = 0; oy < h; oy++) {
int ay = y + oy;
if (ay < 0 || ay >= (int)g.fb_h) break;
if (ay < 0 || ay >= (int)g_fb_h) break;
for (int ox = 0; ox < w; ox++) {
int ax = x + ox;
if (ax < 0 || ax >= (int)g.fb_w) break;
if (ax < 0 || ax >= (int)g_fb_w) break;
if (oy == 0 || oy == h - 1 || ox == 0 || ox == w - 1) {
size_t idx = (size_t)ay * (size_t)stride + (size_t)ax;
fb[idx] = blend565(fb[idx], color, 255);
@@ -401,23 +415,86 @@ static void draw_tofu(uint16_t *fb, int x, int y, int w, int h, uint16_t color)
}
}
/* ---------------- 字符分类器(multi_font 路由依据) ---------------- */
int meow_font_class_of(uint32_t cp) {
/* 数字 */
if (cp >= '0' && cp <= '9') return MEOW_FCLS_NUM;
/* 拉丁字母(基本拉丁) */
if ((cp >= 'A' && cp <= 'Z') || (cp >= 'a' && cp <= 'z')) return MEOW_FCLS_LATIN;
/* 半角标点/空格/控制符(ASCII 非字母数字) */
if (cp < 0x80) return MEOW_FCLS_LATIN_SYM;
/* 全角 / 中文标点 */
if ((cp >= 0x3000 && cp <= 0x303F) || /* CJK 符号与标点(含全角空格 U+3000) */
(cp >= 0xFF00 && cp <= 0xFFEF) || /* 半/全角形式(全角数字字母符号) */
cp == 0x2014 || cp == 0x2018 || cp == 0x2019 || /* — ‘’ */
cp == 0x201C || cp == 0x201D || cp == 0x2026 || /* “” … */
cp == 0x00B7) /* · */
return MEOW_FCLS_CJK_SYM;
/* CJK 表意文字 + 扩展 + 兼容表意 */
if ((cp >= 0x4E00 && cp <= 0x9FFF) ||
(cp >= 0x3400 && cp <= 0x4DBF) ||
(cp >= 0x20000 && cp <= 0x2A6DF) ||
(cp >= 0x2A700 && cp <= 0x2B73F) ||
(cp >= 0x2B740 && cp <= 0x2B81F) ||
(cp >= 0x2B820 && cp <= 0x2CEAF) ||
(cp >= 0x2CEB0 && cp <= 0x2EBEF) ||
(cp >= 0x30000 && cp <= 0x3134F) ||
(cp >= 0xF900 && cp <= 0xFAFF) ||
(cp >= 0x2F800 && cp <= 0x2FA1F))
return MEOW_FCLS_CJK;
return MEOW_FCLS_OTHER;
}
/* ---------------- 路由:一个 codepoint 应使用的槽(multi off → slot0) ---------------- */
static int route_slot(uint32_t cp) {
if (!g_multifont) return 0;
int cls = meow_font_class_of(cp);
if (cls < 0 || cls >= MEOW_FCLS_COUNT) return 0;
int s = g_class_slot[cls];
if (s >= 0 && s < MEOW_MAX_SLOTS && g_slots[s].loaded) return s;
return 0; /* 缺该类别字库/未装载 → 回退全局 slot0 */
}
/* 取某码位在路由落点槽内、对应最近字号的面 + scaleq;无可用字库返回 NULL。
* 顺带带回落点槽(供 blit 采样;不需要者传 NULL)。 */
static meow_face *routed_face(uint32_t cp, int size, unsigned *scaleq, int *slot_out) {
int slot = route_slot(cp);
if (slot_out) *slot_out = slot;
meow_font_slot *st = &g_slots[slot];
if (!st->loaded) return NULL;
meow_face *f = face_for_size(slot, size);
if (!f) return NULL;
if (scaleq) *scaleq = (size == f->px) ? 4096u : (unsigned)(((uint64_t)size << 12) / (uint64_t)f->px);
return f;
}
/* ---------------- 公共 API ---------------- */
void meow_font_set_fb(unsigned fb_w, unsigned fb_h, unsigned pitch_bytes) {
g.fb_w = fb_w; g.fb_h = fb_h; g.fb_pitch = pitch_bytes;
g_fb_w = fb_w; g_fb_h = fb_h; g_fb_pitch = pitch_bytes;
}
void meow_font_unload_slot(int slot) {
if (slot < 0 || slot >= MEOW_MAX_SLOTS) return;
meow_font_slot *st = &g_slots[slot];
if (st->faces) {
for (int i = 0; i < st->face_count; i++) free(st->faces[i].glyphs);
free(st->faces);
}
free(st->plane);
st->faces = NULL; st->plane = NULL; st->face_count = 0;
st->plane_w = st->plane_h = 0; st->plane_rowbytes = 0; st->depth = 0;
st->loaded = 0;
}
void meow_font_unload(void) {
if (g.faces) {
for (int i = 0; i < g.face_count; i++) free(g.faces[i].glyphs);
free(g.faces);
}
free(g.plane);
g.faces = NULL; g.plane = NULL; g.face_count = 0;
g.plane_w = g.plane_h = 0;
for (int i = 0; i < MEOW_MAX_SLOTS; i++) meow_font_unload_slot(i);
g_multifont = 0;
for (int i = 0; i < MEOW_FCLS_COUNT; i++) g_class_slot[i] = 0;
}
int meow_font_load(const char *png_path, const char *idx_path) {
meow_font_unload();
int meow_font_load_slot(int slot, const char *png_path, const char *idx_path) {
if (slot < 0 || slot >= MEOW_MAX_SLOTS) return -9;
meow_font_unload_slot(slot);
size_t plen = 0, ilen = 0;
unsigned char *png = read_file(png_path, &plen);
if (!png) return -1;
@@ -430,46 +507,91 @@ int meow_font_load(const char *png_path, const char *idx_path) {
free(png);
if (!plane) { free(idx); return -2; }
g.plane = plane;
g.depth = depth;
g.plane_w = w;
g.plane_h = h;
g.plane_rowbytes = rowbytes;
meow_font_slot *st = &g_slots[slot];
st->plane = plane;
st->depth = depth;
st->plane_w = w;
st->plane_h = h;
st->plane_rowbytes = rowbytes;
if (load_idx(idx, ilen) != 0) {
if (load_idx(idx, ilen, slot) != 0) {
free(idx);
meow_font_unload();
meow_font_unload_slot(slot);
return -3;
}
free(idx);
st->loaded = 1;
return 0;
}
int meow_font_load(const char *png_path, const char *idx_path) {
meow_font_unload(); /* 全局字库(重)装:清全部槽 + 复位多字体配置 */
return meow_font_load_slot(0, png_path, idx_path);
}
void meow_font_enable_multifont(int on) { g_multifont = on ? 1 : 0; }
void meow_font_set_class_slot(meow_fclass cls, int slot) {
if (cls < 0 || cls >= MEOW_FCLS_COUNT) return;
g_class_slot[cls] = (slot >= 0 && slot < MEOW_MAX_SLOTS && g_slots[slot].loaded) ? slot : 0;
}
int meow_font_has_size(int size) {
for (int i = 0; i < g.face_count; i++)
if (g.faces[i].px == size) return 1;
int lo = g_multifont ? 0 : 0;
int hi = g_multifont ? MEOW_MAX_SLOTS : 1;
for (int s = lo; s < hi; s++) {
meow_font_slot *st = &g_slots[s];
if (!st->loaded) continue;
for (int i = 0; i < st->face_count; i++)
if (st->faces[i].px == size) return 1;
}
return 0;
}
int meow_font_has_glyph(uint32_t cp) {
int slot = route_slot(cp);
meow_font_slot *st = &g_slots[slot];
if (!st->loaded) return 0;
for (int i = 0; i < st->face_count; i++)
if (find_glyph(&st->faces[i], cp)) return 1;
return 0;
}
int meow_font_ascent(int size) {
meow_face *f = face_for_size(size);
if (g_multifont) {
int best = 0;
for (int s = 0; s < MEOW_MAX_SLOTS; s++) {
meow_face *f = face_for_size(s, size);
if (f && f->ascent > best) best = f->ascent;
}
return best;
}
meow_face *f = face_for_size(0, size);
return f ? f->ascent : 0;
}
int meow_font_line_height(int size) {
meow_face *f = face_for_size(size);
if (g_multifont) {
int best = 0;
for (int s = 0; s < MEOW_MAX_SLOTS; s++) {
meow_face *f = face_for_size(s, size);
if (f) { int h = f->ascent + f->descent; if (h > best) best = h; }
}
return best;
}
meow_face *f = face_for_size(0, size);
return f ? (f->ascent + f->descent) : 0;
}
int meow_font_width_text(const char *utf8, int size) {
meow_face *f = face_for_size(size);
if (!f) return 0;
unsigned scaleq = (size == f->px) ? 4096u : (unsigned)(((uint64_t)size << 12) / (uint64_t)f->px);
const unsigned char *p = (const unsigned char *)utf8;
int total = 0;
while (*p) {
int cp = utf8_next(&p);
if (cp < 0) continue;
unsigned scaleq;
meow_face *f = routed_face((uint32_t)cp, size, &scaleq, NULL);
if (!f) continue;
meow_glyph *gr = find_glyph(f, (uint32_t)cp);
unsigned adv = gr ? gr->advance : advance_for_missing(f);
if (scaleq == 4096) total += (int)adv;
@@ -480,17 +602,18 @@ int meow_font_width_text(const char *utf8, int size) {
void meow_font_drawtext(uint16_t *fb, int x, int y, const char *utf8,
uint16_t color, int size) {
meow_face *f = face_for_size(size);
if (!f || !g.plane) return;
unsigned scaleq = (size == f->px) ? 4096u : (unsigned)(((uint64_t)size << 12) / (uint64_t)f->px);
const unsigned char *p = (const unsigned char *)utf8;
int pen = 0;
while (*p) {
int cp = utf8_next(&p);
if (cp < 0) continue;
unsigned scaleq;
int slot;
meow_face *f = routed_face((uint32_t)cp, size, &scaleq, &slot);
if (!f) continue;
meow_glyph *gr = find_glyph(f, (uint32_t)cp);
if (gr && gr->w > 0 && gr->h > 0) {
blit_glyph(fb, f, gr, x + pen, y, color, scaleq);
blit_glyph(fb, slot, f, gr, x + pen, y, color, scaleq);
} else if (!gr) {
/* 缺字:画空心方块 */
unsigned adv = advance_for_missing(f);
@@ -511,22 +634,74 @@ void meow_font_drawtext_outline(uint16_t *fb, int x, int y, const char *utf8,
if (ol <= 0) { meow_font_drawtext(fb, x, y, utf8, color, size); return; }
static const int odx[8] = {-1,1,-1,1,0,0,-1,1};
static const int ody[8] = {-1,-1,1,1,-1,1,0,0};
/* 先将每个字形画 8 方向偏移描边,再画原色:逐字符处理以保证 advance 一致性。 */
meow_face *f = face_for_size(size);
if (!f || !g.plane) return;
unsigned scaleq = (size == f->px) ? 4096u : (unsigned)(((uint64_t)size << 12) / (uint64_t)f->px);
/* 先将每个字形画 8 方向偏移描边,再画原色:逐字符处理以保证 advance 一致。 */
const unsigned char *p = (const unsigned char *)utf8;
int pen = 0;
while (*p) {
int cp = utf8_next(&p);
if (cp < 0) continue;
unsigned scaleq;
int slot;
meow_face *f = routed_face((uint32_t)cp, size, &scaleq, &slot);
if (!f) continue;
meow_glyph *gr = find_glyph(f, (uint32_t)cp);
if (gr && gr->w > 0 && gr->h > 0) {
for (int k = 0; k < 8; k++)
blit_glyph(fb, f, gr, x + pen + odx[k] * ol, y + ody[k] * ol, outline_color, scaleq);
blit_glyph(fb, slot, f, gr, x + pen + odx[k] * ol, y + ody[k] * ol, outline_color, scaleq);
}
int adv = gr ? gr->advance : (int)advance_for_missing(f);
pen += scaleq == 4096 ? adv : (int)(((uint64_t)adv * scaleq) >> 12);
unsigned adv = gr ? gr->advance : advance_for_missing(f);
pen += scaleq == 4096 ? (int)adv : (int)(((uint64_t)adv * scaleq) >> 12);
}
meow_font_drawtext(fb, x, y, utf8, color, size);
}
/* ---------------- 多字体配置(去重 + ≤4 套上限,launcher 与 SDK 共用) ---------------- */
int meow_font_config_multifont(int on,
const char *global_png, const char *global_idx,
const meow_font_class_cfg cfg[MEOW_FCLS_COUNT],
int *distinct, int *truncated) {
if (distinct) *distinct = 0;
if (truncated) *truncated = 0;
(void)global_idx; /* 全局字库已由 meow_font_load(slot0) 装载,idx 此处不重读 */
/* 复位映射(保留已装载槽;未开则纯单字体) */
g_multifont = 0;
for (int i = 0; i < MEOW_FCLS_COUNT; i++) g_class_slot[i] = 0;
int g0 = g_slots[0].loaded;
if (!on) { if (distinct) *distinct = g0 ? 1 : 0; return 0; }
/* 已装载的不同字库(png 路径去重)。首项 = 全局(slot0)。 */
const char *d_png[MEOW_MAX_SLOTS];
int d_slot[MEOW_MAX_SLOTS];
int d_n = 0;
d_png[0] = global_png ? global_png : "";
d_slot[0] = 0;
d_n = 1;
g_multifont = 1;
for (int c = 0; c < MEOW_FCLS_COUNT; c++) {
const char *png = cfg[c].png;
if (!png || !png[0]) continue; /* 空 = 跟随全局 */
int found = -1;
if (global_png && !strcmp(png, global_png)) found = 0;
for (int k = 0; k < d_n && found < 0; k++)
if (!strcmp(png, d_png[k])) found = d_slot[k]; /* 同 family 去重 */
if (found >= 0) { g_class_slot[c] = found; continue; }
/* 新的一套不同字体:上限 MEOW_MAX_SLOTS(含全局)→ 截断 + WARN */
if (d_n >= MEOW_MAX_SLOTS) {
if (truncated) *truncated = 1;
g_class_slot[c] = 0; /* 该类别回退全局 */
continue;
}
int slot = d_n; /* slot0 已被全局占用 */
const char *idx = cfg[c].idx ? cfg[c].idx : "";
if (meow_font_load_slot(slot, png, idx) != 0) { /* 装载失败 → 回退全局 */
g_class_slot[c] = 0;
continue;
}
d_png[d_n] = png; d_slot[d_n] = slot; d_n++;
g_class_slot[c] = slot;
}
if (distinct) *distinct = d_n;
return 0;
}
+56
View File
@@ -51,6 +51,62 @@ void meow_font_drawtext(uint16_t *fb, int x, int y, const char *utf8,
void meow_font_drawtext_outline(uint16_t *fb, int x, int y, const char *utf8,
uint16_t color, uint16_t outline_color, int ol, int size);
/* ---- 字符分类器(multi_font 逐字符路由的依据;按 codepoint 归类)---- */
typedef enum {
MEOW_FCLS_CJK = 0, /* CJK 表意文字 + 扩展 / 兼容表意(U+3400-4DBF/4E00-9FFF/20000-2FA1F/…) */
MEOW_FCLS_CJK_SYM = 1, /* 全角/中文标点(U+3000-303F、U+FF00-FFEF、U+2014/2018-201D/2026、U+00B7) */
MEOW_FCLS_NUM = 2, /* 0-9 */
MEOW_FCLS_LATIN = 3, /* A-Za-z(基本拉丁字母) */
MEOW_FCLS_LATIN_SYM = 4, /* 半角标点(ASCII 非字母数字:空格/括号/引号/运算符/控制符) */
MEOW_FCLS_OTHER = 5, /* 其他(假名/韩文/他文字/扩展区等) */
MEOW_FCLS_COUNT = 6
} meow_fclass;
/* 把一个 codepoint 归类为上述之一。 */
int meow_font_class_of(uint32_t cp);
/* 校验码位是否在「将用于渲染它的字库」中存在字形(1=有,0=缺字/字库未加载→渲染为豆腐)。
* multi_font 开启时按分类路由到对应字库,关闭时仅查全局字库(slot 0)。 */
int meow_font_has_glyph(uint32_t cp);
/* ---- 多字体(MEOW_FCLS* 逐字符路由)----
* 槽位模型:slot 0 = meow_font_load() 装载的全局/默认字库(font.file);额外字库经
* meow_font_load_slot() 装载后,用 meow_font_set_class_slot() 把某字符类指到某一槽。
* meow_font_drawtext/width_text/line_height/... 在 meow_font_enable_multifont(1) 开启时
* 逐字符分类 → 用该类别对应槽的字库取字形与度量;缺该类别字库/未加载 → 回退 slot 0。
* 关闭(默认)时一律走 slot 0,行为与单字体完全一致(零额外开销)。
* 同 family 去重、≤4 套上限由 meow_font_config_multifont() 统一处理(launcher 与 SDK 共用)。 */
#define MEOW_MAX_SLOTS 5 /* slot0(全局) + ≤4 套类别字库;超出则截断并置 *truncated=1(调用方 WARN) */
int meow_font_load_slot(int slot, const char *png_path, const char *idx_path); /* 0=成功,负值失败 */
void meow_font_unload_slot(int slot);
void meow_font_enable_multifont(int on); /* 0=单字体(默认);1=逐字符分类路由 */
void meow_font_set_class_slot(meow_fclass cls, int slot); /* slot 越界/载入失败 → 该类别回退 slot 0 */
/* 一个字符类的候选字库路径(绝对路径,已由调用方按卡根解析;png=NULL/空 = 跟随全局)。 */
typedef struct {
const char *png;
const char *idx;
} meow_font_class_cfg;
/* 依据开关 + 各类候选配置多字体:同 png 路径去重(含与全局相同 → 直接用 slot 0),
* 独立字库数(含全局)> MEOW_MAX_SLOTS 时截断并置 *truncated=1(此前已装载的保留)。
* 返回 0 成功,负值失败;*distinct = 实际装载的不同字库数(含全局)。 */
int meow_font_config_multifont(int on,
const char *global_png, const char *global_idx,
const meow_font_class_cfg cfg[MEOW_FCLS_COUNT],
int *distinct, int *truncated);
/* ---- MeowPanel 预留:TF 卡字体库 .zip 导入/导出(TODO,未实现)----
* 当前仅声明占位,供 MeowPanel 后续接入;不实现、不接 zip 库。
* 调用方约定(待实现):导入=校验 .zip 内含 font.png+font.idx+font.json 三件套,
* 校验通过后解压到 TF 卡 MeowOS/fonts/<family>/(family 取自 font.json 的 family 或 zip 名,
* 冲突时覆盖需先确认);导出=把 MeowOS/fonts/<family>/ 三件套打包为 <out_zip>。
* 目录布局与 font.json schema 见 docs/MEOWOS_SPEC_REF.md §1.2/§5.2。
* 返回码:MEOW_OK=0 成功,负值错误码(沿用本模块失败负值约定)。 */
int meow_font_import_zip(const char *zip_path); /* TODO:MeowPanel */
int meow_font_export_zip(const char *family, const char *out_zip); /* TODO:MeowPanel */
#ifdef __cplusplus
}
#endif
+271
View File
@@ -0,0 +1,271 @@
/*
* test_multi_font.c — MeowOS 多字体功能宿主导出单测。
*
* 验证两块(MEOWOS_SPEC_REF §5.7 多字体 / §5.2 分类路由):
* (1) meow_font_class_of() 字符分类器:各代表 codepoint 归类正确。
* (2) 逐字符路由:meow_font_drawtext/width_text/line_height/has_glyph 在 multi_font
* 开启时按类别用对应槽字库;关闭(默认)时与单字体一致;family 去重;≤4 套上限截断。
*
* 为避免依赖仓库真实字体(不同机器/来源不确定),此处用「假 atlas/stub」:离线合成两张
* 微型位图字体(合法 PNG 灰度8bit + MFNI v2 idx),各含 1-3 个字形、不同 advance,故
* 路由结果可精确断言。
* - lat(slot0/全局):'A' adv=10、'0' adv=10、' '(0x20) adv=10 —— 只有拉丁/数字/空格。
* - cjk(slot1):'中'(0x4E2D) adv=30 —— 只有 CJK。
*
* 用法:宿主 gcc -I. test_multi_font.c meow_font.c -lz -o test_multi_font && ./test_multi_font
* 退出码 0=全过;非 0=某断言失败(打印 FAIL)。
*/
#include "meow_font.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <zlib.h>
/* ---------------- 微型合成字库(假 atlas/stub) ---------------- */
static void wr_be32(unsigned char **p, uint32_t v) { (*p)[0]=v>>24;(*p)[1]=v>>16;(*p)[2]=v>>8;(*p)[3]=v; *p+=4; }
static void wr_le16(unsigned char **p, uint16_t v) { (*p)[0]=v&0xFF;(*p)[1]=v>>8; *p+=2; }
static void wr_le32(unsigned char **p, uint32_t v) { (*p)[0]=v&0xFF;(*p)[1]=(v>>8)&0xFF;(*p)[2]=(v>>16)&0xFF;(*p)[3]=(v>>24)&0xFF; *p+=4; }
static void png_chunk2(unsigned char *dst, size_t *off, const char *type,
const unsigned char *data, size_t dlen) {
unsigned char *o = dst + *off;
wr_be32(&o, (uint32_t)dlen);
memcpy(o, type, 4);
o += 4;
if (dlen) { memcpy(o, data, dlen); o += dlen; }
/* CRC = crc32(type) 续 crc32(data) */
uint32_t c = crc32(0L, (const Bytef *)type, 4);
if (dlen) c = crc32(c, (const Bytef *)data, (uInt)dlen);
wr_be32(&o, c);
*off += 12 + dlen;
}
/* 生成一张合法 PNG:w x h、灰度8bit、非隔行、filter=0 全零像素。返回 malloc 缓冲。 */
static unsigned char *make_png(int w, int h, size_t *outlen) {
const unsigned char sig[8] = {0x89,'P','N','G','\r','\n',0x1a,'\n'};
/* IHDR data (13B) */
unsigned char ihdr[13];
ihdr[0]=w>>24; ihdr[1]=w>>16; ihdr[2]=w>>8; ihdr[3]=w;
ihdr[4]=h>>24; ihdr[5]=h>>16; ihdr[6]=h>>8; ihdr[7]=h;
ihdr[8]=8; /* bit depth 8 */
ihdr[9]=0; /* color type 0 = gray */
ihdr[10]=0; ihdr[11]=0; ihdr[12]=0; /* compression/filter/interlace */
int rowbytes = w; /* depth8 gray */
size_t rawlen = (size_t)(rowbytes + 1) * h;
unsigned char *raw = (unsigned char *)calloc(1, rawlen); /* 每行 filter 字节=0 + 像素=0 */
if (!raw) return NULL;
for (int y = 0; y < h; y++) raw[y * (rowbytes + 1)] = 0;
uLongf destlen = compressBound((uLong)rawlen);
unsigned char *idat = (unsigned char *)malloc(destlen);
if (compress2(idat, &destlen, raw, (uLong)rawlen, Z_DEFAULT_COMPRESSION) != Z_OK) {
free(raw); free(idat); return NULL;
}
free(raw);
size_t total = 8 + 12 + 13 + 12 + destlen + 12;
unsigned char *out = (unsigned char *)malloc(total);
if (!out) { free(idat); return NULL; }
size_t off = 0;
memcpy(out + off, sig, 8); off += 8;
png_chunk2(out, &off, "IHDR", ihdr, 13);
png_chunk2(out, &off, "IDAT", idat, destlen);
png_chunk2(out, &off, "IEND", NULL, 0);
free(idat);
*outlen = off;
return out;
}
/* 生成 MFNI v2 idx:1 个面(px 字号),planes 覆盖全 PNG,含 nglyphs 个字(cp/adv 升序)。 */
static unsigned char *make_idx(int px, int ascent, int descent, int plane_w, int plane_h,
const uint32_t *cps, const int *advs, int nglyphs,
size_t *outlen) {
size_t len = 16 + 20 + (size_t)nglyphs * 16;
unsigned char *out = (unsigned char *)calloc(1, len);
unsigned char *o = out;
memcpy(o, "MFNI", 4); o += 4;
wr_le16(&o, 2); /* version=2 */
*o++ = 8; /* depth */
*o++ = 1; /* nfaces */
wr_le32(&o, (uint32_t)plane_w); wr_le32(&o, (uint32_t)plane_h);
/* face */
wr_le16(&o, (uint16_t)px); /* px */
wr_le16(&o, 0); /* plane_x */
wr_le16(&o, 0); /* plane_y */
wr_le16(&o, (uint16_t)plane_w); /* plane_w */
wr_le16(&o, (uint16_t)plane_h); /* plane_h */
wr_le16(&o, (uint16_t)ascent); /* ascent (s16) */
wr_le16(&o, (uint16_t)descent); /* descent (s16) */
wr_le16(&o, (uint16_t)nglyphs); /* glyph_count */
wr_le32(&o, 16 + 20); /* goff */
/* glyphs 升序 */
for (int i = 0; i < nglyphs; i++) {
wr_le32(&o, cps[i]); /* cp */
wr_le16(&o, 0); /* x */
wr_le16(&o, 0); /* y */
wr_le16(&o, (uint16_t)px); /* w */
wr_le16(&o, (uint16_t)px); /* h */
wr_le16(&o, (uint16_t)advs[i]); /* advance */
*o++ = 0; /* dx */
*o++ = 0; /* dy */
}
*outlen = len;
return out;
}
/* 写一套字库 <base>.png/.idx,返回 0 成功。 */
static int write_font(const char *base, int px, int ascent, int descent,
const uint32_t *cps, const int *advs, int nglyphs) {
char pngp[512], idxp[512];
snprintf(pngp, sizeof pngp, "%s.png", base);
snprintf(idxp, sizeof idxp, "%s.idx", base);
size_t plen = 0, ilen = 0;
unsigned char *png = make_png(64, 64, &plen);
if (!png) { fprintf(stderr, "make_png failed\n"); return -1; }
unsigned char *idx = make_idx(px, ascent, descent, 64, 64, cps, advs, nglyphs, &ilen);
if (!idx) { free(png); return -1; }
FILE *f = fopen(pngp, "wb");
if (!f) { free(png); free(idx); return -1; }
fwrite(png, 1, plen, f); fclose(f);
f = fopen(idxp, "wb");
if (!f) { free(png); free(idx); return -1; }
fwrite(idx, 1, ilen, f); fclose(f);
free(png); free(idx);
return 0;
}
/* ---------------- 断言 ---------------- */
static int g_fail = 0;
#define CHECK(cond, msg) do { \
if (!(cond)) { g_fail++; fprintf(stderr, "FAIL: %s (line %d)\n", msg, __LINE__); } \
else printf("ok : %s\n", msg); \
} while (0)
/* ---------------- 主测 ---------------- */
int main(void) {
/* 造两套 stub 字库 */
const uint32_t lat_cps[] = {0x20, 0x30, 0x41}; /* 空格 '0' 'A' */
const int lat_adv[] = {10, 10, 10};
const uint32_t cjk_cps[] = {0x4E2D}; /* '中' */
const int cjk_adv[] = {30};
if (write_font("/tmp/mf_lat", 20, 20, 6, lat_cps, lat_adv, 3) != 0) return 2;
if (write_font("/tmp/mf_cjk", 20, 25, 8, cjk_cps, cjk_adv, 1) != 0) return 2;
/* ---- (1) 字符分类器 ---- */
printf("\n== 分类器 meow_font_class_of ==\n");
CHECK(meow_font_class_of('A') == MEOW_FCLS_LATIN, "A -> LATIN");
CHECK(meow_font_class_of('z') == MEOW_FCLS_LATIN, "z -> LATIN");
CHECK(meow_font_class_of('5') == MEOW_FCLS_NUM, "5 -> NUM");
CHECK(meow_font_class_of(' ') == MEOW_FCLS_LATIN_SYM, "spc -> LATIN_SYM");
CHECK(meow_font_class_of(',') == MEOW_FCLS_LATIN_SYM, ", -> LATIN_SYM");
CHECK(meow_font_class_of('(') == MEOW_FCLS_LATIN_SYM, "( -> LATIN_SYM");
CHECK(meow_font_class_of('.') == MEOW_FCLS_LATIN_SYM, ". -> LATIN_SYM");
CHECK(meow_font_class_of(0x4E2D) == MEOW_FCLS_CJK, "U+4E2D -> CJK");
CHECK(meow_font_class_of(0x9FFF) == MEOW_FCLS_CJK, "U+9FFF -> CJK");
CHECK(meow_font_class_of(0x3400) == MEOW_FCLS_CJK, "U+3400(ExtA) -> CJK");
CHECK(meow_font_class_of(0x20000) == MEOW_FCLS_CJK, "U+20000(ExtB) -> CJK");
CHECK(meow_font_class_of(0x3001) == MEOW_FCLS_CJK_SYM, "U+3001(、) -> CJK_SYM");
CHECK(meow_font_class_of(0x3000) == MEOW_FCLS_CJK_SYM, "U+3000(全角空格) -> CJK_SYM");
CHECK(meow_font_class_of(0xFF0C) == MEOW_FCLS_CJK_SYM, "U+FF0C(,) -> CJK_SYM");
CHECK(meow_font_class_of(0xFF10) == MEOW_FCLS_CJK_SYM, "U+FF10(0) -> CJK_SYM");
CHECK(meow_font_class_of(0x2026) == MEOW_FCLS_CJK_SYM, "U+2026(…) -> CJK_SYM");
CHECK(meow_font_class_of(0x00B7) == MEOW_FCLS_CJK_SYM, "U+00B7(·) -> CJK_SYM");
CHECK(meow_font_class_of(0x3042) == MEOW_FCLS_OTHER, "U+3042(あ) -> OTHER");
CHECK(meow_font_class_of(0xAC00) == MEOW_FCLS_OTHER, "U+AC00(가) -> OTHER");
CHECK(meow_font_class_of(0xE9) == MEOW_FCLS_OTHER, "U+00E9(é) -> OTHER");
/* ---- (2) 多字体路由 ---- */
printf("\n== 单字体基线(multi off,全局=lat)==\n");
int r = meow_font_load("/tmp/mf_lat.png", "/tmp/mf_lat.idx");
CHECK(r == 0, "load global lat");
CHECK(meow_font_has_size(20), "lat has size 20");
CHECK(meow_font_width_text("A", 20) == 10, "w(A)=10 (lat)");
CHECK(meow_font_width_text("0", 20) == 10, "w(0)=10 (lat)");
/* lat 无 '中' → 缺字回退空格 adv=10 */
CHECK(meow_font_width_text("中", 20) == 10, "w(中)=10 fallback (lat no cjk)");
CHECK(meow_font_width_text("A中", 20) == 20, "w(A中)=20 (single font)");
CHECK(meow_font_has_glyph(0x4E2D) == 0, "has_glyph(中)=0 (lat lacks)");
CHECK(meow_font_has_glyph('A') == 1, "has_glyph(A)=1 (lat has)");
CHECK(meow_font_line_height(20) == 26, "lh(20)=26 (lat ascent20+desc6)");
printf("\n== 开启 multi_font(class_cjk -> cjk 槽)==\n");
meow_font_class_cfg cfg[MEOW_FCLS_COUNT];
memset(cfg, 0, sizeof cfg);
cfg[MEOW_FCLS_CJK].png = "/tmp/mf_cjk.png";
cfg[MEOW_FCLS_CJK].idx = "/tmp/mf_cjk.idx";
char gpng[64], gidx[64];
snprintf(gpng, sizeof gpng, "/tmp/mf_lat.png");
snprintf(gidx, sizeof gidx, "/tmp/mf_lat.idx");
int distinct = 0, truncd = 0;
r = meow_font_config_multifont(1, gpng, gidx, cfg, &distinct, &truncd);
CHECK(r == 0, "config_multifont ok");
CHECK(distinct == 2, "distinct=2 (lat + cjk)");
CHECK(truncd == 0, "not truncated");
CHECK(meow_font_width_text("A", 20) == 10, "w(A)=10 (routed -> global/lat, latin class)");
CHECK(meow_font_width_text("0", 20) == 10, "w(0)=10 (num class -> global)");
CHECK(meow_font_width_text("中", 20) == 30, "w(中)=30 (routed -> cjk slot)");
CHECK(meow_font_width_text("A中", 20) == 40, "w(A中)=40 (per-char routing)");
CHECK(meow_font_has_glyph(0x4E2D) == 1, "has_glyph(中)=1 (routed -> cjk)");
CHECK(meow_font_line_height(20) == 33, "lh(20)=33 (max(lat26, cjk33))");
/* 关闭 → 立即回到单字体 */
meow_font_config_multifont(0, gpng, gidx, cfg, &distinct, &truncd);
CHECK(meow_font_width_text("A中", 20) == 20, "off -> w(A中)=20 again");
CHECK(meow_font_has_glyph(0x4E2D) == 0, "off -> has_glyph(中)=0 again");
/* drawtext smoke:吃一个混合串不崩(当前 cfg 仍为合法 cjk 配置) */
printf("\n== drawtext smoke(不影响断言,仅验证不崩)==\n");
meow_font_set_fb(64, 64, 128);
static uint16_t buf[64 * 64];
memset(buf, 0, sizeof buf);
meow_font_config_multifont(1, gpng, gidx, cfg, &distinct, &truncd);
meow_font_drawtext(buf, 0, 0, "A中0,", 0xFFFF, 20);
printf("drew 'A中0,' ok\n");
printf("\n== family 去重 / 上限 ==\n");
/* latin 类别也指到 cjk → 应去重,distinct 仍为 2 */
memset(cfg, 0, sizeof cfg);
cfg[MEOW_FCLS_CJK].png = "/tmp/mf_cjk.png"; cfg[MEOW_FCLS_CJK].idx = "/tmp/mf_cjk.idx";
cfg[MEOW_FCLS_LATIN].png = "/tmp/mf_cjk.png"; cfg[MEOW_FCLS_LATIN].idx = "/tmp/mf_cjk.idx";
meow_font_config_multifont(1, gpng, gidx, cfg, &distinct, &truncd);
CHECK(distinct == 2, "dedupe: latin==cjk path -> distinct=2");
CHECK(truncd == 0, "dedupe: not truncated");
/* 超上限:6 类各指向一个不同、可加载的字体(除全局)→ 第 5 个起截断。
* MEOW_MAX_SLOTS=5(global + ≤4 类别),故最多 5 套不同字库。 */
{
/* 生成 5 个额外不同字库(不同 advance,保证去重键不同) */
const uint32_t fcps[] = {0x4E2D};
int fav = 100;
char paths[8][64], idxs[8][64];
for (int i = 2; i < 7; i++) { /* 生成 /tmp/mf_trunc2..6 */
snprintf(paths[i], sizeof paths[i], "/tmp/mf_trunc%d", i);
if (write_font(paths[i], 20, 25, 8, fcps, &fav, 1) != 0) return 2;
snprintf(paths[i], sizeof paths[i], "/tmp/mf_trunc%d.png", i);
snprintf(idxs[i], sizeof idxs[i], "/tmp/mf_trunc%d.idx", i);
fav += 50;
}
char pp[6][64];
/* 6 类全部指向不同非全局字库 */
snprintf(pp[MEOW_FCLS_CJK], sizeof pp[0], "/tmp/mf_trunc2.png");
snprintf(pp[MEOW_FCLS_CJK_SYM], sizeof pp[0], "/tmp/mf_trunc3.png");
snprintf(pp[MEOW_FCLS_NUM], sizeof pp[0], "/tmp/mf_trunc4.png");
snprintf(pp[MEOW_FCLS_LATIN], sizeof pp[0], "/tmp/mf_trunc5.png");
snprintf(pp[MEOW_FCLS_LATIN_SYM], sizeof pp[0], "/tmp/mf_trunc6.png");
snprintf(pp[MEOW_FCLS_OTHER], sizeof pp[0], "/tmp/mf_trunc2.png"); /* 与 CJK 同 → 去重,不新增 */
for (int i = 0; i < 6; i++) { cfg[i].png = pp[i]; cfg[i].idx = NULL; }
/* 修 idx:write_font 已生对应 .idx,需给出 */
char ppi[6][64];
const char *maps[6] = {"/tmp/mf_trunc2", "/tmp/mf_trunc3", "/tmp/mf_trunc4",
"/tmp/mf_trunc5", "/tmp/mf_trunc6", "/tmp/mf_trunc2"};
for (int i = 0; i < 6; i++) {
snprintf(ppi[i], sizeof ppi[i], "%s.idx", maps[i]);
cfg[i].idx = ppi[i];
}
meow_font_config_multifont(1, gpng, gidx, cfg, &distinct, &truncd);
CHECK(truncd == 1, "6 distinct -> truncated=1");
CHECK(distinct == MEOW_MAX_SLOTS, "distinct capped at MEOW_MAX_SLOTS");
}
printf("\n%s (%d failures)\n", g_fail ? "RESULT: FAIL" : "RESULT: PASS", g_fail);
return g_fail ? 1 : 0;
}