feat(launcher): 字体扫描迁移三级目录 (agent-193) — <family>/<weight>/<tier>/ 支持(两级向后兼容); FONT_PRESETS_MAX 17→64; 设置页 family→weight 两级不变,同weight多tier按加载大字库开关选档(关=common优先/开=gbk优先); index 落盘含三级条目
launcher ab8e610f→d3cae88b; test_font_tier/test_font_weight PASS 已知缺口: SDK meow_font.c MEOW_FONT_INDEX_MAX=32 截断(>32套需同步提); S_NET_MAX=8 family 上限; default 字体仍两级
This commit is contained in:
1 parent
6603e6686c
commit
3fd4726198
5 files changed
+366
-54
No files matched your search
@@ -2,17 +2,20 @@
|
|||||||
/* 本文件由拆分工具从 meow_launcherd.c 按职责搬移;逻辑与原文 0-diff(仅 static 可见性按跨模块需求调整)。*/
|
/* 本文件由拆分工具从 meow_launcherd.c 按职责搬移;逻辑与原文 0-diff(仅 static 可见性按跨模块需求调整)。*/
|
||||||
#include "meow_launcher_internal.h"
|
#include "meow_launcher_internal.h"
|
||||||
|
|
||||||
/* M2.6 字重结构:卡内字体 = <卡根>/MeowOS/fonts/<family>/<weight>/(三件套)。
|
/* M2.6 字重结构 + M2.8 三级定案(FONT_SYSTEM):卡内字体 =
|
||||||
* 每个 preset = 一套 <family>/<weight>(如 misans/regular)。值 = 卡根相对路径
|
* <卡根>/MeowOS/fonts/<family>/<weight>/(两级,向后兼容)或
|
||||||
* MeowOS/fonts/<family>/<weight>/font.png;family/weight 单列供设置页「family→字重」子菜单
|
* <卡根>/MeowOS/fonts/<family>/<weight>/<tier>/(三级,tier=common/gbk/full)。
|
||||||
* 分组与 meow_font_effective_paths_tiered 按 family+weight 选库。 */
|
* 每个 preset = 一套 <family>/<weight>[/<tier>](如 misans/regular 或 source_han_sans_cn/regular/gbk)。
|
||||||
|
* 值 = 卡根相对路径 MeowOS/fonts/<family>/<weight>[/<tier>]/font.png;family/weight 单列供设置页
|
||||||
|
* 「family→字重」子菜单分组与 meow_font_effective_paths_tiered 按 family+weight 选库。 */
|
||||||
const char *font_presets[FONT_PRESETS_MAX]; /* SC_PRESET 值 = 卡根相对路径 */
|
const char *font_presets[FONT_PRESETS_MAX]; /* SC_PRESET 值 = 卡根相对路径 */
|
||||||
|
|
||||||
const char *font_preset_labels[FONT_PRESETS_MAX]; /* SC_PRESET 中文标签(含字重+档位大小标记) */
|
const char *font_preset_labels[FONT_PRESETS_MAX]; /* SC_PRESET 中文标签(含字重+档位大小标记) */
|
||||||
|
|
||||||
/* M2.5x 字体档位:每套的 charset 档("common"/"gbk"/"full",取自 font.json,缺省 "common")
|
/* M2.5x/M2.8 字体档位:每套的档("common"/"gbk"/"full";两级取 font.json charset,缺省
|
||||||
* 与 glyph_count(信息用)。供档位分层装载(meow_font_effective_paths_tiered)与设置页标注。 */
|
* "common";三级取 tier 目录名,权威)与 glyph_count(信息用)。供档位分层装载
|
||||||
static const char *font_preset_tiers[FONT_PRESETS_MAX];
|
* (meow_font_effective_paths_tiered)与设置页标注/选档。 */
|
||||||
|
const char *font_preset_tiers[FONT_PRESETS_MAX];
|
||||||
|
|
||||||
static int font_preset_glyphs[FONT_PRESETS_MAX];
|
static int font_preset_glyphs[FONT_PRESETS_MAX];
|
||||||
|
|
||||||
@@ -216,10 +219,12 @@ int meow_font_effective_tier(int app_tier)
|
|||||||
* - 生效档 = max(app 声明档, font.load_full ? GBK : COMMON):系统「加载大字库」开 →
|
* - 生效档 = max(app 声明档, font.load_full ? GBK : COMMON):系统「加载大字库」开 →
|
||||||
* 强制 ≥gbk;
|
* 强制 ≥gbk;
|
||||||
* - tier<=common → 走既有 meow_font_effective_paths(font.file/卡默认/固件);
|
* - tier<=common → 走既有 meow_font_effective_paths(font.file/卡默认/固件);
|
||||||
* - tier>0 → 在已扫描的 MeowOS/fonts/<family>/<weight>/ 里找 charset 匹配(gbk 请求接受
|
* - tier>0 → 在已扫描的 preset(两级或三级,值路径 = MeowOS/fonts/<family>/<weight>[/<tier>]/font.png)
|
||||||
* gbk/full;full 请求接受 full)的那套,以其 font.png/idx 绝对路径返回(family = 一级目录名);
|
* 里找档位匹配(gbk 请求接受 gbk/full;full 请求接受 full)的那套,以其 font.png/idx 绝对路径返回
|
||||||
|
* (family = 一级目录名)。
|
||||||
* - M2.6 字重:优先选「与用户当前 font.file 同 family(其次同 weight)」的大字库,避免
|
* - M2.6 字重:优先选「与用户当前 font.file 同 family(其次同 weight)」的大字库,避免
|
||||||
* 大字库跳到另一家字体;找不到匹配 → 回退 meow_font_effective_paths(避免点到固件小库)。
|
* 大字库跳到另一家字体(三级同 family 下按 tier 找 gbk/full,见下);找不到匹配 →
|
||||||
|
* 回退 meow_font_effective_paths(避免点到固件小库)。
|
||||||
* 返回 0 成功(out_png/out_idx/out_family 已填),-1 无可用。 */
|
* 返回 0 成功(out_png/out_idx/out_family 已填),-1 无可用。 */
|
||||||
int meow_font_effective_paths_tiered(int tier, char *out_png, int plen,
|
int meow_font_effective_paths_tiered(int tier, char *out_png, int plen,
|
||||||
char *out_idx, int ilen, char *out_family, int flen)
|
char *out_idx, int ilen, char *out_family, int flen)
|
||||||
@@ -462,11 +467,15 @@ static void font_tier_marker(const char *charset, int glyph_count, char *out, in
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* 探测一个 <卡根>/MeowOS/fonts/<family>/<weight> 套件是否合法:三件套齐全 → 解析 label+档位+字重。
|
/* 探测一套件是否合法:三件套齐全 → 解析 label+档位+字重。
|
||||||
|
* dirpath = <卡根>/MeowOS/fonts/<family>/<weight>(两级)或 .../<family>/<weight>/<tier>(三级)。
|
||||||
|
* tier_dir:三级时传该 tier 目录名(common/gbk/full,权威档位);两级传 NULL(档位读
|
||||||
|
* font.json charset,缺省 common)。
|
||||||
* 返回 1=进列表,0=跳过(缺组件/json 不可读)。sha 失败仅 WARN。
|
* 返回 1=进列表,0=跳过(缺组件/json 不可读)。sha 失败仅 WARN。
|
||||||
* 输出:label=显示名 `<name> <Weight>(common≈4k)`(如 `MiSans Regular(common≈4k)`);
|
* 输出:label=显示名 `<name> <Weight>(common≈4k)`(如 `MiSans Regular(common≈4k)`);
|
||||||
* charset/glyph_count=档位信息;wtout=weight slug(缺省 "regular")。 */
|
* charset/glyph_count=档位信息;wtout=weight slug(缺省 "regular")。 */
|
||||||
static int meow_font_probe_weight(const char *dirpath, const char *family, const char *weight,
|
static int meow_font_probe_weight(const char *dirpath, const char *family, const char *weight,
|
||||||
|
const char *tier_dir,
|
||||||
char *label, int llen,
|
char *label, int llen,
|
||||||
char *charset, int clen, int *glyph_count,
|
char *charset, int clen, int *glyph_count,
|
||||||
char *wtout, int wtlen, char *nameout, int namelen)
|
char *wtout, int wtlen, char *nameout, int namelen)
|
||||||
@@ -475,8 +484,11 @@ static int meow_font_probe_weight(const char *dirpath, const char *family, const
|
|||||||
snprintf(ppath, sizeof ppath, "%s/font.png", dirpath);
|
snprintf(ppath, sizeof ppath, "%s/font.png", dirpath);
|
||||||
snprintf(ipath, sizeof ipath, "%s/font.idx", dirpath);
|
snprintf(ipath, sizeof ipath, "%s/font.idx", dirpath);
|
||||||
snprintf(jpath, sizeof jpath, "%s/font.json", dirpath);
|
snprintf(jpath, sizeof jpath, "%s/font.json", dirpath);
|
||||||
|
const int is3 = (tier_dir != NULL && tier_dir[0] != 0);
|
||||||
|
const char *tsep = is3 ? "/" : "";
|
||||||
|
const char *tiernm = is3 ? tier_dir : "";
|
||||||
if (access(ppath, R_OK) != 0 || access(ipath, R_OK) != 0 || access(jpath, R_OK) != 0) {
|
if (access(ppath, R_OK) != 0 || access(ipath, R_OK) != 0 || access(jpath, R_OK) != 0) {
|
||||||
logmsg("fonts: skip '%s/%s' (缺 font.png/font.idx/font.json)", family, weight);
|
logmsg("fonts: skip '%s/%s%s%s' (缺 font.png/font.idx/font.json)", family, weight, tsep, tiernm);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
char name[64] = "", jsha_png[80] = "", jsha_idx[80] = "";
|
char name[64] = "", jsha_png[80] = "", jsha_idx[80] = "";
|
||||||
@@ -484,27 +496,28 @@ static int meow_font_probe_weight(const char *dirpath, const char *family, const
|
|||||||
meow_font_json_probe(jpath, name, sizeof name, jsha_png, sizeof jsha_png,
|
meow_font_json_probe(jpath, name, sizeof name, jsha_png, sizeof jsha_png,
|
||||||
jsha_idx, sizeof jsha_idx, cs, sizeof cs, &gc, wts, sizeof wts);
|
jsha_idx, sizeof jsha_idx, cs, sizeof cs, &gc, wts, sizeof wts);
|
||||||
const char *wt = wts[0] ? wts : (weight ? weight : "regular");
|
const char *wt = wts[0] ? wts : (weight ? weight : "regular");
|
||||||
|
/* 档位:三级定案 = tier 目录名(权威);两级(向后兼容)= font.json charset(缺省 common)。 */
|
||||||
|
const char *tier = is3 ? tier_dir : (cs[0] ? cs : "common");
|
||||||
if (wtout) snprintf(wtout, wtlen, "%s", wt);
|
if (wtout) snprintf(wtout, wtlen, "%s", wt);
|
||||||
if (nameout) snprintf(nameout, namelen, "%s", name[0] ? name : family);
|
if (nameout) snprintf(nameout, namelen, "%s", name[0] ? name : family);
|
||||||
/* 显示名 = `<name> <Weight>(common≈4k)`(字重缺省 Regular) */
|
/* 显示名 = `<name> <Weight>(common≈4k)`(字重缺省 Regular) */
|
||||||
char wd[24]; font_weight_display(wt, wd, sizeof wd);
|
char wd[24]; font_weight_display(wt, wd, sizeof wd);
|
||||||
char marker[24];
|
char marker[24];
|
||||||
font_tier_marker(cs, gc, marker, sizeof marker);
|
font_tier_marker(tier, gc, marker, sizeof marker);
|
||||||
snprintf(label, llen, "%s %s(%s)", name[0] ? name : family, wd, marker);
|
snprintf(label, llen, "%s %s(%s)", name[0] ? name : family, wd, marker);
|
||||||
if (charset) snprintf(charset, clen, "%s", cs[0] ? cs : "common");
|
if (charset) snprintf(charset, clen, "%s", tier);
|
||||||
if (glyph_count) *glyph_count = gc;
|
if (glyph_count) *glyph_count = gc;
|
||||||
/* 完整性校验:仅 WARN,不硬失败;该套仍标记可选。 */
|
/* 完整性校验:仅 WARN,不硬失败;该套仍标记可选。 */
|
||||||
char apng[80] = "", aidx[80] = "";
|
char apng[80] = "", aidx[80] = "";
|
||||||
if (font_sha256_file(ppath, apng, sizeof apng) != 0)
|
if (font_sha256_file(ppath, apng, sizeof apng) != 0)
|
||||||
logmsg("fonts: '%s/%s' sha256(png) 不可用,跳过校验", family, wt);
|
logmsg("fonts: '%s/%s%s%s' sha256(png) 不可用,跳过校验", family, wt, tsep, tiernm);
|
||||||
else if (jsha_png[0] && !font_sha_eq(apng, jsha_png))
|
else if (jsha_png[0] && !font_sha_eq(apng, jsha_png))
|
||||||
logmsg("fonts: '%s/%s' WARN sha256_png 不匹配(仍可选)", family, wt);
|
logmsg("fonts: '%s/%s%s%s' WARN sha256_png 不匹配(仍可选)", family, wt, tsep, tiernm);
|
||||||
if (font_sha256_file(ipath, aidx, sizeof aidx) != 0)
|
if (font_sha256_file(ipath, aidx, sizeof aidx) != 0)
|
||||||
logmsg("fonts: '%s/%s' sha256(idx) 不可用,跳过校验", family, wt);
|
logmsg("fonts: '%s/%s%s%s' sha256(idx) 不可用,跳过校验", family, wt, tsep, tiernm);
|
||||||
else if (jsha_idx[0] && !font_sha_eq(aidx, jsha_idx))
|
else if (jsha_idx[0] && !font_sha_eq(aidx, jsha_idx))
|
||||||
logmsg("fonts: '%s/%s' WARN sha256_idx 不匹配(仍可选)", family, wt);
|
logmsg("fonts: '%s/%s%s%s' WARN sha256_idx 不匹配(仍可选)", family, wt, tsep, tiernm);
|
||||||
logmsg("fonts: add '%s/%s' (%s) charset=%s glyph_count=%d", family, wt, label,
|
logmsg("fonts: add '%s/%s%s%s' (%s) tier=%s glyph_count=%d", family, wt, tsep, tiernm, label, tier, gc);
|
||||||
cs[0]?cs:"common", gc);
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -572,15 +585,51 @@ static void meow_font_write_index(const char *card_root)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* 扫描 <card_root>/MeowOS/fonts/ 下两级 <family>/<weight>/,重建字体 preset 列表。
|
/* 把一个探测成功的套装登记为 preset i(值路径 = MeowOS/fonts/<family>/<weight>[/<tier>]/font.png)。
|
||||||
* 每个 (family,weight) 组合一套(如 misans/regular、misans/bold);family 级 'default' 由
|
* tier 非空 → 三级路径;否则 → 两级路径(旧结构向后兼容)。返回 0 成功,-1 已满。 */
|
||||||
* 索引 0(默认字体)单独处理,不进循环,避免重复。上限 FONT_PRESETS_MAX
|
static int font_presets_add(const char *family, const char *weight, const char *tier,
|
||||||
* (1 默认 + ≤16 用户字体)。无卡/无目录时仅默认。
|
const char *label, const char *cs, int gc,
|
||||||
|
const char *wt, const char *nm)
|
||||||
|
{
|
||||||
|
if (font_presets_n >= FONT_PRESETS_MAX) return -1;
|
||||||
|
int i = font_presets_n;
|
||||||
|
if (tier && tier[0])
|
||||||
|
snprintf(font_preset_val_buf[i], sizeof font_preset_val_buf[i],
|
||||||
|
"%s/%s/%s/%s/font.png", MEOW_FONTS_CFG, family, weight, tier);
|
||||||
|
else
|
||||||
|
snprintf(font_preset_val_buf[i], sizeof font_preset_val_buf[i],
|
||||||
|
"%s/%s/%s/font.png", MEOW_FONTS_CFG, family, weight);
|
||||||
|
snprintf(font_preset_label_buf[i], sizeof font_preset_label_buf[i], "%s", label);
|
||||||
|
snprintf(font_preset_tier_buf[i], sizeof font_preset_tier_buf[i], "%s", cs && cs[0] ? cs : "common");
|
||||||
|
snprintf(font_preset_fam_buf[i], sizeof font_preset_fam_buf[i], "%s", family);
|
||||||
|
snprintf(font_preset_wt_buf[i], sizeof font_preset_wt_buf[i], "%s", wt && wt[0] ? wt : "regular");
|
||||||
|
snprintf(font_preset_name_buf[i], sizeof font_preset_name_buf[i], "%s", nm ? nm : family);
|
||||||
|
font_presets[i] = font_preset_val_buf[i];
|
||||||
|
font_preset_labels[i] = font_preset_label_buf[i];
|
||||||
|
font_preset_tiers[i] = font_preset_tier_buf[i];
|
||||||
|
font_preset_fams[i] = font_preset_fam_buf[i];
|
||||||
|
font_preset_wts[i] = font_preset_wt_buf[i];
|
||||||
|
font_preset_names[i] = font_preset_name_buf[i];
|
||||||
|
font_preset_glyphs[i] = gc;
|
||||||
|
font_presets_n++;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* 扫描 <card_root>/MeowOS/fonts/ 下字体,重建字体 preset 列表。
|
||||||
|
* 布局(FONT_SYSTEM 三级定案):
|
||||||
|
* - 两级(向后兼容):<family>/<weight>/font.png → 每 (family,weight) 一套,tier 读 font.json
|
||||||
|
* charset(缺省 common)。
|
||||||
|
* - 三级(定案):<family>/<weight>/<tier>/(tier=common/gbk/full,每 tier 一套三件套)→
|
||||||
|
* 每 (family,weight,tier) 一套,tier=目录名(权威)。
|
||||||
|
* - 同一 (family,weight) 下:若 weight 根有 font.png(两级)则按两级;否则扫其 <tier>/ 子目录。
|
||||||
|
* family 级 'default' 由索引 0(默认字体)单独处理,不进循环,避免重复。上限 FONT_PRESETS_MAX
|
||||||
|
* (1 默认 + 卡内各套,按 (family,weight,tier) 一套一 preset 计数)。无卡/无目录时仅默认。
|
||||||
*
|
*
|
||||||
* M2.58 释放语义(规格 §5.7 需求 4「扫→写 index→释放(或按需读 index)」结论):
|
* M2.58 释放语义(规格 §5.7 需求 4「扫→写 index→释放(或按需读 index)」结论):
|
||||||
* - 持久来源 = <card_root>/MeowOS/config/fonts/index(扫描后由 meow_font_write_index 落盘)。
|
* - 持久来源 = <card_root>/MeowOS/config/fonts/index(扫描后由 meow_font_write_index 落盘)。
|
||||||
* SDK 只读该文件,从不依赖本内存数组。
|
* SDK 只读该文件,从不依赖本内存数组。
|
||||||
* - font_presets* / *_buf 均为「静态、有界(FONT_PRESETS_MAX=17)、非堆」数组:无内存泄漏,
|
* - font_presets* / *_buf 均为「静态、有界(FONT_PRESETS_MAX=64)、非堆」数组:无内存泄漏,
|
||||||
* 故无运行期 free;「释放」含义 = 两个扫描点(启动 / 进设置页)重建 + 每套固定上限防膨胀。
|
* 故无运行期 free;「释放」含义 = 两个扫描点(启动 / 进设置页)重建 + 每套固定上限防膨胀。
|
||||||
* - 设置字体列表(font.file / font.class_* 下拉、family→字重子菜单)在本视图内显示时用
|
* - 设置字体列表(font.file / font.class_* 下拉、family→字重子菜单)在本视图内显示时用
|
||||||
* 该数组;离开字体列表视图(run_settings_page 退出)无需显式 free(静态数组),下次
|
* 该数组;离开字体列表视图(run_settings_page 退出)无需显式 free(静态数组),下次
|
||||||
@@ -630,27 +679,37 @@ void meow_font_scan_presets_root(const char *card_root)
|
|||||||
struct dirent *we;
|
struct dirent *we;
|
||||||
while ((we = readdir(w)) && font_presets_n < FONT_PRESETS_MAX) {
|
while ((we = readdir(w)) && font_presets_n < FONT_PRESETS_MAX) {
|
||||||
if (we->d_name[0] == '.') continue;
|
if (we->d_name[0] == '.') continue;
|
||||||
char dirpath[400], label[96], cs[8] = ""; int gc = 0; char wt[32] = "", nm[64] = "";
|
char dirpath[400], pngcheck[420];
|
||||||
snprintf(dirpath, sizeof dirpath, "%s/%s", fampath, we->d_name);
|
snprintf(dirpath, sizeof dirpath, "%s/%s", fampath, we->d_name);
|
||||||
if (meow_font_probe_weight(dirpath, de->d_name, we->d_name, label, sizeof label,
|
snprintf(pngcheck, sizeof pngcheck, "%s/font.png", dirpath);
|
||||||
cs, sizeof cs, &gc, wt, sizeof wt, nm, sizeof nm) == 0)
|
char label[96] = "", cs[8] = ""; int gc = 0; char wt[32] = "", nm[64] = "";
|
||||||
continue; /* 缺组件/损坏 → 跳过该套 */
|
if (access(pngcheck, R_OK) == 0) {
|
||||||
int i = font_presets_n;
|
/* 两级(旧结构向后兼容):<family>/<weight>/font.png,tier 读 font.json charset。 */
|
||||||
snprintf(font_preset_val_buf[i], sizeof font_preset_val_buf[i],
|
if (meow_font_probe_weight(dirpath, de->d_name, we->d_name, NULL,
|
||||||
"%s/%s/%s/font.png", MEOW_FONTS_CFG, de->d_name, we->d_name);
|
label, sizeof label, cs, sizeof cs, &gc,
|
||||||
snprintf(font_preset_label_buf[i], sizeof font_preset_label_buf[i], "%s", label);
|
wt, sizeof wt, nm, sizeof nm) == 0)
|
||||||
snprintf(font_preset_tier_buf[i], sizeof font_preset_tier_buf[i], "%s", cs[0]?cs:"common");
|
continue; /* 缺组件/损坏 → 跳过该套 */
|
||||||
snprintf(font_preset_fam_buf[i], sizeof font_preset_fam_buf[i], "%s", de->d_name);
|
font_presets_add(de->d_name, we->d_name, NULL, label, cs, gc, wt, nm);
|
||||||
snprintf(font_preset_wt_buf[i], sizeof font_preset_wt_buf[i], "%s", wt);
|
continue;
|
||||||
snprintf(font_preset_name_buf[i], sizeof font_preset_name_buf[i], "%s", nm);
|
}
|
||||||
font_presets[i] = font_preset_val_buf[i];
|
/* 三级定案:进入 <weight>/<tier>/ 逐套探测,tier=目录名(权威档位)。 */
|
||||||
font_preset_labels[i] = font_preset_label_buf[i];
|
DIR *t = opendir(dirpath);
|
||||||
font_preset_tiers[i] = font_preset_tier_buf[i];
|
if (!t) continue;
|
||||||
font_preset_fams[i] = font_preset_fam_buf[i];
|
struct dirent *te;
|
||||||
font_preset_wts[i] = font_preset_wt_buf[i];
|
while ((te = readdir(t)) && font_presets_n < FONT_PRESETS_MAX) {
|
||||||
font_preset_names[i] = font_preset_name_buf[i];
|
if (te->d_name[0] == '.') continue;
|
||||||
font_preset_glyphs[i] = gc;
|
/* tier 仅接受 common/gbk/full(FONT_SYSTEM §1);杂散子目录不进列表。 */
|
||||||
font_presets_n++;
|
if (strcmp(te->d_name, "common") && strcmp(te->d_name, "gbk") &&
|
||||||
|
strcmp(te->d_name, "full")) continue;
|
||||||
|
char tpath[420];
|
||||||
|
snprintf(tpath, sizeof tpath, "%s/%s", dirpath, te->d_name);
|
||||||
|
if (meow_font_probe_weight(tpath, de->d_name, we->d_name, te->d_name,
|
||||||
|
label, sizeof label, cs, sizeof cs, &gc,
|
||||||
|
wt, sizeof wt, nm, sizeof nm) == 0)
|
||||||
|
continue; /* 缺组件/损坏 → 跳过该 tier 套 */
|
||||||
|
font_presets_add(de->d_name, we->d_name, te->d_name, label, cs, gc, wt, nm);
|
||||||
|
}
|
||||||
|
closedir(t);
|
||||||
}
|
}
|
||||||
closedir(w);
|
closedir(w);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -115,7 +115,11 @@ extern "C" {
|
|||||||
#define UI_FONT_PNG_SYS "/usr/share/meowos/font/font.png"
|
#define UI_FONT_PNG_SYS "/usr/share/meowos/font/font.png"
|
||||||
#define UI_FONT_IDX_SYS "/usr/share/meowos/font/font.idx"
|
#define UI_FONT_IDX_SYS "/usr/share/meowos/font/font.idx"
|
||||||
#define MEOW_FONTS_CFG "MeowOS/fonts"
|
#define MEOW_FONTS_CFG "MeowOS/fonts"
|
||||||
#define FONT_PRESETS_MAX 17 /* 1 默认 + ≤16 卡内字体(防爆) */
|
/* 字体 preset 上限(FONT_SYSTEM 三级定案后按 (family,weight,tier) 一套一 preset):
|
||||||
|
* 1 默认 + 卡内各套(如 source_han_sans_cn:7 字重 common + regular/medium/bold gbk = 10 套,
|
||||||
|
* 叠加既有 6 套 + 后续多语言/多档位余量)。静态有界、非堆,全数组 BSS 约 36KB,可接受。
|
||||||
|
* 注:SDK meow_font.c MEOW_FONT_INDEX_MAX=32 读 index 时取前 32 条(sdk/ 并行作业,未同步改)。 */
|
||||||
|
#define FONT_PRESETS_MAX 64
|
||||||
#define ROW_H 52
|
#define ROW_H 52
|
||||||
#define ROW_TOP 96
|
#define ROW_TOP 96
|
||||||
#define EXIT_ROW 640
|
#define EXIT_ROW 640
|
||||||
@@ -511,6 +515,7 @@ extern int fb_fd;
|
|||||||
extern const char *font_preset_fams[FONT_PRESETS_MAX];
|
extern const char *font_preset_fams[FONT_PRESETS_MAX];
|
||||||
extern const char *font_preset_labels[FONT_PRESETS_MAX];
|
extern const char *font_preset_labels[FONT_PRESETS_MAX];
|
||||||
extern const char *font_preset_names[FONT_PRESETS_MAX];
|
extern const char *font_preset_names[FONT_PRESETS_MAX];
|
||||||
|
extern const char *font_preset_tiers[FONT_PRESETS_MAX];
|
||||||
extern const char *font_preset_wts[FONT_PRESETS_MAX];
|
extern const char *font_preset_wts[FONT_PRESETS_MAX];
|
||||||
extern const char *font_presets[FONT_PRESETS_MAX];
|
extern const char *font_presets[FONT_PRESETS_MAX];
|
||||||
extern int font_presets_n;
|
extern int font_presets_n;
|
||||||
|
|||||||
Binary file not shown.
@@ -824,10 +824,34 @@ ddone:
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* ---- 字体行「family→字重」两级子菜单(M2.6 字重结构)----
|
/* 三级 (family,weight) 下多 tier 的选档偏好分(越大越优先;设置页写 font.file/class 值用)。
|
||||||
|
* 关(font.load_full=0)→ common 优先(回退 gbk→full);开 → gbk 优先(回退 full→common)。 */
|
||||||
|
static int font_tier_pick_rank(const char *tier, int load_full)
|
||||||
|
{
|
||||||
|
int is_common = tier && !strcmp(tier, "common");
|
||||||
|
int is_gbk = tier && !strcmp(tier, "gbk");
|
||||||
|
int is_full = tier && !strcmp(tier, "full");
|
||||||
|
if (!load_full) {
|
||||||
|
if (is_common) return 3;
|
||||||
|
if (is_gbk) return 2;
|
||||||
|
if (is_full) return 1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (is_gbk) return 3;
|
||||||
|
if (is_full) return 2;
|
||||||
|
if (is_common) return 1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* ---- 字体行「family→字重」两级子菜单(M2.6 字重结构;M2.8 三级适配)----
|
||||||
* 一级:列出扫描到的去重 family(class 行首项「跟随全局」=空值);选 family 后自动进入
|
* 一级:列出扫描到的去重 family(class 行首项「跟随全局」=空值);选 family 后自动进入
|
||||||
* 二级:该 family 的可用字重(regular/bold/italic…)。选中写 out_val = 卡根相对路径
|
* 二级:该 family 的可用字重(regular/bold/italic…)。选中写 out_val = 卡根相对路径
|
||||||
* MeowOS/fonts/<family>/<weight>/font.png(或 class 行「跟随全局」=空串)。
|
* MeowOS/fonts/<family>/<weight>/font.png(两级)或 .../<family>/<weight>/<tier>/font.png(三级,
|
||||||
|
* 或 class 行「跟随全局」=空串)。
|
||||||
|
* M2.8 三级冲突:同 (family,weight) 出现多个 tier → 选择器仍为 family→weight 两级、不新增 tier
|
||||||
|
* 级菜单;每 weight 只显一行(代表 = 按「当前加载大字库」开关选档:关=common 优先、开=gbk 优先,
|
||||||
|
* 见 font_tier_pick_rank),写值 = 该代表 tier 的卡根相对路径。
|
||||||
* 复用 s_dd_modal(与 WiFi/关于 同款模态)。返回 1=已选中(out_val 已填),0=取消。 */
|
* 复用 s_dd_modal(与 WiFi/关于 同款模态)。返回 1=已选中(out_val 已填),0=取消。 */
|
||||||
static int s_font_dropdown(const srow_t *r, char *out_val, int vlen)
|
static int s_font_dropdown(const srow_t *r, char *out_val, int vlen)
|
||||||
{
|
{
|
||||||
@@ -868,21 +892,47 @@ static int s_font_dropdown(const srow_t *r, char *out_val, int vlen)
|
|||||||
if (is_cls && pick == 0) { snprintf(out_val, vlen, ""); return 1; } /* 跟随全局 */
|
if (is_cls && pick == 0) { snprintf(out_val, vlen, ""); return 1; } /* 跟随全局 */
|
||||||
const char *fam = fam_slug[pick];
|
const char *fam = fam_slug[pick];
|
||||||
|
|
||||||
/* ---- 二级:该 family 的字重列表(值=完整卡根相对路径,显示=label)---- */
|
/* ---- 二级:该 family 的字重列表(去重:同 (family,weight) 只显一行)----
|
||||||
|
* 三级结构下同 (family,weight) 出现多个 tier(common/gbk/full)→ 不新增 tier 级菜单,按
|
||||||
|
* 「当前加载大字库」开关定档:关=common 优先、开=gbk 优先(font_tier_pick_rank)。 */
|
||||||
|
int load_full = 0;
|
||||||
|
meow_settings_get_switch("font.load_full", &load_full);
|
||||||
|
char wt_pick[S_NET_MAX][32]; /* 该行代表的 weight slug */
|
||||||
|
int wt_rank[S_NET_MAX]; /* 该行当前档位偏好分(保留最高分 tier 为行代表) */
|
||||||
char wt_val[S_NET_MAX][MEOW_SET_MAX_LEN];
|
char wt_val[S_NET_MAX][MEOW_SET_MAX_LEN];
|
||||||
char wt_text[S_NET_MAX][96];
|
char wt_text[S_NET_MAX][96];
|
||||||
int wn = 0;
|
int wn = 0;
|
||||||
for (int i = 0; i < font_presets_n && wn < S_NET_MAX; i++) {
|
for (int i = 0; i < font_presets_n; i++) {
|
||||||
if (!font_preset_fams[i] || strcmp(font_preset_fams[i], fam)) continue;
|
if (!font_preset_fams[i] || strcmp(font_preset_fams[i], fam)) continue;
|
||||||
snprintf(wt_val[wn], sizeof wt_val[0], "%s", font_presets[i]);
|
const char *wt = font_preset_wts[i] ? font_preset_wts[i] : "regular";
|
||||||
snprintf(wt_text[wn], sizeof wt_text[0], "%s", font_preset_labels[i]);
|
const char *tier = font_preset_tiers[i] ? font_preset_tiers[i] : "common";
|
||||||
wn++;
|
int rank = font_tier_pick_rank(tier, load_full);
|
||||||
|
int slot = -1;
|
||||||
|
for (int k = 0; k < wn; k++) if (!strcmp(wt_pick[k], wt)) { slot = k; break; }
|
||||||
|
if (slot < 0) {
|
||||||
|
if (wn >= S_NET_MAX) break;
|
||||||
|
snprintf(wt_pick[wn], sizeof wt_pick[0], "%s", wt);
|
||||||
|
wt_rank[wn] = -1;
|
||||||
|
slot = wn; wn++;
|
||||||
|
}
|
||||||
|
if (rank > wt_rank[slot]) { /* 保留档位偏好最高的一套作为该 weight 行代表 */
|
||||||
|
wt_rank[slot] = rank;
|
||||||
|
snprintf(wt_val[slot], sizeof wt_val[0], "%s", font_presets[i]);
|
||||||
|
snprintf(wt_text[slot], sizeof wt_text[0], "%s", font_preset_labels[i]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (wn <= 0) return 0; /* 该 family 无字重(理论不出现:能进 family 必已探到字重) */
|
if (wn <= 0) return 0; /* 该 family 无字重(理论不出现:能进 family 必已探到字重) */
|
||||||
const char *witem[S_NET_MAX];
|
const char *witem[S_NET_MAX];
|
||||||
for (int i = 0; i < wn; i++) witem[i] = wt_text[i];
|
for (int i = 0; i < wn; i++) witem[i] = wt_text[i];
|
||||||
|
/* 当前位置高亮:按当前 font.file 的 (family,weight) 命中所显示 weight 行(不依赖 tier 精确匹配,
|
||||||
|
* 因三级下 font.file 具体 tier 可能与该行代表 tier 不同)。 */
|
||||||
int cur_wt = -1;
|
int cur_wt = -1;
|
||||||
for (int i = 0; i < wn; i++) if (cur[0] && !strcmp(wt_val[i], cur)) cur_wt = i;
|
{
|
||||||
|
char cfam2[64] = "", cwt2[32] = "";
|
||||||
|
if (cur[0]) font_family_weight_from_png(cur, cfam2, sizeof cfam2, cwt2, sizeof cwt2);
|
||||||
|
for (int i = 0; i < wn; i++)
|
||||||
|
if (cwt2[0] && !strcmp(wt_pick[i], cwt2)) { cur_wt = i; break; }
|
||||||
|
}
|
||||||
int wp = s_dd_modal(r->label, witem, wn, cur_wt);
|
int wp = s_dd_modal(r->label, witem, wn, cur_wt);
|
||||||
if (wp < 0) return 0;
|
if (wp < 0) return 0;
|
||||||
snprintf(out_val, vlen, "%s", wt_val[wp]);
|
snprintf(out_val, vlen, "%s", wt_val[wp]);
|
||||||
|
|||||||
@@ -0,0 +1,198 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
test_font_tier.py — MeowOS 字体三级(FONT_SYSTEM 定案)宿主导出单测。
|
||||||
|
|
||||||
|
验证 launcher 侧 font 扫描对三级结构的处理(FONT_SYSTEM.md §1/§4.5):
|
||||||
|
(1) 三级扫描:<卡根>/MeowOS/fonts/<family>/<weight>/<tier>/(tier=common/gbk/full)→ 每
|
||||||
|
(family,weight,tier) 一套,值 = MeowOS/fonts/<family>/<weight>/<tier>/font.png,tier=目录名。
|
||||||
|
(2) 两级向后兼容:<family>/<weight>/font.png → 按旧口径,tier 读 font.json charset。
|
||||||
|
(3) 同 (family,weight) 下 weight 根有 font.png → 按两级(不另扫 tier 子目录)。
|
||||||
|
(4) index 落盘:<root>/MeowOS/config/fonts/index 每套一条 6 字段
|
||||||
|
"family","weight","path","label","tier",glyph_count;三级条目含三级 path + tier 列。
|
||||||
|
(5) 缺件(漏 font.idx 等)子目录 → 该套跳过。
|
||||||
|
|
||||||
|
方法:构造假卡根(两/三级混合 + 缺件 + 非法目录),用 qemu-mipsel 跑真实
|
||||||
|
meow_launcherd --fontscan <root>,断言 stdout preset 列表与 index 文件。
|
||||||
|
|
||||||
|
用法(在 patch_v2/meow_launcherd 下):
|
||||||
|
先 build_launcher.sh 生成 meow_launcherd(mipsel 静态 ELF),再:
|
||||||
|
python3 test_font_tier.py
|
||||||
|
依赖 qemu-mipsel(qemu-user)。退出码 0=全过,非 0=断言失败。
|
||||||
|
"""
|
||||||
|
import csv
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
ROOT = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
BIN = os.path.join(ROOT, "meow_launcherd")
|
||||||
|
FIX_ROOT = tempfile.mkdtemp(prefix="mftier_", dir="/tmp")
|
||||||
|
FONTS = os.path.join(FIX_ROOT, "MeowOS", "fonts")
|
||||||
|
|
||||||
|
|
||||||
|
def mk_font(rel, family, name, weight, tier, gcount):
|
||||||
|
"""写一套 <family>/<weight>[/<tier>]/font.png+font.idx+font.json(合法三件套)。"""
|
||||||
|
d = os.path.join(FONTS, rel)
|
||||||
|
os.makedirs(d, exist_ok=True)
|
||||||
|
with open(os.path.join(d, "font.png"), "wb") as f:
|
||||||
|
f.write(b"\x89PNG\r\n\x1a\nPLACEHOLDER")
|
||||||
|
with open(os.path.join(d, "font.idx"), "wb") as f:
|
||||||
|
f.write(b"MFNI\x02\x00PLACEHOLDER")
|
||||||
|
meta = {
|
||||||
|
"family": family, "name": name, "weight": weight, "glyph_count": gcount,
|
||||||
|
"charset": tier, "tier": tier, "size_list": [20, 28], "bit_depth": 4,
|
||||||
|
"sha256_png": "0" * 64, "sha256_idx": "0" * 64,
|
||||||
|
}
|
||||||
|
with open(os.path.join(d, "font.json"), "w", encoding="utf-8") as f:
|
||||||
|
json.dump(meta, f, ensure_ascii=False, indent=2)
|
||||||
|
|
||||||
|
|
||||||
|
def run_scan():
|
||||||
|
r = subprocess.run(["qemu-mipsel", BIN, "--fontscan", FIX_ROOT],
|
||||||
|
capture_output=True, text=True, timeout=120)
|
||||||
|
return r.returncode, r.stdout
|
||||||
|
|
||||||
|
|
||||||
|
def read_index():
|
||||||
|
p = os.path.join(FIX_ROOT, "MeowOS", "config", "fonts", "index")
|
||||||
|
if not os.path.exists(p):
|
||||||
|
return []
|
||||||
|
rows = []
|
||||||
|
with open(p, encoding="utf-8") as f:
|
||||||
|
for line in f:
|
||||||
|
line = line.strip()
|
||||||
|
if not line or line.startswith("#"):
|
||||||
|
continue
|
||||||
|
rows.append(next(csv.reader([line])))
|
||||||
|
return rows
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
if not os.path.exists(BIN):
|
||||||
|
print(f"FAIL: 未找到 {BIN},请先跑 build_launcher.sh"); return 2
|
||||||
|
if shutil.which("qemu-mipsel") is None:
|
||||||
|
print("FAIL: 缺 qemu-mipsel"); return 2
|
||||||
|
|
||||||
|
# ---- 构造卡根 ----
|
||||||
|
mk_font("default/regular", "default", "字体", "regular", "common", 4110) # 两级默认
|
||||||
|
mk_font("misans/regular", "misans", "MiSans", "regular", "common", 4110) # 两级(旧结构)
|
||||||
|
# source_han_sans_cn 三级:regular/common + regular/gbk(同 weight 双 tier)
|
||||||
|
mk_font("source_han_sans_cn/regular/common", "source_han_sans_cn", "思源黑体 CN", "regular", "common", 4110)
|
||||||
|
mk_font("source_han_sans_cn/regular/gbk", "source_han_sans_cn", "思源黑体 CN", "regular", "gbk", 20000)
|
||||||
|
mk_font("source_han_sans_cn/regular/full", "source_han_sans_cn", "思源黑体 CN", "regular", "full", 30000)
|
||||||
|
mk_font("source_han_sans_cn/bold/common", "source_han_sans_cn", "思源黑体 CN", "bold", "common", 4110)
|
||||||
|
# 同 (family,weight) 下 weight 根有 font.png(两级)+ 同 weight 也带 common/ 子目录 → 应按两级处理
|
||||||
|
d = os.path.join(FONTS, "dual", "regular")
|
||||||
|
os.makedirs(d, exist_ok=True)
|
||||||
|
with open(os.path.join(d, "font.png"), "wb") as f:
|
||||||
|
f.write(b"\x89PNG\r\n\x1a\nPLACEHOLDER")
|
||||||
|
with open(os.path.join(d, "font.idx"), "wb") as f:
|
||||||
|
f.write(b"MFNI\x02\x00PLACEHOLDER")
|
||||||
|
with open(os.path.join(d, "font.json"), "w", encoding="utf-8") as f:
|
||||||
|
json.dump({"family": "dual", "name": "Dual", "weight": "regular",
|
||||||
|
"glyph_count": 1000, "charset": "common"}, f)
|
||||||
|
os.makedirs(os.path.join(d, "common"), exist_ok=True) # 应被忽略(已在两级分支)
|
||||||
|
# 缺件三级:source_han_sans_cn/medium 下无 font.idx → 跳过
|
||||||
|
broken = os.path.join(FONTS, "source_han_sans_cn", "medium", "full")
|
||||||
|
os.makedirs(broken, exist_ok=True)
|
||||||
|
with open(os.path.join(broken, "font.png"), "wb") as f:
|
||||||
|
f.write(b"PNG")
|
||||||
|
with open(os.path.join(broken, "font.json"), "w") as f:
|
||||||
|
json.dump({"family": "source_han_sans_cn", "name": "思源黑体 CN", "weight": "medium"}, f)
|
||||||
|
# 杂散三级子目录(tier 名非常规 common/gbk/full,但三件套齐全)→ 应被跳过(仅认 3 档)
|
||||||
|
stray = os.path.join(FONTS, "source_han_sans_cn", "regular", "backup")
|
||||||
|
os.makedirs(stray, exist_ok=True)
|
||||||
|
for n, b in (("font.png", b"PNG"), ("font.idx", b"IDX"), ("font.json", b'{}')):
|
||||||
|
with open(os.path.join(stray, n), "wb") as f:
|
||||||
|
f.write(b)
|
||||||
|
|
||||||
|
rc, out = run_scan()
|
||||||
|
if rc != 0:
|
||||||
|
print(f"FAIL: qemu --fontscan 返回 {rc}\n{out}"); return 1
|
||||||
|
|
||||||
|
presets = {}
|
||||||
|
famwt = {}
|
||||||
|
for line in out.splitlines():
|
||||||
|
line = line.strip()
|
||||||
|
if line.startswith("fonts="):
|
||||||
|
continue
|
||||||
|
if line and line[0].isdigit():
|
||||||
|
parts = line.split(" -> ")
|
||||||
|
if len(parts) != 2:
|
||||||
|
continue
|
||||||
|
idx = int(parts[0].split()[0])
|
||||||
|
rest, famwt_part = parts[1].split(" (fam=", 1)
|
||||||
|
path = rest.strip()
|
||||||
|
fam = famwt_part.split(" wt=")[0]
|
||||||
|
wt = famwt_part.split(" wt=")[1].rstrip(")")
|
||||||
|
presets[path] = idx
|
||||||
|
famwt[path] = (fam, wt)
|
||||||
|
|
||||||
|
fails = 0
|
||||||
|
def check(cond, msg):
|
||||||
|
nonlocal fails
|
||||||
|
if not cond:
|
||||||
|
fails += 1
|
||||||
|
print(f"FAIL: {msg}")
|
||||||
|
else:
|
||||||
|
print(f"ok : {msg}")
|
||||||
|
|
||||||
|
n = int(next((l.split("=")[1] for l in out.splitlines() if l.startswith("fonts=")), "0"))
|
||||||
|
check(n == len(presets), f"fonts={n} 与预设解析数 {len(presets)} 一致")
|
||||||
|
|
||||||
|
# 预期 preset(默认 + dual/misans 两级 + shsc 4 套三级 = 7)
|
||||||
|
check(len(presets) == 7, f"扫描产出 7 套(实际 {len(presets)})")
|
||||||
|
check("MeowOS/fonts/default/regular/font.png" in presets, "两级默认在列表")
|
||||||
|
check("MeowOS/fonts/misans/regular/font.png" in presets, "两级 misans/regular 在列表")
|
||||||
|
check("MeowOS/fonts/dual/regular/font.png" in presets, "dual/regular(weight 根 font.png)按两级处理")
|
||||||
|
p_common = "MeowOS/fonts/source_han_sans_cn/regular/common/font.png"
|
||||||
|
p_gbk = "MeowOS/fonts/source_han_sans_cn/regular/gbk/font.png"
|
||||||
|
p_full = "MeowOS/fonts/source_han_sans_cn/regular/full/font.png"
|
||||||
|
p_bold = "MeowOS/fonts/source_han_sans_cn/bold/common/font.png"
|
||||||
|
check(p_common in presets, "三级 shsc/regular/common 在列表")
|
||||||
|
check(p_gbk in presets, "三级 shsc/regular/gbk 在列表")
|
||||||
|
check(p_full in presets, "三级 shsc/regular/full 在列表")
|
||||||
|
check(p_bold in presets, "三级 shsc/bold/common 在列表")
|
||||||
|
check(famwt.get(p_gbk) == ("source_han_sans_cn", "regular"), "fam/wt 三级条目系家族+字重")
|
||||||
|
check(all("medium" not in k for k in presets), "缺 font.idx 的 shsc/medium/full 被跳过")
|
||||||
|
check(all("/regular/backup/" not in k for k in presets), "杂散 tier 子目录 backup(非常规档名)被跳过")
|
||||||
|
# 同 weight 下的 common 子目录(dual/regular/common)不应被当作三级产出(已在两级分支)
|
||||||
|
check(all("/dual/regular/common/" not in k for k in presets), "dual/regular 的 common/ 子目录未产生独立条目")
|
||||||
|
|
||||||
|
# ---- index 文件 6 字段 + 三级条目 ----
|
||||||
|
rows = read_index()
|
||||||
|
check(len(rows) == len(presets), f"index 条数 {len(rows)} == 扫描数 {len(presets)}")
|
||||||
|
def find(path):
|
||||||
|
for r in rows:
|
||||||
|
if len(r) >= 3 and r[2] == path:
|
||||||
|
return r
|
||||||
|
return None
|
||||||
|
r = find(p_gbk)
|
||||||
|
check(r is not None, "index 含三级 gbk 条目")
|
||||||
|
if r:
|
||||||
|
check(len(r) == 6, f"index 每套 6 字段(实际 {len(r)})")
|
||||||
|
check(r[0] == "source_han_sans_cn" and r[1] == "regular", "index family/weight 列正确")
|
||||||
|
check(r[4] == "gbk", f"index tier 列 = gbk(实际 {r[4]})")
|
||||||
|
check(r[5] == "20000", f"index glyph_count 列 = 20000(实际 {r[5]})")
|
||||||
|
r = find(p_common)
|
||||||
|
check(r is not None and r[4] == "common", "index 三级 common 条目 tier=common")
|
||||||
|
r = find(p_full)
|
||||||
|
check(r is not None and r[4] == "full", "index 三级 full 条目 tier=full")
|
||||||
|
r = find("MeowOS/fonts/dual/regular/font.png")
|
||||||
|
check(r is not None and r[4] == "common", "index 两级 dual 条目 tier=common(读 charset)")
|
||||||
|
r = find("MeowOS/fonts/default/regular/font.png")
|
||||||
|
check(r is not None and r[0] == "default", "index 默认行 family=default")
|
||||||
|
# 三级 label(stdout 含档位标记)
|
||||||
|
check("思源黑体 CN Regular(gbk≈20k)" in out, "label 含 `思源黑体 CN Regular(gbk≈20k)`")
|
||||||
|
check("思源黑体 CN Regular(common≈4k)" in out, "label 含 `思源黑体 CN Regular(common≈4k)`")
|
||||||
|
|
||||||
|
print(f"\n{'RESULT: PASS' if fails == 0 else 'RESULT: FAIL'} ({fails} failures)")
|
||||||
|
return 1 if fails else 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
Reference in new issue
Block a user