字重结构+family→字重子菜单(agent-111: fonts/<family>/<weight>/, gen_font.py --weight, 两级扫描, s_font_dropdown子菜单) + 全屏左滑滑条修复(agent-112)

This commit is contained in:
MeowOS Dev committed 2026-08-30 13:42:41 +08:00
1 parent e8fa373a2f
commit b75ecce5de
16 files changed
+531 -164

No files matched your search

Binary file not shown.
+3 -2
View File
@@ -109,8 +109,9 @@ int meow_font_config_multifont(int on,
/* ---- MeowPanel 预留:TF 卡字体库 .zip 导入/导出(TODO,未实现)----
* 当前仅声明占位,供 MeowPanel 后续接入;不实现、不接 zip 库。
* 调用方约定(待实现):导入=校验 .zip 内含 font.png+font.idx+font.json 三件套,
* 校验通过后解压到 TF 卡 MeowOS/fonts/<family>/(family 取自 font.json 的 family 或 zip 名,
* 冲突时覆盖需先确认);导出=把 MeowOS/fonts/<family>/ 三件套打包为 <out_zip>。
* 校验通过后解压到 TF 卡 MeowOS/fonts/<family>/<weight>/(family/weight 取自 font.json 的
* family/weight 或 zip 名,缺省 weight=regular;冲突时覆盖需先确认);导出=把
* MeowOS/fonts/<family>/<weight>/ 三件套打包为 <out_zip>。
* 目录布局与 font.json schema 见 docs/MEOWOS_SPEC_REF.md §1.2/§5.2。
* 返回码:MEOW_OK=0 成功,负值错误码(沿用本模块失败负值约定)。 */
int meow_font_import_zip(const char *zip_path); /* TODO:MeowPanel */
Binary file not shown.
+272 -93
View File
@@ -782,27 +782,37 @@ static void draw_text(int x, int y, const char *s, int scale, uint32_t color)
/* ================================================================== */
/* M2.6 meow_font 集成(agent-28 字体子系统,font/meow_font.a + libz.a)*/
/* 字库放 TF 卡 MeowOS/fonts/default/font.png+font.idx(或固件 /usr/share/ */
/* meowos/font/);MeowOS 启动 ensure_card_mounted 后再 load,卡晚 */
/* M2.6 MeowOS 字重结构:字库放 TF 卡 MeowOS/fonts/default/regular/font.png+font.idx
*(或固件 /usr/share/meowos/font/);MeowOS 启动 ensure_card_mounted 后再 load,卡晚 */
/* 挂载时轮询重试。没加载成功时 ui_draw 回退 5x7 点阵(ASCII)。 */
/* ================================================================== */
#define UI_FONT_PNG_CARD "/usr/data/mnt/sd_0/MeowOS/fonts/default/font.png"
#define UI_FONT_IDX_CARD "/usr/data/mnt/sd_0/MeowOS/fonts/default/font.idx"
#define UI_FONT_PNG_CARD "/usr/data/mnt/sd_0/MeowOS/fonts/default/regular/font.png"
#define UI_FONT_IDX_CARD "/usr/data/mnt/sd_0/MeowOS/fonts/default/regular/font.idx"
#define UI_FONT_PNG_SYS "/usr/share/meowos/font/font.png"
#define UI_FONT_IDX_SYS "/usr/share/meowos/font/font.idx"
/* M2.5x:TF 卡多字体库 —— 字体目录 = <卡根>/MeowOS/fonts/<family>/(font.png+font.idx+font.json)。
/* M2.5x 多字体库 + M2.6 字重:字体目录 = <卡根>/MeowOS/fonts/<family>/<weight>/(font.png+font.idx+font.json)。
* <卡根> 默认 /usr/data/mnt/sd_0(card_present() 判定;--fontscan 可指定宿主根以便单测)。 */
#define MEOW_FONTS_CFG "MeowOS/fonts"
#define FONT_PRESETS_MAX 17 /* 1 默认 + ≤16 卡内字体(防爆) */
/* M2.6 字重结构:卡内字体 = <卡根>/MeowOS/fonts/<family>/<weight>/(三件套)。
* 每个 preset = 一套 <family>/<weight>(如 misans/regular)。值 = 卡根相对路径
* MeowOS/fonts/<family>/<weight>/font.png;family/weight 单列供设置页「family→字重」子菜单
* 分组与 meow_font_effective_paths_tiered 按 family+weight 选库。 */
static const char *font_presets[FONT_PRESETS_MAX]; /* SC_PRESET 值 = 卡根相对路径 */
static const char *font_preset_labels[FONT_PRESETS_MAX]; /* SC_PRESET 中文标签(含档位大小标记) */
static const char *font_preset_labels[FONT_PRESETS_MAX]; /* SC_PRESET 中文标签(含字重+档位大小标记) */
/* M2.5x 字体档位:每套的 charset 档("common"/"gbk"/"full",取自 font.json,缺省 "common")
* 与 glyph_count(信息用)。供档位分层装载(meow_font_effective_paths_tiered)与设置页标注。 */
static const char *font_preset_tiers[FONT_PRESETS_MAX];
static int font_preset_glyphs[FONT_PRESETS_MAX];
static const char *font_preset_fams[FONT_PRESETS_MAX]; /* family slug(目录一级) */
static const char *font_preset_wts[FONT_PRESETS_MAX]; /* weight slug(目录二级) */
static const char *font_preset_names[FONT_PRESETS_MAX]; /* font.json name(family 显示名) */
static char font_preset_val_buf[FONT_PRESETS_MAX][320]; /* 值持久存储(指向上面指针数组) */
static char font_preset_label_buf[FONT_PRESETS_MAX][80]; /* 标签持久存储 */
static char font_preset_label_buf[FONT_PRESETS_MAX][96]; /* 标签持久存储(含字重+档位标记) */
static char font_preset_tier_buf[FONT_PRESETS_MAX][8]; /* charset 持久存储 */
static char font_preset_fam_buf[FONT_PRESETS_MAX][64]; /* family 持久存储 */
static char font_preset_wt_buf[FONT_PRESETS_MAX][32]; /* weight 持久存储 */
static char font_preset_name_buf[FONT_PRESETS_MAX][64]; /* name 持久存储 */
static int font_presets_n = 0; /* 运行时动态计数(默认=1,仅"默认字体") */
static int mfont_ready = 0;
static int mfont_forced = 0; /* font.file 变更→强制真实(un)load,跳过 SDK 复用捷径 */
@@ -855,7 +865,7 @@ static int meow_font_resolve_selected(char *out_png, int png_len, char *out_idx,
/* 尝试加载字库;成功返回 1。优先级(M2.5x,font.load_full 已接入):
* 0) 生效档 = font.load_full ? gbk : common(launcher 自身 UI 跟随系统大字库开关);
* 1) 生效字体(tier 解析,tier0 即 font.file 所选;相对路径按卡根解析);
* 2) 卡 MeowOS/fonts/default/font.png(默认字库);
* 2) 卡 MeowOS/fonts/default/regular/font.png(默认字库);
* 3) 固件 /usr/share/meowos/font/font.png。
* A-1/A-2:SDK meow_ui_init() 已用同一 meow_font.o 加载过(可能成功也可能未成功)。
* 当字体指向「默认卡字库」或未设置时复用 SDK 已加载状态,避免重复 unload+reload;
@@ -899,9 +909,42 @@ static int meow_font_ensure(void)
return 0;
}
/* 从 MeowOS/fonts/<family>/<weight>/font.png 绝对路径提 family(第一级目录名)。
* 如 .../misans/regular/font.png → "misans"、.../default/regular/font.png → "default";
* 结构异常/找不到前缀 → 回退 "default"。 */
static void font_family_from_png(const char *png, char *out, int olen)
{
const char *prefix = "/MeowOS/fonts/";
const char *p = png ? strstr(png, prefix) : NULL;
if (!p) { snprintf(out, olen, "default"); return; }
const char *q = p + strlen(prefix);
const char *slash = strchr(q, '/');
int n = slash ? (int)(slash - q) : (int)strlen(q);
if (n <= 0 || n >= olen) { snprintf(out, olen, "default"); return; }
memcpy(out, q, (size_t)n); out[n] = 0;
}
/* 同路径同时提取 family + weight(两级目录名)。weight 缺省 "regular"。 */
static void font_family_weight_from_png(const char *png, char *fam, int flen, char *wt, int wlen)
{
const char *prefix = "/MeowOS/fonts/";
const char *p = png ? strstr(png, prefix) : NULL;
if (!p) { snprintf(fam, flen, "default"); snprintf(wt, wlen, "regular"); return; }
const char *q = p + strlen(prefix);
const char *s1 = strchr(q, '/');
if (!s1) { snprintf(fam, flen, "%s", q); snprintf(wt, wlen, "regular"); return; }
int nf = (int)(s1 - q);
if (nf > 0 && nf < flen) { memcpy(fam, q, (size_t)nf); fam[nf] = 0; }
else snprintf(fam, flen, "default");
const char *s2 = strchr(s1 + 1, '/');
int nw = s2 ? (int)(s2 - (s1 + 1)) : (int)strlen(s1 + 1);
if (nw > 0 && nw < wlen) { memcpy(wt, s1 + 1, (size_t)nw); wt[nw] = 0; }
else snprintf(wt, wlen, "regular");
}
/* 求「当前生效字体」的 png/idx 绝对路径 + family,供 launch_app 注入 MEOW_FONT_PNG/IDX/APP。
* 优先级(与 meow_font_ensure 一致):font.file(resolve_selected)→ 卡默认 MeowOS/fonts/default。
* family 取自 MeowOS/fonts/<family>/font.png 目录名;无法判定则 "default"。
* family 取自 MeowOS/fonts/<family>/<weight>/font.png 的第一级目录名;无法判定则 "default"。
* 返回 0 成功(out_png/out_idx/out_family 已填),-1 无卡/无可用字体(调用方跳过注入,
* SDK 自身会回退卡默认→固件→点阵)。 */
static int meow_font_effective_paths(char *out_png, int plen, char *out_idx, int ilen,
@@ -911,15 +954,7 @@ static int meow_font_effective_paths(char *out_png, int plen, char *out_idx, int
if (meow_font_resolve_selected(fpng, sizeof fpng, fidx, sizeof fidx) == 0) {
snprintf(out_png, plen, "%s", fpng);
snprintf(out_idx, ilen, "%s", fidx);
const char *prefix = "/MeowOS/fonts/";
const char *p = strstr(fpng, prefix);
if (p) {
const char *q = p + strlen(prefix);
const char *end = strrchr(q, '/');
int rest = end ? (int)(end - q) : 0;
if (rest > 0 && rest < flen) { memcpy(out_family, q, rest); out_family[rest] = 0; }
else snprintf(out_family, flen, "default");
} else snprintf(out_family, flen, "default");
font_family_from_png(fpng, out_family, flen);
return 0;
}
/* 回退卡默认 MeowOS/fonts/default */
@@ -958,9 +993,10 @@ static int meow_font_effective_tier(int app_tier)
* - 生效档 = max(app 声明档, font.load_full ? GBK : COMMON):系统「加载大字库」开 →
* 强制 ≥gbk;
* - tier<=common → 走既有 meow_font_effective_paths(font.file/卡默认/固件);
* - tier>0 → 在已扫描的 MeowOS/fonts/<family>/ 里找 charset 匹配(gbk 请求接受 gbk/full;
* full 请求接受 full)的那套,以其 font.png/idx 绝对路径返回(family = 目录名);
* - 找不到匹配的大字库 → 回退 meow_font_effective_paths(避免 app 直接点到固件小库)。
* - tier>0 → 在已扫描的 MeowOS/fonts/<family>/<weight>/ 里找 charset 匹配(gbk 请求接受
* gbk/full;full 请求接受 full)的那套,以其 font.png/idx 绝对路径返回(family = 一级目录名);
* - M2.6 字重:优先选「与用户当前 font.file 同 family(其次同 weight)」的大字库,避免
* 大字库跳到另一家字体;找不到匹配 → 回退 meow_font_effective_paths(避免点到固件小库)。
* 返回 0 成功(out_png/out_idx/out_family 已填),-1 无可用。 */
static int meow_font_effective_paths_tiered(int tier, char *out_png, int plen,
char *out_idx, int ilen, char *out_family, int flen)
@@ -969,20 +1005,32 @@ static int meow_font_effective_paths_tiered(int tier, char *out_png, int plen,
if (eff <= MEOW_FONT_TIER_COMMON)
return meow_font_effective_paths(out_png, plen, out_idx, ilen, out_family, flen);
/* 大字库:从扫描结果选 charset 匹配的一套。
* 打分(高优先):gbk 请求 → gbk=2 > full=1;full 请求 → full=2 > gbk=1(降级可接受)。 */
* 打分(档位主导 ×100):gbk 请求 → gbk=200 > full=100;full 请求 → full=200 > gbk=100;
* + 同 family +30(其次同 weight +10,M2.6)仅作同档位内 tie-break:大字库沿用用户所选
* family,但绝不因「同 family」而把 common 小库越档选成大字库。 */
const char *want = font_tier_charset(eff);
char curfam[64] = "", curwt[32] = "";
{ /* 用户当前 font.file 所选 family/weight(无法判定 → 空,不参与加成) */
char fpng[400], fidx[400];
if (meow_font_resolve_selected(fpng, sizeof fpng, fidx, sizeof fidx) == 0)
font_family_weight_from_png(fpng, curfam, sizeof curfam, curwt, sizeof curwt);
}
char ref[320];
int found_family = -1, best_score = 0;
int found = -1, best_score = 0;
for (int i = 0; i < font_presets_n; i++) {
const char *cs = font_preset_tiers[i] ? font_preset_tiers[i] : "common";
/* 档位优先(×100 主导):gbk 请求 → gbk=200 > full=100;full 请求 → full=200 > gbk=100。
* M2.6:同 family +30、同 weight +10 仅作同档位内 tie-break(不越档选小库)。 */
int score = 0;
if (!strcmp(cs, want)) score = 2;
else if (!strcmp(cs, "full") || !strcmp(cs, "gbk")) score = 1; /* 降级 */
if (score > best_score) { best_score = score; found_family = i; }
if (!strcmp(cs, want)) score = 200;
else if (!strcmp(cs, "full") || !strcmp(cs, "gbk")) score = 100; /* 降级 */
if (curfam[0] && font_preset_fams[i] && !strcmp(font_preset_fams[i], curfam)) score += 30;
if (curfam[0] && curwt[0] && font_preset_wts[i] && !strcmp(font_preset_wts[i], curwt)) score += 10;
if (score > best_score) { best_score = score; found = i; }
}
if (found_family >= 0)
snprintf(ref, sizeof ref, "%s", font_presets[found_family]);
if (found_family >= 0) {
if (found >= 0)
snprintf(ref, sizeof ref, "%s", font_presets[found]);
if (found >= 0) {
/* 相对路径按卡根解析为设备绝对路径(与 meow_font_effective_paths 同源)。 */
if (meow_font_resolve_path(ref, out_png, plen) != 0) return -1;
if (access(out_png, R_OK) != 0) goto fallback;
@@ -992,15 +1040,7 @@ static int meow_font_effective_paths_tiered(int tier, char *out_png, int plen,
out_idx[n-4] = '.'; out_idx[n-3] = 'i'; out_idx[n-2] = 'd';
out_idx[n-1] = 'x'; out_idx[n] = 0;
}
const char *prefix = "/MeowOS/fonts/";
const char *p = strstr(out_png, prefix);
if (p) {
const char *q = p + strlen(prefix);
const char *end = strrchr(q, '/');
int rest = end ? (int)(end - q) : 0;
if (rest > 0 && rest < flen) { memcpy(out_family, q, rest); out_family[rest] = 0; }
else snprintf(out_family, flen, "default");
} else snprintf(out_family, flen, "default");
font_family_from_png(out_png, out_family, flen);
return 0;
}
fallback:
@@ -1680,15 +1720,17 @@ static int ensure_card_mounted(void)
}
/* ================================================================== */
/* M2.5x:TF 卡多字体库扫描 + preset 动态化 */
/* 字体目录 = <卡根>/MeowOS/fonts/<family>/,每套含三件套: */
/* M2.5x 多字体库扫描 + M2.6 字重结构 / preset 动态化 */
/* 字体目录 = <卡根>/MeowOS/fonts/<family>/<weight>/,每套含三件套: */
/* font.png(预渲染灰度 PNG,bit depth 4|8)+ font.idx(MFNI v2 索引) */
/* + font.json(元数据 + 完整性校验)。缺任一件 → 该套不进列表 + 日志。 */
/* font.json schema(定版): */
/* { */
/* "family": "家族标识(同目录名)", // 信息用,不参与校验 */
/* "family": "家族标识(同目录名/family)", // 信息用,不参与校验 */
/* "name": "下拉显示名", // 缺则回退目录名 */
/* "weight": "regular", // 字重 slug(缺省 regular) */
/* "glyph_count": 10000, // 信息用 */
/* "charset": "common|gbk|full", // 档位(M2.5x,驱动按需加载) */
/* "size_list": [28,20], // 信息用 */
/* "bit_depth": 4, // 信息用(4|8) */
/* "sha256_png": "<64hex>", // font.png 校验 */
@@ -1746,14 +1788,17 @@ static int font_sha_eq(const char *a, const char *b)
return 1;
}
/* 解析 font.json:提取 name / sha256_png / sha256_idx(文件缺失或解析不出则留空)。 */
/* 解析 font.json:提取 name / sha256_png / sha256_idx / weight(M2.6,缺省 "regular")。
* 文件缺失或解析不出则留空(name 空 → 调用方回退目录名;weight 空 → 回退 "regular")。 */
static void meow_font_json_probe(const char *json_path, char *name, int nlen,
char *sha_png, int slen, char *sha_idx, int xlen,
char *charset, int clen, int *glyph_count)
char *charset, int clen, int *glyph_count,
char *weight, int wlen)
{
name[0] = 0; sha_png[0] = 0; sha_idx[0] = 0;
if (charset) charset[0] = 0;
if (glyph_count) *glyph_count = 0;
if (weight) weight[0] = 0;
FILE *f = fopen(json_path, "r");
if (!f) return;
char buf[256];
@@ -1772,10 +1817,31 @@ static void meow_font_json_probe(const char *json_path, char *name, int nlen,
if (json_str(buf, "glyph_count", v, sizeof v) > 0 && v[0])
*glyph_count = atoi(v);
}
/* M2.6 字重:weight(regular|bold|italic|…),缺省 "regular"。 */
if (weight && !weight[0]) {
char v[32] = "";
if (json_str(buf, "weight", v, sizeof v) > 0 && v[0])
snprintf(weight, wlen, "%s", v);
}
}
fclose(f);
}
/* 字重 slug → 显示名(首字母大写、连字符/下划线转空格:regular→Regular、bold→Bold、italic→Italic)。 */
static void font_weight_display(const char *weight, char *out, int olen)
{
const char *w = (weight && weight[0]) ? weight : "regular";
out[0] = 0;
int o = 0, cap = 1;
for (const char *p = w; *p && o < olen - 1; p++) {
char c = *p;
if (c == '_' || c == '-') { if (o < olen - 1) out[o++] = ' '; cap = 1; continue; }
out[o++] = cap ? (char)((c >= 'a' && c <= 'z') ? c - 32 : c) : c;
cap = 0;
}
out[o] = 0;
}
/* 档位大小标记(设置页字体选项标注):common≈4k / gbk≈20k / full。
* 由 font.json 的 charset + glyph_count 派生;缺 charset 回退 common。 */
static void font_tier_marker(const char *charset, int glyph_count, char *out, int olen)
@@ -1788,62 +1854,78 @@ static void font_tier_marker(const char *charset, int glyph_count, char *out, in
snprintf(out, olen, "%s", tier);
}
/* 探测一个 <dirpath>/<family> 套件是否合法:三件套齐全 → 解析 label + 档位 + 校验 sha。
/* 探测一个 <卡根>/MeowOS/fonts/<family>/<weight> 套件是否合法:三件套齐全 → 解析 label+档位+字重。
* 返回 1=进列表,0=跳过(缺组件/json 不可读)。sha 失败仅 WARN。
* 输出:label=显示名+大小标记 `名字(common≈4k)`;charset/glyph_count=档位信息。 */
static int meow_font_probe_family(const char *dirpath, const char *family,
* 输出:label=显示名 `<name> <Weight>(common≈4k)`(如 `MiSans Regular(common≈4k)`);
* charset/glyph_count=档位信息;wtout=weight slug(缺省 "regular")。 */
static int meow_font_probe_weight(const char *dirpath, const char *family, const char *weight,
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 ppath[400], ipath[400], jpath[400];
snprintf(ppath, sizeof ppath, "%s/font.png", dirpath);
snprintf(ipath, sizeof ipath, "%s/font.idx", dirpath);
snprintf(jpath, sizeof jpath, "%s/font.json", dirpath);
if (access(ppath, R_OK) != 0 || access(ipath, R_OK) != 0 || access(jpath, R_OK) != 0) {
logmsg("fonts: skip '%s' (缺 font.png/font.idx/font.json)", family);
logmsg("fonts: skip '%s/%s' (缺 font.png/font.idx/font.json)", family, weight);
return 0;
}
char name[64] = "", jsha_png[80] = "", jsha_idx[80] = "";
char cs[8] = ""; int gc = 0;
char cs[8] = ""; int gc = 0; char wts[32] = "";
meow_font_json_probe(jpath, name, sizeof name, jsha_png, sizeof jsha_png,
jsha_idx, sizeof jsha_idx, cs, sizeof cs, &gc);
/* 显示名 = 名 + 档位大小标记(如 `default(common≈4k)` / `gbk≈20k` / `full`) */
jsha_idx, sizeof jsha_idx, cs, sizeof cs, &gc, wts, sizeof wts);
const char *wt = wts[0] ? wts : (weight ? weight : "regular");
if (wtout) snprintf(wtout, wtlen, "%s", wt);
if (nameout) snprintf(nameout, namelen, "%s", name[0] ? name : family);
/* 显示名 = `<name> <Weight>(common≈4k)`(字重缺省 Regular) */
char wd[24]; font_weight_display(wt, wd, sizeof wd);
char marker[24];
font_tier_marker(cs, gc, marker, sizeof marker);
snprintf(label, llen, "%s(%s)", name[0] ? name : family, marker);
snprintf(label, llen, "%s %s(%s)", name[0] ? name : family, wd, marker);
if (charset) snprintf(charset, clen, "%s", cs[0] ? cs : "common");
if (glyph_count) *glyph_count = gc;
/* 完整性校验:仅 WARN,不硬失败;该套仍标记可选。 */
char apng[80] = "", aidx[80] = "";
if (font_sha256_file(ppath, apng, sizeof apng) != 0)
logmsg("fonts: '%s' sha256(png) 不可用,跳过校验", family);
logmsg("fonts: '%s/%s' sha256(png) 不可用,跳过校验", family, wt);
else if (jsha_png[0] && !font_sha_eq(apng, jsha_png))
logmsg("fonts: '%s' WARN sha256_png 不匹配(仍可选)", family);
logmsg("fonts: '%s/%s' WARN sha256_png 不匹配(仍可选)", family, wt);
if (font_sha256_file(ipath, aidx, sizeof aidx) != 0)
logmsg("fonts: '%s' sha256(idx) 不可用,跳过校验", family);
logmsg("fonts: '%s/%s' sha256(idx) 不可用,跳过校验", family, wt);
else if (jsha_idx[0] && !font_sha_eq(aidx, jsha_idx))
logmsg("fonts: '%s' WARN sha256_idx 不匹配(仍可选)", family);
logmsg("fonts: add '%s' (%s) charset=%s glyph_count=%d", label, family, cs[0]?cs:"common", gc);
logmsg("fonts: '%s/%s' WARN sha256_idx 不匹配(仍可选)", family, wt);
logmsg("fonts: add '%s/%s' (%s) charset=%s glyph_count=%d", family, wt, label,
cs[0]?cs:"common", gc);
return 1;
}
/* 扫描 <card_root>/MeowOS/fonts/ 下每个家族子目录,重建字体 preset 列表(默认 + 卡内合法套件)。
* 上限 FONT_PRESETS_MAX(1 默认 + ≤16 用户字体)。无卡/无目录时仅默认。 */
/* 扫描 <card_root>/MeowOS/fonts/ 下两级 <family>/<weight>/,重建字体 preset 列表。
* 每个 (family,weight) 组合一套(如 misans/regular、misans/bold);family 级 'default' 由
* 索引 0(默认字体)单独处理,不进循环,避免重复。上限 FONT_PRESETS_MAX
* (1 默认 + ≤16 用户字体)。无卡/无目录时仅默认。 */
static void meow_font_scan_presets_root(const char *card_root)
{
font_presets_n = 0;
font_presets[0] = "MeowOS/fonts/default/font.png"; /* 默认字库 = MeowOS/fonts/default */
font_preset_labels[0] = font_preset_label_buf[0]; /* 指向缓冲(运行时派生含大小标记) */
/* 默认套也读 font.json 档位(存在则跟真实档,缺则 common);档位信息供分层装载/标注。 */
font_presets[0] = "MeowOS/fonts/default/regular/font.png"; /* 默认字库 = MeowOS/fonts/default/regular */
font_preset_labels[0] = font_preset_label_buf[0]; /* 指向缓冲(运行时派生含字重+大小标记) */
font_preset_fams[0] = font_preset_fam_buf[0];
font_preset_wts[0] = font_preset_wt_buf[0];
font_preset_names[0] = font_preset_name_buf[0];
/* 默认套也读 font.json 档位+字重(存在则跟真实档,缺则 common/regular);供分层装载/标注。 */
{
char jp[400], cs[8] = ""; int gc = 0;
char nm0[64] = "", sp0[80] = "", si0[80] = "";
snprintf(jp, sizeof jp, "%s/%s/default/font.json", card_root, MEOW_FONTS_CFG);
char nm0[64] = "", sp0[80] = "", si0[80] = "", wt0[32] = "";
snprintf(jp, sizeof jp, "%s/%s/default/regular/font.json", card_root, MEOW_FONTS_CFG);
meow_font_json_probe(jp, nm0, sizeof nm0, sp0, sizeof sp0, si0, sizeof si0,
cs, sizeof cs, &gc);
cs, sizeof cs, &gc, wt0, sizeof wt0);
char mk[24]; font_tier_marker(cs, gc, mk, sizeof mk);
snprintf(font_preset_label_buf[0], sizeof font_preset_label_buf[0], "默认字体(%s)", mk);
char wd[24]; font_weight_display(wt0[0] ? wt0 : "regular", wd, sizeof wd);
snprintf(font_preset_label_buf[0], sizeof font_preset_label_buf[0], "默认字体 %s(%s)", wd, mk);
snprintf(font_preset_tier_buf[0], sizeof font_preset_tier_buf[0], "%s", cs[0]?cs:"common");
snprintf(font_preset_fam_buf[0], sizeof font_preset_fam_buf[0], "default");
snprintf(font_preset_wt_buf[0], sizeof font_preset_wt_buf[0], "%s", wt0[0]?wt0:"regular");
snprintf(font_preset_name_buf[0], sizeof font_preset_name_buf[0], "默认字体");
font_preset_tiers[0] = font_preset_tier_buf[0];
font_preset_glyphs[0] = gc;
}
@@ -1855,21 +1937,37 @@ static void meow_font_scan_presets_root(const char *card_root)
struct dirent *de;
while ((de = readdir(d)) && font_presets_n < FONT_PRESETS_MAX) {
if (de->d_name[0] == '.') continue;
char dirpath[400], label[80], cs[8] = ""; int gc = 0;
snprintf(dirpath, sizeof dirpath, "%s/%s", root, de->d_name);
if (meow_font_probe_family(dirpath, de->d_name, label, sizeof label,
cs, sizeof cs, &gc) == 0)
continue; /* 缺组件/损坏 → 跳过该套 */
int i = font_presets_n;
snprintf(font_preset_val_buf[i], sizeof font_preset_val_buf[i],
"%s/%s/font.png", MEOW_FONTS_CFG, de->d_name);
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[0]?cs:"common");
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_glyphs[i] = gc;
font_presets_n++;
if (!strcmp(de->d_name, "default")) continue; /* 默认字体由索引 0 单独处理 */
char fampath[400];
snprintf(fampath, sizeof fampath, "%s/%s", root, de->d_name);
DIR *w = opendir(fampath); /* 一级:<family>/ */
if (!w) continue;
struct dirent *we;
while ((we = readdir(w)) && font_presets_n < FONT_PRESETS_MAX) {
if (we->d_name[0] == '.') continue;
char dirpath[400], label[96], cs[8] = ""; int gc = 0; char wt[32] = "", nm[64] = "";
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,
cs, sizeof cs, &gc, wt, sizeof wt, nm, sizeof nm) == 0)
continue; /* 缺组件/损坏 → 跳过该套 */
int i = font_presets_n;
snprintf(font_preset_val_buf[i], sizeof font_preset_val_buf[i],
"%s/%s/%s/font.png", MEOW_FONTS_CFG, de->d_name, we->d_name);
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[0]?cs:"common");
snprintf(font_preset_fam_buf[i], sizeof font_preset_fam_buf[i], "%s", de->d_name);
snprintf(font_preset_wt_buf[i], sizeof font_preset_wt_buf[i], "%s", wt);
snprintf(font_preset_name_buf[i], sizeof font_preset_name_buf[i], "%s", nm);
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++;
}
closedir(w);
}
closedir(d);
}
@@ -4630,7 +4728,7 @@ static const srow_t SMENU[] = {
* 开 → 强制全系统用大字库 gbk≈20k 档(覆盖 app 未声明/覆盖默认 font.file);
* 关(默认)→ 小字库 common≈4k,仅 app 声明 fonts:"full" 才大字库。 */
{ "加载大字库", "font.load_full", SC_SWITCH, 0,0,0, NULL,NULL,0 },
/* M2.5x 多字体:开关 + 6 个字符类别字体(preset=可用 MeowOS/fonts/<family>/ 列表;
/* M2.5x 多字体:开关 + 6 个字符类别字体(preset=可用 MeowOS/fonts/<family>/<weight>/ 列表;
* 值空 = 跟随全局 font.file)。仅开关开启时 6 行可见(s_row_visible)。 */
{ "多字体", "display.multi_font", SC_SWITCH, 0,0,0, NULL,NULL,0 },
{ "中文(CJK)", "font.class_cjk", SC_PRESET, 0,0,0, font_presets, font_preset_labels, 0 },
@@ -4733,8 +4831,14 @@ static int s_next_setting(int i)
return s_first_setting(); /* 回绕 */
}
/* 字体行(font.file + font.class_*)走「family→字重」两级子菜单(M2.6 字重结构) */
static int s_row_is_font(const srow_t *r)
{
return r && r->key && (!strcmp(r->key, "font.file") || !strncmp(r->key, "font.class_", 11));
}
/* SC_PRESET 行取「选项表 + 数量」:字体行(font.file + font.class_*)用运行时动态数组
* (TF 卡 MeowOS/fonts/<family>/ 扫描结果,见字体区 font_presets/font_presets_n),
* (TF 卡 MeowOS/fonts/<family>/<weight>/ 扫描结果,见字体区 font_presets/font_presets_n),
* 其余行用编译期静态表(r->presets / r->preset_n)。 */
static const char *const *s_row_presets(const srow_t *r, int *n)
{
@@ -6036,6 +6140,71 @@ ddone:
return result;
}
/* ---- 字体行「family→字重」两级子菜单(M2.6 字重结构)----
* 一级:列出扫描到的去重 family(class 行首项「跟随全局」=空值);选 family 后自动进入
* 二级:该 family 的可用字重(regular/bold/italic…)。选中写 out_val = 卡根相对路径
* MeowOS/fonts/<family>/<weight>/font.png(或 class 行「跟随全局」=空串)。
* 复用 s_dd_modal(与 WiFi/关于 同款模态)。返回 1=已选中(out_val 已填),0=取消。 */
static int s_font_dropdown(const srow_t *r, char *out_val, int vlen)
{
if (!r) return 0;
int is_cls = r->key && !strncmp(r->key, "font.class_", 11);
char cur[MEOW_SET_MAX_LEN];
meow_settings_get_str(r->key, cur, sizeof cur);
char curfam[64] = "", curwt[32] = "";
if (cur[0])
font_family_weight_from_png(cur, curfam, sizeof curfam, curwt, sizeof curwt);
/* ---- 一级:去重 family 列表(class 行首项「跟随全局」)---- */
char fam_slug[S_NET_MAX][64], fam_disp[S_NET_MAX][64];
int famn = 0;
if (is_cls) {
if (famn < S_NET_MAX) { snprintf(fam_slug[famn], sizeof fam_slug[0], "");
snprintf(fam_disp[famn], sizeof fam_disp[0], "跟随全局字体"); famn++; }
}
for (int i = 0; i < font_presets_n && famn < S_NET_MAX; i++) {
const char *f = font_preset_fams[i] ? font_preset_fams[i] : "";
int dup = 0;
for (int k = 0; k < famn; k++) if (!strcmp(fam_slug[k], f)) { dup = 1; break; }
if (dup) continue;
snprintf(fam_slug[famn], sizeof fam_slug[0], "%s", f);
snprintf(fam_disp[famn], sizeof fam_disp[0], "%s",
(font_preset_names[i] && font_preset_names[i][0]) ? font_preset_names[i] : f);
famn++;
}
if (famn <= 0) return 0;
const char *fitem[S_NET_MAX];
char ftext[S_NET_MAX][64];
for (int i = 0; i < famn; i++) { snprintf(ftext[i], sizeof ftext[i], "%s", fam_disp[i]); fitem[i] = ftext[i]; }
int cur_fam = is_cls ? 0 : -1;
for (int i = is_cls ? 1 : 0; i < famn; i++)
if (curfam[0] && !strcmp(fam_slug[i], curfam)) cur_fam = i;
int pick = s_dd_modal(r->label, fitem, famn, cur_fam);
if (pick < 0) return 0;
if (is_cls && pick == 0) { snprintf(out_val, vlen, ""); return 1; } /* 跟随全局 */
const char *fam = fam_slug[pick];
/* ---- 二级:该 family 的字重列表(值=完整卡根相对路径,显示=label)---- */
char wt_val[S_NET_MAX][MEOW_SET_MAX_LEN];
char wt_text[S_NET_MAX][96];
int wn = 0;
for (int i = 0; i < font_presets_n && wn < S_NET_MAX; i++) {
if (!font_preset_fams[i] || strcmp(font_preset_fams[i], fam)) continue;
snprintf(wt_val[wn], sizeof wt_val[0], "%s", font_presets[i]);
snprintf(wt_text[wn], sizeof wt_text[0], "%s", font_preset_labels[i]);
wn++;
}
if (wn <= 0) return 0; /* 该 family 无字重(理论不出现:能进 family 必已探到字重) */
const char *witem[S_NET_MAX];
for (int i = 0; i < wn; i++) witem[i] = wt_text[i];
int cur_wt = -1;
for (int i = 0; i < wn; i++) if (cur[0] && !strcmp(wt_val[i], cur)) cur_wt = i;
int wp = s_dd_modal(r->label, witem, wn, cur_wt);
if (wp < 0) return 0;
snprintf(out_val, vlen, "%s", wt_val[wp]);
return 1;
}
/* ---- 运行设置页(独立子循环;run_menu 触摸命中设置行后调用)---- */
static void run_settings_page(void)
{
@@ -6043,7 +6212,7 @@ static void run_settings_page(void)
int scroll_off = 0;
int H = fb_yres;
/* M2.5x:进设置页扫描一次 TF 卡 MeowOS/fonts/<family>/,刷新「字体文件」下拉。
/* M2.6:进设置页扫描一次 TF 卡 MeowOS/fonts/<family>/<weight>/,刷新「字体文件」下拉。
* 进设置时卡通常在位;此步只枚举/校验,不加载(加载由 meow_font_ensure 按 font.file 决定)。 */
meow_font_scan_presets();
@@ -6369,11 +6538,20 @@ static void run_settings_page(void)
} else if (r->ctrl == SC_ENUM || r->ctrl == SC_PRESET) {
/* 单次点击即开选择器子对话框 */
after_modal = 1; /* 关闭后整帧重绘(还原被 dim 的状态栏) */
int pick = s_dropdown(r);
if (pick >= 0) {
if (r->ctrl == SC_ENUM) meow_settings_set_enum(r->key, pick);
else meow_settings_set_str(r->key, s_preset_value(r, pick));
s_commit(r, 1);
if (s_row_is_font(r)) {
/* M2.6 字重:字体行走「family→字重」两级子菜单 */
char fval[MEOW_SET_MAX_LEN];
if (s_font_dropdown(r, fval, sizeof fval)) {
meow_settings_set_str(r->key, fval);
s_commit(r, 1);
}
} else {
int pick = s_dropdown(r);
if (pick >= 0) {
if (r->ctrl == SC_ENUM) meow_settings_set_enum(r->key, pick);
else meow_settings_set_str(r->key, s_preset_value(r, pick));
s_commit(r, 1);
}
}
/* M2.38 Bug3:s_dropdown 返回后彻底排空本页 tfd +
* 重置触摸基线。根因:input 子系统把事件广播到所有
@@ -7429,17 +7607,18 @@ int main(int argc, char **argv)
* menu open so edits apply without a daemon restart) */
settings_load(); /* M2.11:读系统设置(audio.volume 等),供音量/状态栏使用 */
/* M2.5x:启动扫描一次 TF 卡 MeowOS/fonts/<family>/,填充「字体文件」下拉 preset。
/* M2.6:启动扫描一次 TF 卡 MeowOS/fonts/<family>/<weight>/,填充「字体文件」下拉 preset。
* 无卡/无字库目录 → 仅默认字体(等价现状)。进设置页时 run_settings_page 会再扫一次。 */
meow_font_scan_presets();
if (fontscan_root) {
/* M2.5x 测试钩子:扫描指定卡根下的 MeowOS/fonts/<family>/ 并打印 preset(qemu/宿主可测)。
* 用于验证 font.json 解析/三件套/缺件跳过/sha 校验 + preset 动态化逻辑。 */
/* M2.6 测试钩子:扫描指定卡根下的 MeowOS/fonts/<family>/<weight>/ 并打印 preset(qemu/宿主可测)。
* 用于验证 font.json 解析(含 weight)/三件套/缺件跳过/sha 校验 + preset 动态化逻辑。 */
meow_font_scan_presets_root(fontscan_root);
printf("fonts=%d\n", font_presets_n);
for (int i = 0; i < font_presets_n; i++)
printf(" %d %s -> %s\n", i, font_preset_labels[i], font_presets[i]);
printf(" %d %s -> %s (fam=%s wt=%s)\n", i, font_preset_labels[i], font_presets[i],
font_preset_fams[i], font_preset_wts[i] ? font_preset_wts[i] : "regular");
return 0;
}
+1 -1
View File
@@ -95,7 +95,7 @@ static const meow_setting_def g_defs[] = {
/* M2.38:设置页滑动方向切换(0=标准:手指上滑→列表往下翻;1=反向) */
{ "input.swipe_invert", SET_BOOL, "0", 0, 0, NULL, NULL },
/* 字体/主题 */
{ "font.file", SET_STR, "MeowOS/fonts/default/font.png", 0,0, NULL, NULL },
{ "font.file", SET_STR, "MeowOS/fonts/default/regular/font.png", 0,0, NULL, NULL },
/* M2.5x 多字体:6 个字符类别各一字体(SET_STR,值=字体 family 相对路径,缺省空=跟随全局 font.file)。
* 仅 display.multi_font=1 时生效;同一 family 去重;上限 4 套不同字体(超出 WARN)。 */
{ "font.class_cjk", SET_STR, "", 0,0, NULL, NULL },
+155
View File
@@ -0,0 +1,155 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
test_font_weight.py — MeowOS 字重结构(M2.6)宿主导出单测。
验证 launcher 侧 font 扫描/解析的两块(MEOWOS_SPEC_REF §5.2 字重结构 + §5.7.1 扫描):
(1) 两级扫描:<卡根>/MeowOS/fonts/<family>/<weight>/ 每 (family,weight) 一套,生成
preset(值 = MeowOS/fonts/<family>/<weight>/font.png,fam/wt 单列)。
(2) font.json weight 解析:label = `<name> <Weight>(common≈4k)`(如 `MiSans Regular(common≈4k)`);
缺 weight 回退 regular;缺三件套/损坏 → 跳过;旧式一级布局(font.png 直接在 <family>/)
→ 不产出(迁移后需重扫)。
方法:构造一个假卡根(含合法多 weight family / 缺件 family / 旧式文件 / 顶层 stray),
用 qemu-mipsel 跑真实 meow_launcherd --fontscan <root>,断言 stdout 的 preset 列表。
用法(在 patch_v2/meow_launcherd 下):
先 build_launcher.sh 生成 meow_launcherd(mipsel 静态 ELF),再:
python3 test_font_weight.py
依赖 qemu-mipsel(qemu-user)。退出码 0=全过,非 0=断言失败。
"""
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="mfscan_", dir="/tmp")
FONTS = os.path.join(FIX_ROOT, "MeowOS", "fonts")
def write_font(fam, wt, name, weight="regular", charset="common"):
"""写一套 <fam>/<wt>/font.png+font.idx+font.json(合法三件套;png/idx 为占位字节)。"""
d = os.path.join(FONTS, fam, wt)
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": fam, "name": name, "weight": weight, "glyph_count": 4110,
"charset": charset, "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():
"""返回 (ret, stdout)。"""
r = subprocess.run(["qemu-mipsel", BIN, "--fontscan", FIX_ROOT],
capture_output=True, text=True, timeout=120)
return r.returncode, r.stdout
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
# ---- 构造卡根 ----
write_font("misans", "regular", "MiSans", weight="regular")
write_font("misans", "bold", "MiSans", weight="bold")
write_font("meslo", "regular", "Meslo LG S Mono", weight="regular")
write_font("default", "regular", "font", weight="regular")
# 缺件:无 font.idx → 应跳过
broken = os.path.join(FONTS, "broken", "regular")
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": "broken", "name": "Broken", "weight": "regular"}, f)
# 旧式一级布局:font.png 直接在 <family>/ 下(无 weight 级)→ 迁移后应不产出
old = os.path.join(FONTS, "legacy")
os.makedirs(old, exist_ok=True)
with open(os.path.join(old, "font.png"), "wb") as f:
f.write(b"PNG")
# 顶层 stray 文件(非目录)→ 应忽略
with open(os.path.join(FONTS, "stray.png"), "wb") as f:
f.write(b"PNG")
rc, out = run_scan()
if rc != 0:
print(f"FAIL: qemu --fontscan 返回 {rc}\n{out}"); return 1
if os.path.exists(os.path.join(os.path.dirname(FIX_ROOT), "meow_boot.log")):
pass # launcher 日志在 /tmp,忽略
# ---- stdout -> dict ----
presets = {}
famwt = {}
for line in out.splitlines():
line = line.strip()
if line.startswith("fonts="):
continue
if line and line[0].isdigit():
# " 0 Label -> path (fam=.. wt=..)" 但行首 i 数字后无空格分隔可靠断言,用 split
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)
elif line.startswith("fonts="):
n = int(line.split("=")[1])
if n != len(presets):
print(f"FAIL: fonts={n} 但解析到 {len(presets)} 条\n{out}"); return 1
fails = 0
def check(cond, msg):
nonlocal fails
if not cond:
fails += 1
print(f"FAIL: {msg}")
else:
print(f"ok : {msg}")
p1 = "MeowOS/fonts/misans/regular/font.png"
p2 = "MeowOS/fonts/misans/bold/font.png"
p3 = "MeowOS/fonts/meslo/regular/font.png"
p0 = "MeowOS/fonts/default/regular/font.png"
# 两级扫描产出(默认 + 3 套,共 4)
check(len(presets) == 4, f"扫描产出 4 套(实际 {len(presets)})")
check(p0 in presets and presets[p0] == 0, "索引 0 = default/regular")
check(p1 in presets, "misans/regular 进列表")
check(p2 in presets, "misans/bold 进列表")
check(p3 in presets, "meslo/regular 进列表")
check(famwt.get(p1) == ("misans", "regular"), "fam/wt(misans/regular) 系家族+字重")
check(famwt.get(p2) == ("misans", "bold"), "fam/wt(misans/bold) 系家族+字重")
# 缺件 / 旧式一级 / 顶层文件 → 不产出
check(all("broken" not in k for k in presets), "缺 font.idx 的 broken/regular 被跳过")
check(all("legacy" not in k for k in presets), "旧式一级布局 legacy(无 weight 级)被忽略(迁移后需重扫)")
check(all("stray" not in k for k in presets), "顶层 stray.png(非目录)被忽略")
# weight 解析 → 显示名 `Name Weight(common≈4k)`
labels = {os.path.basename(os.path.dirname(os.path.dirname(p))):
p for p in presets}
# 用 stdout 里的 Label 校验(weight 大写 + 档位标记)
out_has = "MiSans Regular(common≈4k)" in out
out_has_bold = "MiSans Bold(common≈4k)" in out
check(out_has, "label 含 `MiSans Regular(common≈4k)`(weight 解析+大小标记)")
check(out_has_bold, "label 含 `MiSans Bold(common≈4k)`(bold 字重解析)")
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())
+17 -16
View File
@@ -29,12 +29,12 @@
#define FBIO_WAITFORVSYNC 0x4620
#define FB_DEV "/dev/fb0"
#define MEOW_FONT_PNG_CARD "/usr/data/mnt/sd_0/MeowOS/fonts/default/font.png"
#define MEOW_FONT_IDX_CARD "/usr/data/mnt/sd_0/MeowOS/fonts/default/font.idx"
#define MEOW_FONT_PNG_CARD "/usr/data/mnt/sd_0/MeowOS/fonts/default/regular/font.png"
#define MEOW_FONT_IDX_CARD "/usr/data/mnt/sd_0/MeowOS/fonts/default/regular/font.idx"
#define MEOW_FONT_PNG_SYS "/usr/share/meowos/font/font.png"
#define MEOW_FONT_IDX_SYS "/usr/share/meowos/font/font.idx"
/* 默认字库(卡 MeowOS/fonts/default)与固件内置之外,launcher 会把「当前生效字体」
* 经环境变量下发给 app(见 meow_launcherd.c launch_app / meow_font_effective_paths):
/* 默认字库(卡 MeowOS/fonts/default/regular,M2.6 字重结构)与固件内置之外,launcher 会把
* 「当前生效字体」经环境变量下发给 app(见 meow_launcherd.c launch_app / meow_font_effective_paths):
* MEOW_FONT_PNG / MEOW_FONT_IDX = 绝对路径;MEOW_FONT_APP = <family> 或 "default"。
* SDK load_font 优先读 env(匹配系统 font.file),无则回退卡默认 → 固件。 */
@@ -361,18 +361,19 @@ void meow_ui_swipe_hint(int x)
* 有状态栏(非全屏)→ y = 状态栏高度;收合(collapsed)→ y=提示线下缘
* (MUI_SBAR_COLLAPSE_H,与提示线不重叠);无状态栏(全屏/隐藏)→ y=0。 */
int hy = meow_ui_statusbar_height();
if (x > 0) {
if (x > g_xres) x = g_xres;
/* 往回滑(新长度 < 上次)时,先清掉「上次画到旧长度、但本次超出的部分」,
* 否则条只增不减(用户要求:右滑变长、左滑回缩实时可见)。 */
if (g_hint_last > x) meow_ui_fill_rect(0, hy, g_hint_last, MUI_HINT_H, th->bg);
meow_ui_fill_rect(0, hy, x, MUI_HINT_H, th->accent);
g_hint_last = x;
} else if (g_hint_last > 0) {
/* 手指回原点/抬手未达标/抬手达标:清掉此前画的条(填回背景色) */
meow_ui_fill_rect(0, hy, g_hint_last, MUI_HINT_H, th->bg);
g_hint_last = 0;
/* x<0 视为清空(条长 0);条长上限=屏宽。 */
if (x < 0) x = 0;
if (x > g_xres) x = g_xres;
/* 只按「新旧长度的差」增量绘制,避免整段重涂背景色:
* - 伸长:仅补画新增段(旧段已是 accent,不再用 bg 重涂——否则 app 底色≠theme bg
* 时会在旧段留下 bg 色块,就是残留残条);
* - 回缩/清空:仅把「超出新长度」的尾部填回背景色(实时回缩)。 */
if (x > g_hint_last) {
meow_ui_fill_rect(g_hint_last, hy, x - g_hint_last, MUI_HINT_H, th->accent);
} else if (x < g_hint_last) {
meow_ui_fill_rect(x, hy, g_hint_last - x, MUI_HINT_H, th->bg);
}
g_hint_last = x;
/* 不发 flush:由调用方 meow_ui_flush_region(0,0,宽,6) 只刷顶部条 */
}
@@ -704,7 +705,7 @@ int meow_font_set_tier(int tier)
/* 字库加载优先级(M2.x):
* 1) 环境变量 MEOW_FONT_PNG/MEOW_FONT_IDX(launcher 注入的「当前生效字体」= 系统 font.file)
* —— app 未自带字库(§5.7 load_handle 未实现)时跟随系统 font.file;
* 2) 卡默认 MeowOS/fonts/default/font.png(不再硬编码 MeowAPPs/fonts);
* 2) 卡默认 MeowOS/fonts/default/regular/font.png(M2.6 字重结构;不再硬编码 MeowAPPs/fonts);
* 3) 固件 /usr/share/meowos/font/font.png。
* 全失败 → g_mfont_ready=0,text_draw 回退 5x7 点阵(系统不死)。 */
static int load_font(void)
+39 -18
View File
@@ -125,6 +125,7 @@ static int t_press_valid = 0; /* BTN_TOUCH=1 已见(本次按下的 gate
static int t_dx = 0, t_dy = 0; /* 按下原点(BTN_TOUCH 后首个有效 ABS 锁定) */
static int t_cx = 0, t_cy = 0; /* 当前坐标(-1=本次尚未见到有效坐标) */
static int t_origin_valid = 0; /* 原点是否已锁定 */
static int t_gmax_dx = 0; /* 本手势「最大右移位移」(t_cx-t_dx 的最大值,条长上限) */
/* 按下边沿:只标记按下,绝不用传入坐标立即锁原点(避免用上一手势残留坐标当原点)。
* t_cx/t_cy 归 -1 —— 本次手势以「无坐标」起步,只接受随后到来的有效 ABS。 */
@@ -136,6 +137,7 @@ static void touch_press(int x, int y)
t_origin_valid = 0;
t_cx = -1; t_cy = -1;
t_dx = 0; t_dy = 0;
t_gmax_dx = 0; /* 新手势:最大右移位移清零(条长从 0 计) */
}
/* 由 ABS_X/ABS_Y 驱动:若本次按下且原点未锁且坐标有效 → 锁原点;再更新当前坐标。 */
static void touch_update(int x, int y)
@@ -145,13 +147,17 @@ static void touch_update(int x, int y)
}
if (x >= 0) t_cx = x;
if (y >= 0) t_cy = y;
if (t_down && t_origin_valid) {
int dx = t_cx - t_dx;
if (dx > t_gmax_dx) t_gmax_dx = dx; /* 记录本手势已达的最大右移位移(条长上限) */
}
}
static void touch_release(void)
{
if (!t_down) return;
t_down = 0; t_press_valid = 0;
/* 本次按下从未锁到有效坐标(幽灵触摸/坐标未就绪):丢弃,且清空全部轨迹。 */
if (!t_origin_valid) { t_cx = t_cy = -1; t_dx = t_dy = 0; return; }
if (!t_origin_valid) { t_cx = t_cy = -1; t_dx = t_dy = 0; t_gmax_dx = 0; return; }
t_origin_valid = 0;
int mdx = t_cx - t_dx, mdy = t_cy - t_dy;
int adx = mdx < 0 ? -mdx : mdx, ady = mdy < 0 ? -mdy : mdy;
@@ -163,7 +169,8 @@ static void touch_release(void)
* 被手势级清空,绝不继承上一手势坐标,确保:
* ① 中部右滑不会误判成左缘返回(t_dx>=EDGE_W);
* ② 先点中部再侧滑不返回(原点锁定在中部,>=EDGE_W),符合「必须从左缘起划」;
* ③ 右移严格正向(t_cx-t_dx>=0),杜绝「向左划也出条/误返回」。
* ③ 返回判定要求右移严格正向(t_cx-t_dx>=0 且 ≥SWIPE_THRESH),杜绝「向左划也误返回」;
* 左划过程仅让进度条实时回缩,不额外生长(过程见上方 MEOW_EV_SWIPE_PROGRESS)。
* SDK 统一上报为 MEOW_SWIPE_LEFT(设计"左滑=返回"契约,hello 用 on_swipe(LEFT) 退出)。
*
* 屏幕中部滑动不是返回手势:中部右滑→RIGHT(内容导航),中部左滑→非返回(落 TAP,
@@ -192,7 +199,7 @@ static void touch_release(void)
push_ev(MEOW_EV_TAP, t_cx, t_cy);
}
/* 手势级干净起点:清空全部轨迹,下一手势从零开始,杜绝污染无自愈。 */
t_cx = t_cy = -1; t_dx = t_dy = 0;
t_cx = t_cy = -1; t_dx = t_dy = 0; t_gmax_dx = 0;
}
/* ---- 输入线程:poll 所有 event 设备 + 解析 ---- */
@@ -224,12 +231,22 @@ static void *input_thread(void *arg)
if (code == ABS_X || code == ABS_MT_POSITION_X) {
if (t_down) touch_update(val, t_cy); /* 锁原点+更新当前(t_down 时才驱动) */
else t_cx = val;
/* 左滑进度实时上报(视觉条):原点确实在左缘(t_dx<EDGE_W) 且 向右移动
* (t_cx>t_dx)。抬手触发仍由 touch_release 判定;这里只报过程。
* t_dx 是本次按下后首个有效 ABS 锁定的真实按下点(非残留),且每次抬手已
* 清空,杜绝「中部点一下出条 / 向左划也出条」。 */
if (t_down && t_origin_valid && t_dx >= 0 && t_dx < EDGE_W && t_cx > t_dx)
push_ev(MEOW_EV_SWIPE_PROGRESS, t_dx, t_cx);
/* 左滑进度实时上报(视觉条)。抬手触发仍由 touch_release 判定;这里只报过程。
* 修复要点(左缘返回滑条 bug):
* ① 双向上报(不再要求 t_cx>t_dx):向右→条伸长、向左→条实时回缩,
* 摒弃「只在返回瞬间闪末帧 / 反向才出条」;
* ② 仅左缘起手(t_dx<EDGE_W)才上报并被绘制——屏幕中部起手绝不触发条/返回;
* ③ 条长 = 本手势右移位移 clamped 到「本次手势最大右移位移」(t_gmax_dx),
* 条的最大 x 恒等于本次手势达过的最大右移距离,反复反向滑不会放大它。
* 负数(左滑越过原点)→ 条长 0(清空)。
* 事件载荷:a=按下X(t_dx),b=条长 len(clamp 后,≥0)。 */
if (t_down && t_origin_valid && t_dx >= 0 && t_dx < EDGE_W) {
int dx = t_cx - t_dx;
if (dx > t_gmax_dx) t_gmax_dx = dx;
int len = dx < 0 ? 0 : dx;
if (len > t_gmax_dx) len = t_gmax_dx;
push_ev(MEOW_EV_SWIPE_PROGRESS, t_dx, len);
}
}
else if (code == ABS_Y || code == ABS_MT_POSITION_Y) {
if (t_down) touch_update(t_cx, val); else t_cy = val;
@@ -282,9 +299,12 @@ void meow_ui_run(const meow_ui_events *ev)
meow_input_ev e;
int got = 0;
while (meow_input_pop(&e)) {
if (g_exit) break; /* 已请求退出:不再分发剩余事件,杜绝「退出后又触发」的偶发再进/退 */
got = 1;
int in_startup = (now_ms() < startup_cooldown_until); /* M2.22(C) */
if (e.type == MEOW_EV_TAP) {
if (in_startup) continue; /* 启动窗口也吞 TAP:旧手势残留点按(如上一次左滑抬起)
* 在进入瞬间命中 UI 按钮(如全屏按钮)会误触发——丢弃。 */
/* 抬手未达标:清视觉条 */
meow_ui_swipe_hint(-1);
meow_ui_flush_region(0, meow_ui_statusbar_height(), meow_ui_width(), 6);
@@ -315,16 +335,17 @@ void meow_ui_run(const meow_ui_events *ev)
meow_ui_sys_notif_pull();
} else if (e.type == MEOW_EV_SWIPE_PROGRESS) {
if (in_startup) continue; /* 启动窗口进度条静音 */
int x0 = e.a, xc = e.b, dx = xc - x0; /* dx=当前X-按下X */
/* SDK 自动画顶部条:长度=手指当前 X(=距左缘距离,向内划长度随位移增长)。
* M2.5x/A-13:全屏/收合下「左缘返回手势保留」,顶部动态提示条(meow_ui_swipe_hint)
* 仍需绘制——不再以 meow_ui_sys_statusbar_active()(全屏/隐藏=0)开关,否则全屏
* 时滑条完全不显示。条位 y=meow_ui_statusbar_height():非全屏=状态栏下沿、
* 全屏=0、收合=提示线下缘(MUI_SBAR_COLLAPSE_H),与提示线不重叠加。
* APP 若注册 on_swipe_progress 仍可自绘(系统条照常画)。 */
meow_ui_swipe_hint(xc);
/* e.a=按下X(t_dx),e.b=条长 len(输入层已 clamp 到「本手势最大右移位移」且≥0)。
* SDK 自动画顶部条:从 x=0 画到 len(右移位移),向右伸长、向左实时回缩。
* M2.5x/A-13:全屏/收合下「左缘返回手势保留」,顶部动态提示条仍需绘制——不再以
* meow_ui_sys_statusbar_active()(全屏/隐藏=0)开关,否则全屏时滑条完全不显示。
* 条位 y=meow_ui_statusbar_height():非全屏=状态栏下沿、全屏=0、收合=提示线下缘
* (MUI_SBAR_COLLAPSE_H),与提示线不重叠加。APP 若注册 on_swipe_progress 仍可自绘
* (系统条照常画);递交给 app 的是 (len-x_start) 使 x_start+dx==len,与系统条同长。 */
int len = e.b;
meow_ui_swipe_hint(len);
if (g_events_set && g_events.on_swipe_progress)
g_events.on_swipe_progress(x0, dx);
g_events.on_swipe_progress(e.a, len - e.a);
meow_ui_flush_region(0, meow_ui_statusbar_height(), meow_ui_width(), 6);
}
}