/* * 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 #include #include #include /* 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(); }