三特性: 字体分层(gen_font.py --charset + meow_font_reload_tier + font.load_full + 尺寸标记) + 全屏机制/状态栏收合(meow_ui_sys/input) + 通知#6 TF转储(CSV+10MB+开机清板载+内存表)
This commit is contained in:
1 parent
0b4d1d3215
commit
a2673d5922
18 files changed
+819
-80
No files matched your search
+93
-6
@@ -44,8 +44,11 @@ idx 二进制布局(little-endian, version=2):
|
||||
+15 u8 dy
|
||||
"""
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import struct
|
||||
import unicodedata
|
||||
import zlib
|
||||
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
@@ -96,32 +99,80 @@ def pack_plane_depth(plane_l8, width, height, depth):
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# 字符集
|
||||
# 字符集(字体档位:common / gbk / full)
|
||||
# 口径见 docs/MEOWOS_SPEC_REF.md §5.2「字体档位/大字库」与 §5.7.8:
|
||||
# common ≈ 4k 码位(ASCII + GB2312 常用 + 标点 + --extra) = 现状;
|
||||
# gbk ≈ 20k+(再并入 CJK 统一表意 U+4E00-9FFF + CJK 符号/全角,含常用简繁/生僻);
|
||||
# full ≈ 全 Unicode 可渲染区间(gbk + CJK 扩展 A/B-F + 拉丁/希腊/西里尔 + 通用标点,可选)。
|
||||
# 每档在产物 font.json 写入 charset + glyph_count(见 write_font_json)。
|
||||
# --------------------------------------------------------------------------
|
||||
def build_charset(extra=""):
|
||||
CHARSET_TIERS = ("common", "gbk", "full")
|
||||
CHARSET_ALIAS = {"0": "common", "common": "common", "1": "gbk", "gbk": "gbk",
|
||||
"2": "full", "full": "full"}
|
||||
|
||||
|
||||
def build_charset(tier="common", extra=""):
|
||||
if tier not in CHARSET_TIERS:
|
||||
tier = "common"
|
||||
cps = []
|
||||
def add(cp):
|
||||
if cp not in cps:
|
||||
if 0 <= cp <= 0x10FFFF and cp not in cps:
|
||||
cps.append(cp)
|
||||
# ASCII 可打印字符
|
||||
for c in range(0x20, 0x7F):
|
||||
add(c)
|
||||
# GB2312 一/二级常用简体(B0-D7 + D8 常用二级首段)
|
||||
for hi in range(0xB0, 0xD8):
|
||||
for lo in range(0xA1, 0xFF):
|
||||
try:
|
||||
add(ord(bytes([hi, lo]).decode("gb2312")))
|
||||
except UnicodeDecodeError:
|
||||
pass
|
||||
for hi in range(0xA1, 0xA4):
|
||||
# GB2312 符号/全角(A1-A4)
|
||||
for hi in range(0xA1, 0xA5):
|
||||
for lo in range(0xA1, 0xFF):
|
||||
try:
|
||||
add(ord(bytes([hi, lo]).decode("gb2312")))
|
||||
except UnicodeDecodeError:
|
||||
pass
|
||||
if tier in ("gbk", "full"):
|
||||
# CJK 统一表意(含常用简繁/生僻):U+4E00-9FFF ≈ 2万+
|
||||
for cp in range(0x4E00, 0xA000):
|
||||
add(cp)
|
||||
# CJK 符号与标点 + 全角形式(补齐 common 未含的)
|
||||
for cp in range(0x3000, 0x3040):
|
||||
add(cp)
|
||||
for cp in range(0xFF00, 0xFFF0):
|
||||
add(cp)
|
||||
if tier == "full":
|
||||
# 拉丁补充 / 希腊 / 西里尔 等常用非 CJK(Latin-1 起已覆盖 AI 常见重音)
|
||||
for cp in range(0xA0, 0x0530):
|
||||
add(cp)
|
||||
# CJK 扩展 A + 兼容表意
|
||||
for cp in range(0x3400, 0x4DC0):
|
||||
add(cp)
|
||||
for cp in range(0xF900, 0xFB00):
|
||||
add(cp)
|
||||
# 通用标点 / 货币等(— … “ ” 等已在 gbk;此处补全)
|
||||
for cp in range(0x2000, 0x2070):
|
||||
add(cp)
|
||||
for cp in range(0x20A0, 0x20D0):
|
||||
add(cp)
|
||||
# CJK 扩展 B-F(补充平面)
|
||||
for cp in range(0x20000, 0x2EBF0):
|
||||
add(cp)
|
||||
for cp in range(0x30000, 0x31350):
|
||||
add(cp)
|
||||
for ch in extra:
|
||||
add(ord(ch))
|
||||
return sorted(cps)
|
||||
|
||||
|
||||
def charset_glyph_count(cps):
|
||||
"""glyph_count = 字符集码位数(信息用,落 font.json)。"""
|
||||
return len(cps)
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# 逐字形渲染
|
||||
# --------------------------------------------------------------------------
|
||||
@@ -227,6 +278,33 @@ def write_idx(path, bit_depth, faces, glyph_tabs):
|
||||
return len(header) + len(body)
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# font.json(元数据 + 完整性校验;schema 见 docs/MEOWOS_SPEC_REF.md §5.2)
|
||||
# --------------------------------------------------------------------------
|
||||
def write_font_json(out_dir, name, family, tier, glyph_count, sizes, bitdepth,
|
||||
png_path, idx_path):
|
||||
def sha256(path):
|
||||
h = hashlib.sha256()
|
||||
with open(path, "rb") as f:
|
||||
for blk in iter(lambda: f.read(1 << 16), b""):
|
||||
h.update(blk)
|
||||
return h.hexdigest()
|
||||
meta = {
|
||||
"family": family or name, # 同目录名(信息用;扫描端以目录名为 family)
|
||||
"name": name, # 下拉显示名(缺则回退目录名)
|
||||
"glyph_count": glyph_count, # 信息用
|
||||
"charset": tier, # common|gbk|full(档位;驱动按需加载)
|
||||
"size_list": sizes, # 原生面字号
|
||||
"bit_depth": bitdepth, # 4|8
|
||||
"sha256_png": sha256(png_path), # font.png 校验(扫描端仅 WARN)
|
||||
"sha256_idx": sha256(idx_path), # font.idx 校验(扫描端仅 WARN)
|
||||
}
|
||||
jpath = os.path.join(out_dir, "font.json")
|
||||
with open(jpath, "w", encoding="utf-8") as f:
|
||||
json.dump(meta, f, ensure_ascii=False, indent=2)
|
||||
return jpath
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# 主流程
|
||||
# --------------------------------------------------------------------------
|
||||
@@ -236,14 +314,18 @@ def main():
|
||||
ap.add_argument("--sizes", default="28,20")
|
||||
ap.add_argument("--out", required=True)
|
||||
ap.add_argument("--name", default="font")
|
||||
ap.add_argument("--family", default="", help="font.json family(缺省 = --name;信息用)")
|
||||
ap.add_argument("--charset", default="common", choices=CHARSET_TIERS,
|
||||
help="字体档位:common≈4k / gbk≈20k+ / full(可选,默认不推)")
|
||||
ap.add_argument("--bitdepth", type=int, default=4, choices=(4, 8))
|
||||
ap.add_argument("--extra", default="")
|
||||
args = ap.parse_args()
|
||||
|
||||
os.makedirs(args.out, exist_ok=True)
|
||||
sizes = [int(s) for s in args.sizes.split(",") if s.strip()]
|
||||
cps = build_charset(args.extra)
|
||||
print(f"字符集: {len(cps)} 码位")
|
||||
tier = CHARSET_ALIAS.get(args.charset, "common")
|
||||
cps = build_charset(tier, args.extra)
|
||||
print(f"字符集: {len(cps)} 码位 (charset={tier})")
|
||||
|
||||
all_planes = [] # (aw, ah, plane_l8 bytes, tab)
|
||||
max_w = 0
|
||||
@@ -311,10 +393,15 @@ def main():
|
||||
idx_path = os.path.join(args.out, f"{args.name}.idx")
|
||||
idx_size = write_idx(idx_path, args.bitdepth, face_meta, [p[3] for p in all_planes])
|
||||
|
||||
# ---- font.json(写入 charset + glyph_count + sha256,供扫描端标注档位/大小标记) ----
|
||||
jpath = write_font_json(args.out, args.name, args.family, tier, charset_glyph_count(cps),
|
||||
sizes, args.bitdepth, png_path, idx_path)
|
||||
|
||||
png_size = os.path.getsize(png_path)
|
||||
total = png_size + idx_size
|
||||
print(f"\nfont.png {png_size} B")
|
||||
print(f"font.idx {idx_size} B")
|
||||
print(f"font.json {os.path.getsize(jpath)} B (charset={tier}, glyph_count={len(cps)})")
|
||||
print(f"合计 {total} B = {total/1024:.1f} KiB = {total/1048576:.2f} MiB (预算 2MiB)")
|
||||
|
||||
|
||||
|
||||
@@ -529,6 +529,22 @@ int meow_font_load(const char *png_path, const char *idx_path) {
|
||||
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) {
|
||||
|
||||
@@ -33,6 +33,15 @@ int meow_font_load(const char *png_path, const char *idx_path);
|
||||
/* 释放字库。 */
|
||||
void meow_font_unload(void);
|
||||
|
||||
/* 按档装载大字库(字体档位分层,见 MEOWOS_SPEC_REF §5.2/§5.7.8 与 meow_ui.h §10):
|
||||
* tier = 0 common≈4k(小/默认)/ 1 gbk≈20k+(大字库)/ 2 full(全,可选)。
|
||||
* png/idx 为调用方已按档解析的设备绝对路径(launcher 经 env 注入)。切换时先 unload 旧
|
||||
* 再 load 新(释放旧 atlas —— 大字库 GBK 解码约 5-15MB,避免新旧同时驻留内存)。
|
||||
* 返回 0 成功,负值失败(失败时旧库已释放,调用方应回退卡默认/固件)。
|
||||
* 由 meow_ui_gfx.c 的 meow_font_set_tier() 在档位变化时唤起;meow_font_load() 仍是装载
|
||||
* 系统默认(slot0)字库的入口。 */
|
||||
int meow_font_reload_tier(int tier, const char *png, const char *idx);
|
||||
|
||||
/* 某字号是否存在(命中原生面返回 1)。 */
|
||||
int meow_font_has_size(int size);
|
||||
|
||||
|
||||
@@ -266,6 +266,38 @@ int main(void) {
|
||||
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;
|
||||
}
|
||||
Reference in new issue
Block a user