724 lines
28 KiB
C
724 lines
28 KiB
C
/*
|
||
* 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;
|
||
}
|