Files
MeowOS-For-R3P2/font/meow_font.c
T

724 lines
28 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>
/* ---------------- 最小 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;
}
/* ---------------- zlib inflate ---------------- */
static int inflate_mem(const unsigned char *src, size_t srclen,
unsigned char *dst, size_t dstlen, size_t *out) {
mzf_stream s;
memset(&s, 0, sizeof(s));
s.next_in = (mzf_Bytef *)src;
s.avail_in = (mzf_uInt)srclen;
s.next_out = (mzf_Bytef *)dst;
s.avail_out = (mzf_uInt)dstlen;
s.zalloc = 0; s.zfree = 0; s.opaque = 0;
if (inflateInit_(&s, zlibVersion(), (int)sizeof(s)) != MZF_OK)
return -1;
int ret = inflate(&s, MZF_FINISH);
*out = (size_t)(dstlen - s.avail_out);
inflateEnd(&s);
return (ret == MZF_STREAM_END) ? 0 : -1;
}
/* ---------------- 最小 PNG 解码(灰度 non-interlaced) ---------------- */
/* 返回打包像素面(调用方释放),并输出 width/height/depth/plane_rowbytes。失败返回 NULL。 */
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_len = 0;
unsigned char *idat = NULL;
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) { free(idat); 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) {
unsigned char *na = (unsigned char *)realloc(idat, idat_len + clen);
if (!na) { free(idat); return NULL; }
idat = na;
memcpy(idat + idat_len, png + i + 8, clen);
idat_len += clen;
} else if (memcmp(type, "IEND", 4) == 0) {
break;
}
i += 12 + (size_t)clen;
}
if (!have_ihdr || !idat ||
color != 0 || compression != 0 || filt != 0 || interlace != 0 ||
(depth != 4 && depth != 8) || w == 0 || h == 0) {
free(idat);
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 *raw = (unsigned char *)malloc(rawlen);
if (!raw) { free(idat); return NULL; }
size_t outlen = 0;
if (inflate_mem(idat, idat_len, raw, rawlen, &outlen) != 0 || outlen != rawlen) {
free(raw); free(idat); return NULL;
}
free(idat);
/* 反滤波(bpp=1:灰度 depth 4|8) → out 为每行 rowbytes 的打包像素 */
size_t outlen_px = rowbytes * h;
unsigned char *out = (unsigned char *)malloc(outlen_px);
if (!out) { free(raw); return NULL; }
const unsigned bpp = 1;
for (size_t y = 0; y < h; y++) {
unsigned char ft = raw[y * scanlen];
const unsigned char *c = raw + y * scanlen + 1;
unsigned char *dst = out + y * rowbytes;
unsigned char *prev = (y == 0) ? NULL : out + (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: free(raw); free(out); return NULL;
}
}
}
free(raw);
*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;
}
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);
}
/* ---------------- 多字体配置(去重 + ≤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;
}