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

304 lines
16 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.
/*
* test_multi_font.c — MeowOS 多字体功能宿主导出单测。
*
* 验证两块(MEOWOS_SPEC_REF §5.7 多字体 / §5.2 分类路由):
* (1) meow_font_class_of() 字符分类器:各代表 codepoint 归类正确。
* (2) 逐字符路由:meow_font_drawtext/width_text/line_height/has_glyph 在 multi_font
* 开启时按类别用对应槽字库;关闭(默认)时与单字体一致;family 去重;≤4 套上限截断。
*
* 为避免依赖仓库真实字体(不同机器/来源不确定),此处用「假 atlas/stub」:离线合成两张
* 微型位图字体(合法 PNG 灰度8bit + MFNI v2 idx),各含 1-3 个字形、不同 advance,故
* 路由结果可精确断言。
* - lat(slot0/全局):'A' adv=10、'0' adv=10、' '(0x20) adv=10 —— 只有拉丁/数字/空格。
* - cjk(slot1):'中'(0x4E2D) adv=30 —— 只有 CJK。
*
* 用法:宿主 gcc -I. test_multi_font.c meow_font.c -lz -o test_multi_font && ./test_multi_font
* 退出码 0=全过;非 0=某断言失败(打印 FAIL)。
*/
#include "meow_font.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <zlib.h>
/* ---------------- 微型合成字库(假 atlas/stub) ---------------- */
static void wr_be32(unsigned char **p, uint32_t v) { (*p)[0]=v>>24;(*p)[1]=v>>16;(*p)[2]=v>>8;(*p)[3]=v; *p+=4; }
static void wr_le16(unsigned char **p, uint16_t v) { (*p)[0]=v&0xFF;(*p)[1]=v>>8; *p+=2; }
static void wr_le32(unsigned char **p, uint32_t v) { (*p)[0]=v&0xFF;(*p)[1]=(v>>8)&0xFF;(*p)[2]=(v>>16)&0xFF;(*p)[3]=(v>>24)&0xFF; *p+=4; }
static void png_chunk2(unsigned char *dst, size_t *off, const char *type,
const unsigned char *data, size_t dlen) {
unsigned char *o = dst + *off;
wr_be32(&o, (uint32_t)dlen);
memcpy(o, type, 4);
o += 4;
if (dlen) { memcpy(o, data, dlen); o += dlen; }
/* CRC = crc32(type) 续 crc32(data) */
uint32_t c = crc32(0L, (const Bytef *)type, 4);
if (dlen) c = crc32(c, (const Bytef *)data, (uInt)dlen);
wr_be32(&o, c);
*off += 12 + dlen;
}
/* 生成一张合法 PNG:w x h、灰度8bit、非隔行、filter=0 全零像素。返回 malloc 缓冲。 */
static unsigned char *make_png(int w, int h, size_t *outlen) {
const unsigned char sig[8] = {0x89,'P','N','G','\r','\n',0x1a,'\n'};
/* IHDR data (13B) */
unsigned char ihdr[13];
ihdr[0]=w>>24; ihdr[1]=w>>16; ihdr[2]=w>>8; ihdr[3]=w;
ihdr[4]=h>>24; ihdr[5]=h>>16; ihdr[6]=h>>8; ihdr[7]=h;
ihdr[8]=8; /* bit depth 8 */
ihdr[9]=0; /* color type 0 = gray */
ihdr[10]=0; ihdr[11]=0; ihdr[12]=0; /* compression/filter/interlace */
int rowbytes = w; /* depth8 gray */
size_t rawlen = (size_t)(rowbytes + 1) * h;
unsigned char *raw = (unsigned char *)calloc(1, rawlen); /* 每行 filter 字节=0 + 像素=0 */
if (!raw) return NULL;
for (int y = 0; y < h; y++) raw[y * (rowbytes + 1)] = 0;
uLongf destlen = compressBound((uLong)rawlen);
unsigned char *idat = (unsigned char *)malloc(destlen);
if (compress2(idat, &destlen, raw, (uLong)rawlen, Z_DEFAULT_COMPRESSION) != Z_OK) {
free(raw); free(idat); return NULL;
}
free(raw);
size_t total = 8 + 12 + 13 + 12 + destlen + 12;
unsigned char *out = (unsigned char *)malloc(total);
if (!out) { free(idat); return NULL; }
size_t off = 0;
memcpy(out + off, sig, 8); off += 8;
png_chunk2(out, &off, "IHDR", ihdr, 13);
png_chunk2(out, &off, "IDAT", idat, destlen);
png_chunk2(out, &off, "IEND", NULL, 0);
free(idat);
*outlen = off;
return out;
}
/* 生成 MFNI v2 idx:1 个面(px 字号),planes 覆盖全 PNG,含 nglyphs 个字(cp/adv 升序)。 */
static unsigned char *make_idx(int px, int ascent, int descent, int plane_w, int plane_h,
const uint32_t *cps, const int *advs, int nglyphs,
size_t *outlen) {
size_t len = 16 + 20 + (size_t)nglyphs * 16;
unsigned char *out = (unsigned char *)calloc(1, len);
unsigned char *o = out;
memcpy(o, "MFNI", 4); o += 4;
wr_le16(&o, 2); /* version=2 */
*o++ = 8; /* depth */
*o++ = 1; /* nfaces */
wr_le32(&o, (uint32_t)plane_w); wr_le32(&o, (uint32_t)plane_h);
/* face */
wr_le16(&o, (uint16_t)px); /* px */
wr_le16(&o, 0); /* plane_x */
wr_le16(&o, 0); /* plane_y */
wr_le16(&o, (uint16_t)plane_w); /* plane_w */
wr_le16(&o, (uint16_t)plane_h); /* plane_h */
wr_le16(&o, (uint16_t)ascent); /* ascent (s16) */
wr_le16(&o, (uint16_t)descent); /* descent (s16) */
wr_le16(&o, (uint16_t)nglyphs); /* glyph_count */
wr_le32(&o, 16 + 20); /* goff */
/* glyphs 升序 */
for (int i = 0; i < nglyphs; i++) {
wr_le32(&o, cps[i]); /* cp */
wr_le16(&o, 0); /* x */
wr_le16(&o, 0); /* y */
wr_le16(&o, (uint16_t)px); /* w */
wr_le16(&o, (uint16_t)px); /* h */
wr_le16(&o, (uint16_t)advs[i]); /* advance */
*o++ = 0; /* dx */
*o++ = 0; /* dy */
}
*outlen = len;
return out;
}
/* 写一套字库 <base>.png/.idx,返回 0 成功。 */
static int write_font(const char *base, int px, int ascent, int descent,
const uint32_t *cps, const int *advs, int nglyphs) {
char pngp[512], idxp[512];
snprintf(pngp, sizeof pngp, "%s.png", base);
snprintf(idxp, sizeof idxp, "%s.idx", base);
size_t plen = 0, ilen = 0;
unsigned char *png = make_png(64, 64, &plen);
if (!png) { fprintf(stderr, "make_png failed\n"); return -1; }
unsigned char *idx = make_idx(px, ascent, descent, 64, 64, cps, advs, nglyphs, &ilen);
if (!idx) { free(png); return -1; }
FILE *f = fopen(pngp, "wb");
if (!f) { free(png); free(idx); return -1; }
fwrite(png, 1, plen, f); fclose(f);
f = fopen(idxp, "wb");
if (!f) { free(png); free(idx); return -1; }
fwrite(idx, 1, ilen, f); fclose(f);
free(png); free(idx);
return 0;
}
/* ---------------- 断言 ---------------- */
static int g_fail = 0;
#define CHECK(cond, msg) do { \
if (!(cond)) { g_fail++; fprintf(stderr, "FAIL: %s (line %d)\n", msg, __LINE__); } \
else printf("ok : %s\n", msg); \
} while (0)
/* ---------------- 主测 ---------------- */
int main(void) {
/* 造两套 stub 字库 */
const uint32_t lat_cps[] = {0x20, 0x30, 0x41}; /* 空格 '0' 'A' */
const int lat_adv[] = {10, 10, 10};
const uint32_t cjk_cps[] = {0x4E2D}; /* '中' */
const int cjk_adv[] = {30};
if (write_font("/tmp/mf_lat", 20, 20, 6, lat_cps, lat_adv, 3) != 0) return 2;
if (write_font("/tmp/mf_cjk", 20, 25, 8, cjk_cps, cjk_adv, 1) != 0) return 2;
/* ---- (1) 字符分类器 ---- */
printf("\n== 分类器 meow_font_class_of ==\n");
CHECK(meow_font_class_of('A') == MEOW_FCLS_LATIN, "A -> LATIN");
CHECK(meow_font_class_of('z') == MEOW_FCLS_LATIN, "z -> LATIN");
CHECK(meow_font_class_of('5') == MEOW_FCLS_NUM, "5 -> NUM");
CHECK(meow_font_class_of(' ') == MEOW_FCLS_LATIN_SYM, "spc -> LATIN_SYM");
CHECK(meow_font_class_of(',') == MEOW_FCLS_LATIN_SYM, ", -> LATIN_SYM");
CHECK(meow_font_class_of('(') == MEOW_FCLS_LATIN_SYM, "( -> LATIN_SYM");
CHECK(meow_font_class_of('.') == MEOW_FCLS_LATIN_SYM, ". -> LATIN_SYM");
CHECK(meow_font_class_of(0x4E2D) == MEOW_FCLS_CJK, "U+4E2D -> CJK");
CHECK(meow_font_class_of(0x9FFF) == MEOW_FCLS_CJK, "U+9FFF -> CJK");
CHECK(meow_font_class_of(0x3400) == MEOW_FCLS_CJK, "U+3400(ExtA) -> CJK");
CHECK(meow_font_class_of(0x20000) == MEOW_FCLS_CJK, "U+20000(ExtB) -> CJK");
CHECK(meow_font_class_of(0x3001) == MEOW_FCLS_CJK_SYM, "U+3001(、) -> CJK_SYM");
CHECK(meow_font_class_of(0x3000) == MEOW_FCLS_CJK_SYM, "U+3000(全角空格) -> CJK_SYM");
CHECK(meow_font_class_of(0xFF0C) == MEOW_FCLS_CJK_SYM, "U+FF0C(,) -> CJK_SYM");
CHECK(meow_font_class_of(0xFF10) == MEOW_FCLS_CJK_SYM, "U+FF10(0) -> CJK_SYM");
CHECK(meow_font_class_of(0x2026) == MEOW_FCLS_CJK_SYM, "U+2026(…) -> CJK_SYM");
CHECK(meow_font_class_of(0x00B7) == MEOW_FCLS_CJK_SYM, "U+00B7(·) -> CJK_SYM");
CHECK(meow_font_class_of(0x3042) == MEOW_FCLS_OTHER, "U+3042(あ) -> OTHER");
CHECK(meow_font_class_of(0xAC00) == MEOW_FCLS_OTHER, "U+AC00(가) -> OTHER");
CHECK(meow_font_class_of(0xE9) == MEOW_FCLS_OTHER, "U+00E9(é) -> OTHER");
/* ---- (2) 多字体路由 ---- */
printf("\n== 单字体基线(multi off,全局=lat)==\n");
int r = meow_font_load("/tmp/mf_lat.png", "/tmp/mf_lat.idx");
CHECK(r == 0, "load global lat");
CHECK(meow_font_has_size(20), "lat has size 20");
CHECK(meow_font_width_text("A", 20) == 10, "w(A)=10 (lat)");
CHECK(meow_font_width_text("0", 20) == 10, "w(0)=10 (lat)");
/* lat 无 '中' → 缺字回退空格 adv=10 */
CHECK(meow_font_width_text("中", 20) == 10, "w(中)=10 fallback (lat no cjk)");
CHECK(meow_font_width_text("A中", 20) == 20, "w(A中)=20 (single font)");
CHECK(meow_font_has_glyph(0x4E2D) == 0, "has_glyph(中)=0 (lat lacks)");
CHECK(meow_font_has_glyph('A') == 1, "has_glyph(A)=1 (lat has)");
CHECK(meow_font_line_height(20) == 26, "lh(20)=26 (lat ascent20+desc6)");
printf("\n== 开启 multi_font(class_cjk -> cjk 槽)==\n");
meow_font_class_cfg cfg[MEOW_FCLS_COUNT];
memset(cfg, 0, sizeof cfg);
cfg[MEOW_FCLS_CJK].png = "/tmp/mf_cjk.png";
cfg[MEOW_FCLS_CJK].idx = "/tmp/mf_cjk.idx";
char gpng[64], gidx[64];
snprintf(gpng, sizeof gpng, "/tmp/mf_lat.png");
snprintf(gidx, sizeof gidx, "/tmp/mf_lat.idx");
int distinct = 0, truncd = 0;
r = meow_font_config_multifont(1, gpng, gidx, cfg, &distinct, &truncd);
CHECK(r == 0, "config_multifont ok");
CHECK(distinct == 2, "distinct=2 (lat + cjk)");
CHECK(truncd == 0, "not truncated");
CHECK(meow_font_width_text("A", 20) == 10, "w(A)=10 (routed -> global/lat, latin class)");
CHECK(meow_font_width_text("0", 20) == 10, "w(0)=10 (num class -> global)");
CHECK(meow_font_width_text("中", 20) == 30, "w(中)=30 (routed -> cjk slot)");
CHECK(meow_font_width_text("A中", 20) == 40, "w(A中)=40 (per-char routing)");
CHECK(meow_font_has_glyph(0x4E2D) == 1, "has_glyph(中)=1 (routed -> cjk)");
CHECK(meow_font_line_height(20) == 33, "lh(20)=33 (max(lat26, cjk33))");
/* 关闭 → 立即回到单字体 */
meow_font_config_multifont(0, gpng, gidx, cfg, &distinct, &truncd);
CHECK(meow_font_width_text("A中", 20) == 20, "off -> w(A中)=20 again");
CHECK(meow_font_has_glyph(0x4E2D) == 0, "off -> has_glyph(中)=0 again");
/* drawtext smoke:吃一个混合串不崩(当前 cfg 仍为合法 cjk 配置) */
printf("\n== drawtext smoke(不影响断言,仅验证不崩)==\n");
meow_font_set_fb(64, 64, 128);
static uint16_t buf[64 * 64];
memset(buf, 0, sizeof buf);
meow_font_config_multifont(1, gpng, gidx, cfg, &distinct, &truncd);
meow_font_drawtext(buf, 0, 0, "A中0,", 0xFFFF, 20);
printf("drew 'A中0,' ok\n");
printf("\n== family 去重 / 上限 ==\n");
/* latin 类别也指到 cjk → 应去重,distinct 仍为 2 */
memset(cfg, 0, sizeof cfg);
cfg[MEOW_FCLS_CJK].png = "/tmp/mf_cjk.png"; cfg[MEOW_FCLS_CJK].idx = "/tmp/mf_cjk.idx";
cfg[MEOW_FCLS_LATIN].png = "/tmp/mf_cjk.png"; cfg[MEOW_FCLS_LATIN].idx = "/tmp/mf_cjk.idx";
meow_font_config_multifont(1, gpng, gidx, cfg, &distinct, &truncd);
CHECK(distinct == 2, "dedupe: latin==cjk path -> distinct=2");
CHECK(truncd == 0, "dedupe: not truncated");
/* 超上限:6 类各指向一个不同、可加载的字体(除全局)→ 第 5 个起截断。
* MEOW_MAX_SLOTS=5(global + ≤4 类别),故最多 5 套不同字库。 */
{
/* 生成 5 个额外不同字库(不同 advance,保证去重键不同) */
const uint32_t fcps[] = {0x4E2D};
int fav = 100;
char paths[8][64], idxs[8][64];
for (int i = 2; i < 7; i++) { /* 生成 /tmp/mf_trunc2..6 */
snprintf(paths[i], sizeof paths[i], "/tmp/mf_trunc%d", i);
if (write_font(paths[i], 20, 25, 8, fcps, &fav, 1) != 0) return 2;
snprintf(paths[i], sizeof paths[i], "/tmp/mf_trunc%d.png", i);
snprintf(idxs[i], sizeof idxs[i], "/tmp/mf_trunc%d.idx", i);
fav += 50;
}
char pp[6][64];
/* 6 类全部指向不同非全局字库 */
snprintf(pp[MEOW_FCLS_CJK], sizeof pp[0], "/tmp/mf_trunc2.png");
snprintf(pp[MEOW_FCLS_CJK_SYM], sizeof pp[0], "/tmp/mf_trunc3.png");
snprintf(pp[MEOW_FCLS_NUM], sizeof pp[0], "/tmp/mf_trunc4.png");
snprintf(pp[MEOW_FCLS_LATIN], sizeof pp[0], "/tmp/mf_trunc5.png");
snprintf(pp[MEOW_FCLS_LATIN_SYM], sizeof pp[0], "/tmp/mf_trunc6.png");
snprintf(pp[MEOW_FCLS_OTHER], sizeof pp[0], "/tmp/mf_trunc2.png"); /* 与 CJK 同 → 去重,不新增 */
for (int i = 0; i < 6; i++) { cfg[i].png = pp[i]; cfg[i].idx = NULL; }
/* 修 idx:write_font 已生对应 .idx,需给出 */
char ppi[6][64];
const char *maps[6] = {"/tmp/mf_trunc2", "/tmp/mf_trunc3", "/tmp/mf_trunc4",
"/tmp/mf_trunc5", "/tmp/mf_trunc6", "/tmp/mf_trunc2"};
for (int i = 0; i < 6; i++) {
snprintf(ppi[i], sizeof ppi[i], "%s.idx", maps[i]);
cfg[i].idx = ppi[i];
}
meow_font_config_multifont(1, gpng, gidx, cfg, &distinct, &truncd);
CHECK(truncd == 1, "6 distinct -> truncated=1");
CHECK(distinct == MEOW_MAX_SLOTS, "distinct capped at MEOW_MAX_SLOTS");
}
/* ---- (3) 字体档位分层:meow_font_reload_tier(大字库按档装载/切换)----
* 用同两套 stub(lat=common 模拟,cjk=gbk 模拟)+ 一个不会释放的 cjk 大面,验证
* tier>0 装载大字库、切换 unload 旧/load 新、失败回退。 */
printf("\n== 字体档位分层 meow_font_reload_tier ==\n");
/* 档位 0=common/1=gbk/2=full(与 meow_ui.h MEOW_FONT_TIER_* 一致;本测试只链 meow_font.h) */
r = meow_font_reload_tier(0, "/tmp/mf_lat.png", "/tmp/mf_lat.idx");
CHECK(r == 0, "reload_tier(common, lat) ok");
CHECK(meow_font_has_glyph('A') == 1, "reload common -> has_glyph(A)=1");
CHECK(meow_font_has_glyph(0x4E2D) == 0, "reload common -> has_glyph(中)=0 (lat no cjk)");
CHECK(meow_font_width_text("A", 20) == 10, "reload common -> w(A)=10");
/* 切到大字库(cjk 槽):应 unload 旧 lat 再 load cjk → '中' 可用、'A' 缺。 */
r = meow_font_reload_tier(1, "/tmp/mf_cjk.png", "/tmp/mf_cjk.idx");
CHECK(r == 0, "reload_tier(gbk, cjk) ok");
CHECK(meow_font_has_glyph(0x4E2D) == 1, "reload gbk -> has_glyph(中)=1");
CHECK(meow_font_has_glyph('A') == 0, "reload gbk -> has_glyph(A)=0 (cjk lacks lat)");
CHECK(meow_font_width_text("中", 20) == 30, "reload gbk -> w(中)=30 (cjk slot)");
CHECK(meow_font_line_height(20) == 33, "reload gbk -> lh(20)=33 (cjk ascent25+desc8)");
/* 切回 common:再 unload cjk / load lat → 'A' 回来。 */
r = meow_font_reload_tier(0, "/tmp/mf_lat.png", "/tmp/mf_lat.idx");
CHECK(r == 0, "reload_tier(common) again ok");
CHECK(meow_font_has_glyph('A') == 1, "reload back common -> has_glyph(A)=1");
CHECK(meow_font_has_glyph(0x4E2D) == 0, "reload back common -> has_glyph(中)=0");
CHECK(meow_font_width_text("A", 20) == 10, "reload back common -> w(A)=10");
/* Full 档(tier=2)同一套 cjk 作为大字库装载也应成功(档仅记录,装载同路径)。 */
r = meow_font_reload_tier(2, "/tmp/mf_cjk.png", "/tmp/mf_cjk.idx");
CHECK(r == 0, "reload_tier(full, cjk) ok");
CHECK(meow_font_has_glyph(0x4E2D) == 1, "reload full -> has_glyph(中)=1");
/* 非法参数:NULL/空路径 → 负值(不崩),且字库已被上次 reload 释放(档位装载失败不驻留)。 */
r = meow_font_reload_tier(1, NULL, NULL);
CHECK(r < 0, "reload_tier(NULL paths) -> negative");
CHECK(meow_font_has_glyph('A') == 0, "reload failed -> font unloaded (has_glyph=0)");
printf("\n%s (%d failures)\n", g_fail ? "RESULT: FAIL" : "RESULT: PASS", g_fail);
return g_fail ? 1 : 0;
}