三特性: 字体分层(gen_font.py --charset + meow_font_reload_tier + font.load_full + 尺寸标记) + 全屏机制/状态栏收合(meow_ui_sys/input) + 通知#6 TF转储(CSV+10MB+开机清板载+内存表)

This commit is contained in:
MeowOS Dev committed 2026-08-30 11:46:29 +08:00
1 parent 0b4d1d3215
commit a2673d5922
18 files changed
+819 -80

No files matched your search

+93 -6
View File
@@ -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)")
+16
View File
@@ -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) {
+9
View File
@@ -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);
+32
View File
@@ -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;
}