272 lines
14 KiB
C
272 lines
14 KiB
C
/*
|
||
* 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");
|
||
}
|
||
|
||
printf("\n%s (%d failures)\n", g_fail ? "RESULT: FAIL" : "RESULT: PASS", g_fail);
|
||
return g_fail ? 1 : 0;
|
||
}
|