Files
MeowOS-SDK/sdk/meow_font.c
T

965 lines
38 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* meow_font.c — MeowOS 预渲染位图字体运行时(独立库,不绑定启动器)。
*
* 依赖:
* - libz(musl 静态)仅用于 inflate PNG 像素面。目标系统无 libpng,故模块内自带
* 最小 PNG 解码(灰度 non-interlaced,bit depth 4|8,filter 0-4)。
* - zlib 头文件不在 musl sysroot,故在文件底部声明 ABI 兼容的最小 API(z_stream 布局
* 与 libz.a 一致,已用 qemu-mipsel 验证)。
*
* 文件格式参见 gen/gen_font.py 注释(version=2,magic 'MFNI')。
*
* 绘制模型: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"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h> /* access():字库缺失判定用 */
/* ---------------- 最小 zlib ABI 声明(与 libz.a 匹配,mipsel 32-bit unsigned long=4B) ------- */
typedef unsigned char mzf_Bytef;
typedef unsigned int mzf_uInt;
typedef unsigned long mzf_uLong;
typedef void *mzf_voidpf;
typedef unsigned int (*mzf_alloc_func)(unsigned int, unsigned int);
typedef void (*mzf_free_func)(unsigned int, unsigned int);
typedef struct mzf_stream_s {
mzf_Bytef *next_in;
mzf_uInt avail_in;
mzf_uLong total_in;
mzf_Bytef *next_out;
mzf_uInt avail_out;
mzf_uLong total_out;
char *msg;
void *state;
mzf_alloc_func zalloc;
mzf_free_func zfree;
mzf_voidpf opaque;
int data_type;
mzf_uLong adler;
mzf_uLong reserved;
} mzf_stream;
#define MZF_OK 0
#define MZF_STREAM_END 1
#define MZF_NO_FLUSH 0
#define MZF_FINISH 4
/* 函数符号名必须与 libz.a 一致(inflate 等);结构名仅 C 侧,ABI 由布局决定。 */
extern int inflateInit_(mzf_stream *, const char *, int);
extern int inflate(mzf_stream *, int);
extern int inflateEnd(mzf_stream *);
extern const char *zlibVersion(void);
/* ---------------- 内部数据结构 ---------------- */
typedef struct {
uint32_t cp;
uint16_t x, y, w, h, advance;
int8_t dx;
uint8_t dy;
} meow_glyph;
typedef struct {
int px; /* 原生字号 */
unsigned plane_x, plane_y; /* 在大画布(plane_w x plane_h)内的面左上角 */
unsigned plane_w, plane_h;
int ascent, descent; /* descent>=0 */
unsigned glyph_count;
meow_glyph *glyphs; /* 按 cp 升序 */
} meow_face;
/* 一个槽 = 一套已装载的字库(一个 PNG plane + 若干 face)。 */
typedef struct {
size_t plane_rowbytes;
unsigned depth; /* 4 或 8 */
unsigned plane_w, plane_h; /* 画布尺寸 */
unsigned char *plane; /* 打包像素面 */
int face_count;
meow_face *faces;
int loaded; /* 是否已装载 */
} meow_font_slot;
/* 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) {
return ((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16) |
((uint32_t)p[2] << 8) | (uint32_t)p[3];
}
static uint32_t le32(const unsigned char *p) {
return (uint32_t)p[0] | ((uint32_t)p[1] << 8) |
((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24);
}
/* ---------------- 内存读文件 ---------------- */
static unsigned char *read_file(const char *path, size_t *out_len) {
FILE *f = fopen(path, "rb");
if (!f) return NULL;
fseek(f, 0, SEEK_END);
long n = ftell(f);
fseek(f, 0, SEEK_SET);
if (n < 0) { fclose(f); return NULL; }
unsigned char *buf = (unsigned char *)malloc((size_t)n ? (size_t)n : 1);
if (!buf) { fclose(f); return NULL; }
size_t rd = fread(buf, 1, (size_t)n, f);
fclose(f);
if (rd != (size_t)n) { free(buf); return NULL; }
*out_len = (size_t)n;
return buf;
}
/* ---------------- 最小 PNG 解码(灰度 non-interlaced) ----------------
* 流式/分块解码,消除「整幅原始扫描行 raw + 整段 idat 拷贝」双份驻留:
* - idat 不再另外 malloc 拷贝,直接以 png 文件缓冲内 IDAT 区间作 zlib 输入;
* - 逐行 inflate 到单行小缓冲 rowbuf(rowbytes+1 字节),随即反滤波写入打包面 out;
* 不再分配整幅 raw(= 扫描行数×(rowbytes+1))——对大字库这是 6-16MB 级的一次性峰值。
* 大字库 gbk 解码面本身很大(行数×rowbytes),流式后 load 峰值 ≈ 文件缓冲 + idx + 打包面,
* 而非 文件缓冲 + idat + raw + 打包面。*/
static unsigned char *png_decode_gray(const unsigned char *png, size_t len,
unsigned *out_w, unsigned *out_h,
unsigned *out_depth, size_t *out_rowbytes) {
if (len < 8 || memcmp(png, "\x89PNG\r\n\x1a\n", 8) != 0)
return NULL;
size_t i = 8;
unsigned w = 0, h = 0, depth = 0, color = 0, interlace = 1, compression = 1, filt = 1;
int have_ihdr = 0;
size_t idat_off = 0, idat_len = 0; /* IDAT 在 png 内的连续区间(偏移/长度) */
int have_idat = 0;
while (i + 8 <= len) {
uint32_t clen = be32(png + i);
const unsigned char *type = png + i + 4;
if (i + 8 + (size_t)clen > len) break;
if (memcmp(type, "IHDR", 4) == 0) {
if (clen < 13) return NULL;
w = be32(png + i + 8);
h = be32(png + i + 12);
depth = png[i + 16];
color = png[i + 17];
compression = png[i + 18];
filt = png[i + 19];
interlace = png[i + 20];
have_ihdr = 1;
} else if (memcmp(type, "IDAT", 4) == 0) {
if (!have_idat) { idat_off = i + 8; idat_len = clen; have_idat = 1; }
else {
/* 要求 IDAT 连续(合法 PNG/单段压缩满足;gen_font.py 亦单段) */
if (idat_off + idat_len != i + 8) return NULL;
idat_len += clen;
}
} else if (memcmp(type, "IEND", 4) == 0) {
break;
}
i += 12 + (size_t)clen;
}
if (!have_ihdr || !have_idat ||
color != 0 || compression != 0 || filt != 0 || interlace != 0 ||
(depth != 4 && depth != 8) || w == 0 || h == 0)
return NULL;
size_t rowbytes = ((size_t)w * depth + 7) / 8;
size_t scanlen = rowbytes + 1; /* 每扫描行 = 1 filter 字节 + rowbytes 像素 */
size_t rawlen = scanlen * h; /* 期望的解压总量(校验用,不据此分配) */
unsigned char *out = (unsigned char *)malloc(rowbytes * h);
if (!out) return NULL;
unsigned char *rowbuf = (unsigned char *)malloc(scanlen);
if (!rowbuf) { free(out); return NULL; }
mzf_stream s;
memset(&s, 0, sizeof(s));
s.next_in = (mzf_Bytef *)(png + idat_off);
s.avail_in = (mzf_uInt)idat_len;
s.zalloc = 0; s.zfree = 0; s.opaque = 0;
if (inflateInit_(&s, zlibVersion(), (int)sizeof(s)) != MZF_OK) {
free(out); free(rowbuf); return NULL;
}
const unsigned bpp = 1;
size_t produced = 0;
int ok = 1, stream_end = 0;
for (unsigned y = 0; y < h; y++) {
/* 逐行填满 rowbuf(zlib 可能分批产出,内层循环补足到 scanlen) */
size_t filled = 0;
while (filled < scanlen) {
s.next_out = rowbuf + filled;
s.avail_out = (mzf_uInt)(scanlen - filled);
int ret = inflate(&s, MZF_NO_FLUSH);
size_t got = (scanlen - filled) - s.avail_out;
filled += got;
if (ret == MZF_STREAM_END) { stream_end = 1; break; }
if (ret != MZF_OK || got == 0) { ok = 0; break; }
}
if (!ok || filled != scanlen) { ok = 0; break; }
/* 反滤波(bpp=1:灰度 depth 4|8)→ out 为每行 rowbytes 的打包像素 */
unsigned char ft = rowbuf[0];
const unsigned char *c = rowbuf + 1;
unsigned char *dst = out + (size_t)y * rowbytes;
unsigned char *prev = (y == 0) ? NULL : out + (size_t)(y - 1) * rowbytes;
for (size_t x = 0; x < rowbytes; x++) {
unsigned char left = (x >= bpp) ? dst[x - bpp] : 0;
unsigned char up = prev ? prev[x] : 0;
unsigned char ul = (prev && x >= bpp) ? prev[x - bpp] : 0;
unsigned char v = c[x];
switch (ft) {
case 0: dst[x] = v; break;
case 1: dst[x] = v + left; break;
case 2: dst[x] = v + up; break;
case 3: dst[x] = v + (((unsigned)left + (unsigned)up) >> 1); break;
case 4: {
int p = (int)left + (int)up - (int)ul;
int pa = p - (int)left; if (pa < 0) pa = -pa;
int pb = p - (int)up; if (pb < 0) pb = -pb;
int pc = p - (int)ul; if (pc < 0) pc = -pc;
unsigned char pred = (pa <= pb && pa <= pc) ? left : (pb <= pc ? up : ul);
dst[x] = v + pred;
break;
}
default: ok = 0; break;
}
}
if (!ok) break;
produced += scanlen;
if (stream_end) break;
}
inflateEnd(&s);
free(rowbuf);
if (!ok || produced != rawlen) { free(out); return NULL; }
*out_w = w; *out_h = h; *out_depth = depth; *out_rowbytes = rowbytes;
return out;
}
/* ---------------- 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 != st->depth) return -2; /* 位深须与 PNG 一致 */
size_t face_off = 16;
meow_face *faces = (meow_face *)calloc((size_t)nfaces, sizeof(meow_face));
if (!faces) return -3;
for (int i = 0; i < nfaces; i++) {
const unsigned char *fh = buf + face_off + (size_t)i * 20;
meow_face *f = &faces[i];
f->px = fh[0] | (fh[1] << 8);
f->plane_x = fh[2] | (fh[3] << 8);
f->plane_y = fh[4] | (fh[5] << 8);
f->plane_w = fh[6] | (fh[7] << 8);
f->plane_h = fh[8] | (fh[9] << 8);
int16_t asc = (int16_t)(fh[10] | (fh[11] << 8));
int16_t dsc = (int16_t)(fh[12] | (fh[13] << 8));
f->ascent = asc;
f->descent = dsc;
f->glyph_count = fh[14] | (fh[15] << 8);
uint32_t goff = le32(fh + 16);
if (f->glyph_count > 0 && goff + (size_t)f->glyph_count * 16 <= len) {
f->glyphs = (meow_glyph *)malloc((size_t)f->glyph_count * sizeof(meow_glyph));
if (!f->glyphs) { for (int j = 0; j < i; j++) free(faces[j].glyphs); free(faces); return -4; }
for (unsigned k = 0; k < f->glyph_count; k++) {
const unsigned char *gchr = buf + goff + (size_t)k * 16;
meow_glyph *gr = &f->glyphs[k];
gr->cp = le32(gchr);
gr->x = gchr[4] | (gchr[5] << 8);
gr->y = gchr[6] | (gchr[7] << 8);
gr->w = gchr[8] | (gchr[9] << 8);
gr->h = gchr[10] | (gchr[11] << 8);
gr->advance = gchr[12] | (gchr[13] << 8);
gr->dx = (int8_t)gchr[14];
gr->dy = gchr[15];
}
} else if (f->glyph_count == 0) {
f->glyphs = NULL;
} else {
for (int j = 0; j < i; j++) free(faces[j].glyphs);
free(faces);
return -5;
}
}
st->faces = faces;
st->face_count = nfaces;
return 0;
}
/* ---------------- 面/字形查找(槽感知) ---------------- */
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 < 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 = &st->faces[i]; }
}
return best;
}
static meow_glyph *find_glyph(meow_face *f, uint32_t cp) {
unsigned lo = 0, hi = f->glyph_count;
while (lo < hi) {
unsigned mid = (lo + hi) >> 1;
uint32_t c = f->glyphs[mid].cp;
if (c == cp) return &f->glyphs[mid];
if (c < cp) lo = mid + 1; else hi = mid;
}
return NULL;
}
/* ---------------- 像素采样(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;
}
/* ---------------- RGB565 alpha 混合 ---------------- */
static inline uint16_t blend565(uint16_t bg, uint16_t fg, unsigned a) {
unsigned inv = 255 - a;
unsigned br = (bg >> 11) & 31, bgc = (bg >> 5) & 63, bb = bg & 31;
unsigned fr = (fg >> 11) & 31, fgc = (fg >> 5) & 63, fb = fg & 31;
unsigned r = ((fr * a + br * inv) + 127) / 255;
unsigned gc = ((fgc * a + bgc * inv) + 127) / 255;
unsigned b = ((fb * a + bb * inv) + 127) / 255;
return (uint16_t)((r << 11) | (gc << 5) | b);
}
/* ---------------- 字形 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 元素每行数 */
/* 目标尺寸 = 源尺寸 * scaleq / 4096 */
unsigned gw, gh;
int dx, dy;
if (scaleq == 4096) {
gw = gr->w; gh = gr->h;
dx = gr->dx; dy = gr->dy;
} else {
gw = (unsigned)(((uint64_t)gr->w * scaleq) >> 12);
gh = (unsigned)(((uint64_t)gr->h * scaleq) >> 12);
dx = (int)(((int64_t)gr->dx * scaleq) >> 12);
dy = (int)(((int64_t)gr->dy * scaleq) >> 12);
}
if (gw == 0 || gh == 0) return;
int px = pen_x + dx;
int py = y + dy;
unsigned sw = gr->w, sh = gr->h;
for (unsigned oy = 0; oy < gh; oy++) {
int ay = py + (int)oy;
if (ay < 0) continue;
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;
int sx = (int)((uint64_t)ox * sw / gw);
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;
fb[idx] = blend565(fb[idx], color, a);
}
}
}
/* ---------------- UTF-8 解码 ---------------- */
static int 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;
}
static uint32_t advance_for_missing(meow_face *f) {
/* 缺字:用空格或 人 位 advance;找不到就用 fontSize/2 */
meow_glyph *sp = find_glyph(f, 0x20);
return sp ? sp->advance : (uint32_t)(f->px / 2);
}
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);
for (int oy = 0; oy < h; oy++) {
int ay = y + oy;
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 (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);
}
}
}
}
/* ---------------- 字符分类器(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;
}
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) {
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;
}
#ifdef MEOW_FONT_ENABLE_DEBUG
/* 宿主调试/校验钩子:取某槽已装载(打包)像素面 + 几何。仅宿主单测用,
* mipsel 构建不含 MEOW_FONT_ENABLE_DEBUG,无此符号、零开销。 */
const unsigned char *meow_font_debug_plane(int slot, unsigned *w, unsigned *h,
unsigned *depth, size_t *rowbytes) {
meow_font_slot *st = &g_slots[slot];
if (!st->loaded) return NULL;
*w = st->plane_w; *h = st->plane_h; *depth = st->depth; *rowbytes = st->plane_rowbytes;
return st->plane;
}
#endif
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;
unsigned char *idx = read_file(idx_path, &ilen);
if (!idx) { free(png); return -1; }
unsigned w, h, depth;
size_t rowbytes;
unsigned char *plane = png_decode_gray(png, plen, &w, &h, &depth, &rowbytes);
free(png);
if (!plane) { free(idx); return -2; }
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, slot) != 0) {
free(idx);
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);
}
/* ---------------- 按档装载大字库(字体档位分层) ---------------- */
/* 当前装载档位(0=common 1=gbk 2=full;仅记录,getter 走 meow_ui_gfx.c meow_font_tier) */
static int g_reload_tier = 0;
int meow_font_reload_tier(int tier, const char *png_path, const char *idx_path)
{
if (tier < 0) tier = 0;
if (tier > 2) tier = 2;
if (!png_path || !png_path[0] || !idx_path || !idx_path[0]) return -1;
/* 先 unload 旧(释放旧 atlas——大字库 GBK/Full 解码 5-15MB+,避免新旧同时驻留内存),
* 再 load 新(slot0 = 全局/默认字库)。meow_font_load 内部即 unload+load。 */
int r = meow_font_load(png_path, idx_path);
if (r == 0) g_reload_tier = tier;
return r;
}
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) {
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) {
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) {
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) {
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;
else total += (int)(((uint64_t)adv * scaleq) >> 12);
}
return total;
}
void meow_font_drawtext(uint16_t *fb, int x, int y, const char *utf8,
uint16_t color, int size) {
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, slot, f, gr, x + pen, y, color, scaleq);
} else if (!gr) {
/* 缺字:画空心方块 */
unsigned adv = advance_for_missing(f);
int tw, th;
if (scaleq == 4096) { tw = f->px / 2; th = f->ascent; }
else { tw = (int)(((uint64_t)(f->px / 2) * scaleq) >> 12); th = (int)(((uint64_t)f->ascent * scaleq) >> 12); }
draw_tofu(fb, x + pen + 1, y + 1, tw - 2, th - 2, color);
pen += scaleq == 4096 ? (int)adv : (int)(((uint64_t)adv * scaleq) >> 12);
continue;
}
int adv = gr ? gr->advance : (int)advance_for_missing(f);
pen += scaleq == 4096 ? adv : (int)(((uint64_t)adv * scaleq) >> 12);
}
}
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) {
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 一致。 */
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, slot, f, gr, x + pen + odx[k] * ol, y + ody[k] * ol, outline_color, scaleq);
}
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);
}
/* ---------------- 多字体配置(去重 + ≤3 套上限 + 内存预算,launcher 与 SDK 共用) ---------------- */
/* 多字体全部槽(含 slot0)「打包像素面」总字节预算:超出时新增类别字库被拒、回退全局并用
* *truncated=1 通知调用方 WARN。设定依据(真机 56MB,launcher+App 常态化紧张):
* slot0 若为 gbk 大字库,其 plane 已 ≈16.6MiB(>预算)→ 自动拒绝任何额外类别字库,
* 杜绝「大字库 + 多字体」双驻留 OOM;slot0 为 common(≈4.2MiB)时允许 1-3 个额外常用类
* (+小类拉丁/数字等),封顶后单进程字库总驻留可控制在 ~16MiB 内。
* 值 16 MiB:56MiB 总内存 − launcher 基线(估 26-28MiB)− 应用运行时 → 字库红利带。 */
#define MEOW_MULTIFONT_MEM_BUDGET (16u * 1024 * 1024)
static size_t mfont_total_plane_bytes(void) {
size_t t = 0;
for (int i = 0; i < MEOW_MAX_SLOTS; i++) {
meow_font_slot *st = &g_slots[i];
if (st->loaded) t += (size_t)st->plane_rowbytes * st->plane_h;
}
return t;
}
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;
}
/* 内存预算:装载成功后,若全部槽 plane 总字节超预算 → 卸载该新槽 + 回退全局 + 截断通知。
* 防「大字库 slot0 + 多个大字库类别字库」同时驻留(真机 56MB 会 OOM)。 */
if (mfont_total_plane_bytes() > MEOW_MULTIFONT_MEM_BUDGET) {
meow_font_unload_slot(slot);
if (truncated) *truncated = 1;
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;
}
/* ================================================================== */
/* M2.58 字体索引 + 应用字库查询(规格 §5.7:索引文件 / 查列表 / 丢失降级默认) */
/* ================================================================== */
/* SDK 可选日志:SDK 链 meow_log 强符号(→app 日志);launcher 无 meow_log(弱符号 → NULL,
* 静默跳过)——避免 meow_font.o 依赖 SDK,launcher 链接不至于为未定义 meow_log 而失败。 */
extern void meow_log(int lvl, const char *fmt, ...) __attribute__((weak));
#define MEOW_FONT_INDEX_MAX 32 /* index 条数上限(实际 ≤17,留余量) */
#define MEOW_FONT_DEF_PNG "/usr/data/mnt/sd_0/MeowOS/fonts/default/regular/font.png"
#define MEOW_FONT_DEF_IDX "/usr/data/mnt/sd_0/MeowOS/fonts/default/regular/font.idx"
#define MEOW_FONT_SYS_PNG "/usr/share/meowos/font/font.png"
#define MEOW_FONT_SYS_IDX "/usr/share/meowos/font/font.idx"
const char *meow_font_card_root(void)
{
const char *e = getenv("MEOW_CARD_ROOT");
return (e && e[0]) ? e : "/usr/data/mnt/sd_0";
}
int meow_font_index_path(char *out, int olen)
{
if (!out || olen <= 0) return -1;
snprintf(out, (size_t)olen, "%s/%s", meow_font_card_root(), MEOW_FONT_INDEX_REL);
return 0;
}
/* 合成「板载默认」条目(index0;无卡/无 index 时唯一项)。 */
static void meow_font_default_entry(meow_font_info *out)
{
if (!out) return;
memset(out, 0, sizeof *out);
snprintf(out->family, sizeof out->family, "default");
snprintf(out->name, sizeof out->name, "默认字体");
snprintf(out->weight, sizeof out->weight, "regular");
snprintf(out->tier, sizeof out->tier, "common");
out->glyph_count = 0;
snprintf(out->label, sizeof out->label, "默认字体 Regular(common)");
snprintf(out->path_png, sizeof out->path_png, "MeowOS/fonts/default/regular/font.png");
}
/* CSV 单字段读取(支持双引号包裹 + 内嵌双引号转义)。p 走到下一字段/行尾。 */
static int csv_field(const char **pp, char *out, int olen)
{
const char *p = *pp;
if (!out || olen <= 0) return 0;
int o = 0;
if (*p == '"') {
p++;
while (*p && o < olen - 1) {
if (*p == '"') {
if (p[1] == '"') { out[o++] = '"'; p += 2; continue; }
p++; /* 结束引号 */
break;
}
out[o++] = *p++;
}
if (*p == ',') p++;
else if (*p == '\r' || *p == '\n') { /* 行尾,由外层清理 */ }
} else {
while (*p && *p != ',' && *p != '\r' && *p != '\n' && o < olen - 1)
out[o++] = *p++;
if (*p == ',') p++;
}
out[o] = 0;
*pp = p;
return 1;
}
/* 有界复制(字符串入 dst≤dlen 且保证 NUL;避免 -Wformat-truncation 之类告警)。 */
static void fcpy(char *dst, int dlen, const char *src)
{
if (dlen <= 0) return;
size_t n = src ? strlen(src) : 0;
if (n >= (size_t)dlen) n = (size_t)dlen - 1;
memcpy(dst, src, n);
dst[n] = 0;
}
/* 解析一行 index(CSV 6 字段)。成功填 info 并返回 1;path 为空 → 0(跳过)。 */
static int meow_font_parse_index_line(char *line, meow_font_info *info)
{
if (!line || !info) return 0;
const char *p = line;
char f[6][360];
for (int i = 0; i < 6; i++) {
if (!csv_field(&p, f[i], (int)sizeof f[i])) return 0;
if (!f[i][0]) return 0;
}
memset(info, 0, sizeof *info);
fcpy(info->family, sizeof info->family, f[0]);
fcpy(info->name, sizeof info->name, f[0]); /* index 无单列 name → 回退 family */
fcpy(info->weight, sizeof info->weight, f[1]);
fcpy(info->path_png, sizeof info->path_png, f[2]);
fcpy(info->label, sizeof info->label, f[3]);
fcpy(info->tier, sizeof info->tier, f[4]);
info->glyph_count = atoi(f[5]);
return info->path_png[0] ? 1 : 0;
}
int meow_font_index_load(meow_font_info *out, int max)
{
if (!out || max <= 0) return 0;
char path[450];
if (meow_font_index_path(path, sizeof path) != 0) return 0;
FILE *f = fopen(path, "r");
if (!f) { meow_font_default_entry(&out[0]); return 1; } /* 无卡/无 index → 仅板载默认 */
int n = 0, total = 0;
char line[1024];
while (fgets(line, sizeof line, f)) {
char *s = line;
while (*s == ' ' || *s == '\t') s++;
if (!*s || *s == '\n' || *s == '\r' || *s == '#') continue; /* 忽略空行/注释 */
meow_font_info tmp;
if (!meow_font_parse_index_line(s, &tmp)) continue;
total++;
if (n < max) { out[n] = tmp; n++; }
}
fclose(f);
if (total == 0) { meow_font_default_entry(&out[0]); return 1; }
return total;
}
int meow_font_get_available_count(void)
{
meow_font_info buf[1];
return meow_font_index_load(buf, 1); /* max=1:只统计总数,不要求全填 */
}
int meow_font_enumerate(int idx, meow_font_info *out)
{
if (!out || idx < 0) return 0;
meow_font_info buf[MEOW_FONT_INDEX_MAX];
int n = meow_font_index_load(buf, MEOW_FONT_INDEX_MAX);
if (idx >= n) return 0;
memcpy(out, &buf[idx], sizeof *out);
return 1;
}
/* 相对路径按卡根解析为绝对路径(绝对路径原样返回)。 */
static void font_abs_path(const char *rel, char *out, int olen)
{
if (!rel || !rel[0]) { if (olen > 0) out[0] = 0; return; }
if (rel[0] == '/') { snprintf(out, (size_t)olen, "%s", rel); return; }
snprintf(out, (size_t)olen, "%s/%s", meow_font_card_root(), rel);
}
/* 由 png 绝对路径派生同目录 font.idx(.png → .idx)。0=成功。 */
static int font_idx_from_png(const char *png, char *out, int olen)
{
if (!png || olen <= 0) return -1;
snprintf(out, (size_t)olen, "%s", png);
size_t n = strlen(out);
if (n > 4 && !strcmp(out + n - 4, ".png")) {
out[n-4]='.'; out[n-3]='i'; out[n-2]='d'; out[n-1]='x'; out[n]=0;
return 0;
}
return -1;
}
/* 装载系统默认字体(跟随 font.file,经 env MEOW_FONT_PNG/IDX → 卡默认 → 固件)。
* 与 SDK meow_ui_gfx load_font 的优先级一致;app 经 select_by_id(0) 或任何降级回退时走此路径。 */
static int meow_font_load_system_default(void)
{
const char *ep = getenv("MEOW_FONT_PNG");
const char *ei = getenv("MEOW_FONT_IDX");
char idxbuf[400];
if (ep && ep[0]) {
if (!ei || !ei[0]) {
if (font_idx_from_png(ep, idxbuf, sizeof idxbuf) != 0) ei = NULL;
else ei = idxbuf;
}
if (ei && meow_font_load(ep, ei) == 0) return 0;
}
if (meow_font_load(MEOW_FONT_DEF_PNG, MEOW_FONT_DEF_IDX) == 0) return 0;
if (meow_font_load(MEOW_FONT_SYS_PNG, MEOW_FONT_SYS_IDX) == 0) return 0;
return -1;
}
int meow_font_select_by_id(int id)
{
if (id == 0) return meow_font_load_system_default(); /* 跟随系统默认(font.file) */
meow_font_info info;
if (!meow_font_enumerate(id, &info)) return -1; /* 越界 → 负值 */
if (!info.path_png[0]) return -1;
char png[400], idx[400];
font_abs_path(info.path_png, png, sizeof png);
if (font_idx_from_png(png, idx, sizeof idx) != 0) return -1;
if (access(png, R_OK) == 0 && meow_font_load(png, idx) == 0) return 0;
/* 字库缺失 → 降级默认(不丢请求)+ WARN(app 日志;meow_log 为 SDK 统一日志) */
if (meow_log) meow_log(2 /* MEOW_LOG_WARN */, "font %s missing, fallback default", png);
return meow_font_load_system_default();
}