Files
MeowOS-For-R3P2/patch_v2/meow_launcherd/font_theme.c
T
MeowOS e6f0b665e3 refactor(launcher): 拆分 meow_launcherd.c(9778行)→meow_launcherd/ 多文件(agent-149)
- 14 模块 .c + meow_launcher_internal.h(642): util/font_theme/fb_compositor/settings_page/power_menu/notif_center/app_launcher/main_menu/audio/backlight/net/keys/about/statusbar; meow_launcherd.c 已删
- 按行逐字搬移(0-diff), 118函数提升非static+47组extern(内部头), #if0退役区不编入(源码留.pre_split.bak)
- 关于本机小改: 作者 AskaEth/17 + info/item[13]防越界 + s_dd_modal 超高滚动(clamp/触摸滚动/音量键跟随/裁剪, 仅超高启用)
- build_launcher.sh 编译列表→模块; 文档/注释引用 meow_launcherd.c→模块/目录
- 编译0warning/-Wall -Wextra告警集与原文一致(77条无新增)/qemu-mipsel冒烟一致(--scan/--sysdump); launcher=c0799357(505KB; md5因多文件重编译与原文异,非回归依据)
- 备份 meow_launcherd.c.pre_split.bak/meow_launcherd.pre_split.bak; M2.69(0.7.64)已推卡并用户验证OK
2026-08-30 21:38:30 +08:00

733 lines
38 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* font_theme.c — MeowOS launcher 拆分模块(LAUNCHER_SPLIT_PLAN 拆分)。*/
/* 本文件由拆分工具从 meow_launcherd.c 按职责搬移;逻辑与原文 0-diff(仅 static 可见性按跨模块需求调整)。*/
#include "meow_launcher_internal.h"
/* 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 选库。 */
const char *font_presets[FONT_PRESETS_MAX]; /* SC_PRESET 值 = 卡根相对路径 */
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];
const char *font_preset_fams[FONT_PRESETS_MAX]; /* family slug(目录一级) */
const char *font_preset_wts[FONT_PRESETS_MAX]; /* weight slug(目录二级) */
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][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 持久存储 */
int font_presets_n = 0; /* 运行时动态计数(默认=1,仅"默认字体") */
int mfont_ready = 0;
int mfont_forced = 0; /* font.file 变更→强制真实(un)load,跳过 SDK 复用捷径 */
/* fb_open 后调用:设置字库绘制目标为当前双缓冲(buffer0 起点 = fb 偏移 0) */
void meow_font_bind_fb(void)
{
meow_font_set_fb(fb_xres, fb_yres, fb_line);
}
/* 把 font.file 存的「卡根相对/绝对」路径解析为设备绝对路径。
* 相对路径以 /usr/data/mnt/sd_0/ 为卡根(与 MEOWAPPS 同源);绝对路径原样。
* 返回 0 成功,-1 参数无效。 */
int meow_font_resolve_path(const char *ref, char *out, int olen)
{
if (!ref || !ref[0]) return -1;
if (ref[0] == '/') { snprintf(out, olen, "%s", ref); return 0; }
snprintf(out, olen, "/usr/data/mnt/sd_0/%s", ref);
return 0;
}
/* 解析 font.file 指向的 png + 派生同目录 idx(font.png → font.idx)。
* 仅当指向的 png 存在(access R_OK)返回 0;否则 -1(交由回退链)。
* 返回 0:out_png/out_idx 已填设备绝对路径。 */
static int meow_font_resolve_selected(char *out_png, int png_len, char *out_idx, int idx_len)
{
char ref[MEOW_SET_MAX_LEN];
if (meow_settings_get_str("font.file", ref, sizeof ref) != 0) return -1;
if (meow_font_resolve_path(ref, out_png, png_len) != 0) return -1;
if (access(out_png, R_OK) != 0) return -1; /* 指向不存在 → 回退卡默认/固件 */
snprintf(out_idx, idx_len, "%s", out_png); /* 同目录 font.idx(标准三件套布局) */
size_t n = strlen(out_idx);
if (n > 4 && !strcmp(out_idx + n - 4, ".png")) {
out_idx[n-4] = '.'; out_idx[n-3] = 'i'; out_idx[n-2] = 'd'; out_idx[n-1] = 'x'; out_idx[n] = 0;
}
return 0;
}
/* 尝试加载字库;成功返回 1。优先级(M2.5x,font.load_full 已接入):
* 0) 生效档 = font.load_full ? gbk : common(launcher 自身 UI 跟随系统大字库开关);
* 1) 生效字体(tier 解析,tier0 即 font.file 所选;相对路径按卡根解析);
* 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;
* 当字体指向自定义字库时必须显式 load(SDK 只加载默认,不读 font.file)。
* mfont_forced=1(font.file/font.load_full 变更触发)时无条件真实 reload,跳过上述捷径。
* 全失败 → mfont_ready=0,ui_draw 回退 5x7 点阵(系统不死)。 */
int meow_font_ensure(void)
{
if (mfont_ready && !mfont_forced) return 1;
mfont_forced = 0;
char fpng[400], fidx[400], fam[96];
int tier = meow_font_effective_tier(MEOW_FONT_TIER_COMMON); /* launcher 自身 UI 跟随 load_full */
int have_sel = (meow_font_effective_paths_tiered(tier, fpng, sizeof fpng,
fidx, sizeof fidx, fam, sizeof fam) == 0);
/* SDK 已加载且所选字库为「默认卡字库」(或未设置)→ 复用,避免重复解码。 */
if (meow_gfx_mfont_ready() && (!have_sel || !strcmp(fpng, UI_FONT_PNG_CARD))) {
mfont_ready = 1;
logmsg("meow_font: reuse sdk-loaded font");
return 1;
}
/* 生效字体优先(tier=common 时即 font.file;load_full → 大字库 gbk 档)。 */
if (have_sel && meow_font_load(fpng, fidx) == 0) {
mfont_ready = 1;
logmsg("meow_font: loaded %s (%d/%d sizes), tier=%d", fpng,
meow_font_has_size(28), meow_font_has_size(20), tier);
return 1;
}
if (have_sel)
logmsg("meow_font: selected font %s missing, fallback default", fpng); /* 系统日志 */
/* 卡默认 → 固件。 */
if (meow_font_load(UI_FONT_PNG_CARD, UI_FONT_IDX_CARD) == 0 ||
meow_font_load(UI_FONT_PNG_SYS, UI_FONT_IDX_SYS) == 0) {
mfont_ready = 1;
logmsg("meow_font: loaded fallback (%d/%d sizes)",
meow_font_has_size(28), meow_font_has_size(20));
return 1;
}
logmsg("meow_font: all font sources missing, using 5x7 bitmap"); /* WARN:字库不可用 */
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"。 */
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>/<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,
char *out_family, int flen)
{
char fpng[400], fidx[400];
if (meow_font_resolve_selected(fpng, sizeof fpng, fidx, sizeof fidx) == 0) {
snprintf(out_png, plen, "%s", fpng);
snprintf(out_idx, ilen, "%s", fidx);
font_family_from_png(fpng, out_family, flen);
return 0;
}
/* 回退卡默认 MeowOS/fonts/default */
if (access(UI_FONT_PNG_CARD, R_OK) == 0) {
snprintf(out_png, plen, "%s", UI_FONT_PNG_CARD);
snprintf(out_idx, ilen, "%s", UI_FONT_IDX_CARD);
snprintf(out_family, flen, "default");
return 0;
}
return -1;
}
/* 档位 → font.json charset 字符串名(common/gbk/full)。 */
static const char *font_tier_charset(int tier)
{
if (tier >= MEOW_FONT_TIER_FULL) return "full";
if (tier >= MEOW_FONT_TIER_GBK) return "gbk";
return "common";
}
/* 生效档:app 声明档(0/1/2,越界 clamp)合并「加载大字库」开关(font.load_full)。
* 开 → 强制 ≥GBK(gbk≈20k 档大字库);关(默认)→ 保持 app 声明(缺省 common≈4k)。
* 断言:0<=返回<=2。 */
int meow_font_effective_tier(int app_tier)
{
if (app_tier < MEOW_FONT_TIER_COMMON) app_tier = MEOW_FONT_TIER_COMMON;
if (app_tier > MEOW_FONT_TIER_FULL) app_tier = MEOW_FONT_TIER_FULL;
int lf = 0;
if (meow_settings_get_switch("font.load_full", &lf) == 0 && lf)
if (app_tier < MEOW_FONT_TIER_GBK) app_tier = MEOW_FONT_TIER_GBK;
return app_tier;
}
/* 【tier 选择入口,M2.5x 已实现】按字体档位取「当前生效字体」png/idx,供 launch_app 注入
* MEOW_FONT_PNG/IDX/APP(配合 MEOW_FONT_TIER)。口径(MEOWOS_SPEC_REF §5.2/§5.7.8):
* - 生效档 = max(app 声明档, font.load_full ? GBK : COMMON):系统「加载大字库」开 →
* 强制 ≥gbk;
* - tier<=common → 走既有 meow_font_effective_paths(font.file/卡默认/固件);
* - 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 无可用。 */
int meow_font_effective_paths_tiered(int tier, char *out_png, int plen,
char *out_idx, int ilen, char *out_family, int flen)
{
int eff = meow_font_effective_tier(tier); /* 合并 font.load_full:开 → 强制 ≥GBK */
if (eff <= MEOW_FONT_TIER_COMMON)
return meow_font_effective_paths(out_png, plen, out_idx, ilen, out_family, flen);
/* 大字库:从扫描结果选 charset 匹配的一套。
* 打分(档位主导 ×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 = -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 = 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 >= 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;
snprintf(out_idx, ilen, "%s", out_png);
size_t n = strlen(out_idx);
if (n > 4 && !strcmp(out_idx + n - 4, ".png")) {
out_idx[n-4] = '.'; out_idx[n-3] = 'i'; out_idx[n-2] = 'd';
out_idx[n-1] = 'x'; out_idx[n] = 0;
}
font_family_from_png(out_png, out_family, flen);
return 0;
}
fallback:
/* 无匹配大字库/不可读 → 回退全局(font.file 所选;其可能是大字库或默认)。 */
return meow_font_effective_paths(out_png, plen, out_idx, ilen, out_family, flen);
}
/* M2.5x 多字体:从 settings 读 display.multi_font + 6 类 font.class_*,把各自相对路径按卡根
* 解析为绝对路径,去重装载到 meow_font 槽位(同 family 去重 & ≤4 套上限由
* meow_font_config_multifont 统一处理)。仅当全局字库已装载(mfont_ready / slot0)时生效;
* 关或未装载 → 单字体(on=0),零额外开销。调用时机:启动 meow_font_ensure() 之后,以及
* 设置页变更 display.multi_font / font.class_* / font.file。 */
void meow_font_reload_multifont(void)
{
static const char *cls_keys[MEOW_FCLS_COUNT] = {
"font.class_cjk", "font.class_cjk_sym", "font.class_num",
"font.class_latin", "font.class_latin_sym", "font.class_other" };
int on = 0;
meow_settings_get_switch("display.multi_font", &on);
char gpng[400], gidx[400], gfam[96];
/* 全局跟生效档(合并 font.load_full → 大字库),与 meow_font_ensure 同源,避免槽0实际装载与
* 此处理解不一致(多字体去重以全局 png 为基准)。 */
if (meow_font_effective_paths_tiered(meow_font_effective_tier(MEOW_FONT_TIER_COMMON),
gpng, sizeof gpng, gidx, sizeof gidx, gfam, sizeof gfam) != 0)
return; /* 无全局字体:无法多字体 */
/* 各类候选(静态缓冲保活;config_multifont 同步读取,不持指针) */
static char cpng[MEOW_FCLS_COUNT][400], cidx[MEOW_FCLS_COUNT][400];
meow_font_class_cfg cfg[MEOW_FCLS_COUNT];
memset(cfg, 0, sizeof cfg);
for (int c = 0; c < MEOW_FCLS_COUNT; c++) {
char ref[MEOW_SET_MAX_LEN];
if (meow_settings_get_str(cls_keys[c], ref, sizeof ref) == 0 && ref[0]) {
char abs[400];
if (meow_font_resolve_path(ref, abs, sizeof abs) == 0 && access(abs, R_OK) == 0) {
snprintf(cpng[c], sizeof cpng[c], "%s", abs);
snprintf(cidx[c], sizeof cidx[c], "%s", abs);
size_t n = strlen(cidx[c]);
if (n > 4 && !strcmp(cidx[c] + n - 4, ".png")) {
cidx[c][n-4]='.'; cidx[c][n-3]='i'; cidx[c][n-2]='d'; cidx[c][n-1]='x'; cidx[c][n]=0;
}
cfg[c].png = cpng[c]; cfg[c].idx = cidx[c];
} else {
/* M2.58:类别字体配置了但缺失 → 该类别回退全局,并记系统 WARN。 */
logmsg("multi_font: class %s font %s missing, fallback global", cls_keys[c], ref);
}
}
}
int distinct = 0, truncated = 0;
if (meow_font_config_multifont(on, gpng, gidx, cfg, &distinct, &truncated) != 0)
logmsg("multi_font: config failed -> single-font");
else if (truncated)
logmsg("multi_font: WARN >%d 套不同字体(会增内存),已按上限截断", distinct);
else if (on)
logmsg("multi_font: enabled, %d distinct fonts (global=%s)", distinct, gfam);
}
/* ================================================================== */
/* 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)", // 信息用,不参与校验 */
/* "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 校验 */
/* "sha256_idx": "<64hex>", // font.idx 校验 */
/* "source": "...", // 可选:来源 */
/* "license": "..." // 可选:许可 */
/* } */
/* 校验:sha256 不匹配仅 WARN 不硬失败(仍可选);缺三件套或 font.json */
/* 解析不出 name → 跳过 + 日志。仅启动/进设置页扫描一次(勿放热路径)。 */
/* ================================================================== */
/* 计算文件 sha256(走 busybox/宿主 sha256sum;输出小写 64hex)。返回 0 成功。 */
static int font_sha256_file(const char *path, char *out, int olen)
{
out[0] = 0;
/* 单引号转义防 shell 注入(路径含 ' 时安全;转义后超长则截断 → sha256sum 失败走 WARN) */
char esc[1024]; int j = 0;
for (const char *p = path; *p && j < (int)sizeof esc - 4; ) {
if (*p == '\'') { memcpy(esc + j, "'\\''", 4); j += 4; p++; }
else esc[j++] = *p++;
}
esc[j] = 0;
char cmd[1080];
snprintf(cmd, sizeof cmd, "sha256sum '%s' 2>/dev/null", esc);
FILE *f = popen(cmd, "r");
if (!f) return -1;
char line[160];
int ok = -1;
if (fgets(line, sizeof line, f)) {
int i = 0, k = 0;
while (line[i] == ' ' || line[i] == '\t') i++; /* 跳前导空白 */
while (line[i] && k < 64) {
char c = line[i++];
if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'))
out[k++] = c;
else break;
}
out[k] = 0;
ok = (k == 64) ? 0 : -1;
}
pclose(f);
return ok;
}
/* 比较两个 sha256 十六进制串(大小写不敏感)。均为 64hex 才比对,否则视为不匹配。 */
static int font_sha_eq(const char *a, const char *b)
{
if (!a || !b || strlen(a) < 64 || strlen(b) < 64) return 0;
for (int i = 0; i < 64; i++) {
char ca = a[i], cb = b[i];
if (ca >= 'A' && ca <= 'F') ca += 32;
if (cb >= 'A' && cb <= 'F') cb += 32;
if (ca != cb) return 0;
}
return 1;
}
/* 解析 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 *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];
while (fgets(buf, sizeof buf, f)) {
if (!name[0]) json_str(buf, "name", name, nlen);
if (!sha_png[0]) json_str(buf, "sha256_png", sha_png, slen);
if (!sha_idx[0]) json_str(buf, "sha256_idx", sha_idx, xlen);
/* M2.5x 字体档位:charset(common|gbk|full)+ glyph_count(信息用,驱动档位分层/标注)。 */
if (charset && !charset[0]) {
char v[8] = "";
if (json_str(buf, "charset", v, sizeof v) > 0 && v[0])
snprintf(charset, clen, "%s", v);
}
if (glyph_count && *glyph_count == 0) {
char v[16] = "";
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)
{
const char *tier = (charset && charset[0]) ? charset : "common";
if (!strcmp(tier, "full")) { snprintf(out, olen, "full"); return; }
if (glyph_count > 0)
snprintf(out, olen, "%s≈%dk", tier, (glyph_count + 500) / 1000);
else
snprintf(out, olen, "%s", tier);
}
/* 探测一个 <卡根>/MeowOS/fonts/<family>/<weight> 套件是否合法:三件套齐全 → 解析 label+档位+字重。
* 返回 1=进列表,0=跳过(缺组件/json 不可读)。sha 失败仅 WARN。
* 输出: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 *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/%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 wts[32] = "";
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);
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(%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/%s' sha256(png) 不可用,跳过校验", family, wt);
else if (jsha_png[0] && !font_sha_eq(apng, jsha_png))
logmsg("fonts: '%s/%s' WARN sha256_png 不匹配(仍可选)", family, wt);
if (font_sha256_file(ipath, aidx, sizeof aidx) != 0)
logmsg("fonts: '%s/%s' sha256(idx) 不可用,跳过校验", family, wt);
else if (jsha_idx[0] && !font_sha_eq(aidx, jsha_idx))
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;
}
/* M2.58 字体索引:CSV 字段写入(始终双引号包裹,内嵌双引号加倍)。返回写入长度。 */
static int font_index_csvq(char *dst, int olen, const char *s)
{
int o = 0;
if (olen <= 0) return 0;
if (o < olen) dst[o++] = '"';
for (; *s && o < olen - 3; s++) {
if (*s == '"') dst[o++] = '"';
if (o < olen - 2) dst[o++] = *s;
}
if (o < olen) dst[o++] = '"';
dst[o] = 0;
return o;
}
/* 把当前 font_presets* 扫描结果落盘为 <card_root>/MeowOS/config/fonts/index(覆盖)。
* 每套一条 `"family","weight","path","label","tier",glyph_count`;path=卡根相对 font.png。
* 目录不存在则 mkdir -p;无卡(mkdir/fopen 失败)→ 静默放弃(仅内存默认项)。
* 该文件是 SDK meow_font_get_available_count()/enumerate() 的持久数据源(规格 §5.7)。 */
static void meow_font_write_index(const char *card_root)
{
if (!card_root || !card_root[0]) return;
char t1[360], t2[380], base[400], path[420];
snprintf(t1, sizeof t1, "%s/MeowOS", card_root);
snprintf(t2, sizeof t2, "%s/MeowOS/config", card_root);
snprintf(base, sizeof base, "%s/MeowOS/config/fonts", card_root);
/* 无卡(card_root 父目录不存在 → ENOENT)→ 静默放弃:仅内存默认项,不刷系统日志。 */
if (mkdir(t1, 0755) != 0 && errno != EEXIST) {
if (errno != ENOENT) logmsg("fonts: index mkdir fail %s", t1);
return;
}
if (mkdir(t2, 0755) != 0 && errno != EEXIST) { logmsg("fonts: index mkdir fail %s", t2); return; }
if (mkdir(base, 0755) != 0 && errno != EEXIST) { logmsg("fonts: index mkdir fail %s", base); return; }
snprintf(path, sizeof path, "%s/index", base);
FILE *f = fopen(path, "w");
if (!f) { logmsg("fonts: index fopen fail %s", path); return; }
char line[1024];
for (int i = 0; i < font_presets_n; i++) {
const char *fam = font_preset_fams[i] ? font_preset_fams[i] : "default";
const char *wt = font_preset_wts[i] ? font_preset_wts[i] : "regular";
const char *val = font_presets[i] ? font_presets[i] : "MeowOS/fonts/default/regular/font.png";
const char *lbl = font_preset_labels[i] ? font_preset_labels[i] : fam;
const char *tier = font_preset_tiers[i] ? font_preset_tiers[i] : "common";
int o = 0;
o = font_index_csvq(line, sizeof line, fam);
line[o++] = ','; line[o] = 0;
o += font_index_csvq(line + o, sizeof line - (size_t)o, wt);
line[o++] = ','; line[o] = 0;
o += font_index_csvq(line + o, sizeof line - (size_t)o, val);
line[o++] = ','; line[o] = 0;
o += font_index_csvq(line + o, sizeof line - (size_t)o, lbl);
line[o++] = ','; line[o] = 0;
o += font_index_csvq(line + o, sizeof line - (size_t)o, tier);
line[o++] = ','; line[o] = 0;
snprintf(line + o, sizeof line - (size_t)o, "%d\n", font_preset_glyphs[i]);
fputs(line, f);
}
fclose(f);
logmsg("fonts: index written (%d entries) -> %s", font_presets_n, path);
}
/* 扫描 <card_root>/MeowOS/fonts/ 下两级 <family>/<weight>/,重建字体 preset 列表。
* 每个 (family,weight) 组合一套(如 misans/regular、misans/bold);family 级 'default' 由
* 索引 0(默认字体)单独处理,不进循环,避免重复。上限 FONT_PRESETS_MAX
* (1 默认 + ≤16 用户字体)。无卡/无目录时仅默认。
*
* M2.58 释放语义(规格 §5.7 需求 4「扫→写 index→释放(或按需读 index)」结论):
* - 持久来源 = <card_root>/MeowOS/config/fonts/index(扫描后由 meow_font_write_index 落盘)。
* SDK 只读该文件,从不依赖本内存数组。
* - font_presets* / *_buf 均为「静态、有界(FONT_PRESETS_MAX=17)、非堆」数组:无内存泄漏,
* 故无运行期 free;「释放」含义 = 两个扫描点(启动 / 进设置页)重建 + 每套固定上限防膨胀。
* - 设置字体列表(font.file / font.class_* 下拉、family→字重子菜单)在本视图内显示时用
* 该数组;离开字体列表视图(run_settings_page 退出)无需显式 free(静态数组),下次
* 进设置/重扫自动重建。
* - ⚠️ 未做「启动后即清空数组」:meow_font_effective_paths_tiered(launch_app 大字库档位
* 选库)与 meow_font_reload_multifont 在运行期持续读 font_presets_n,清空会造成大字库
* 档位退化(red line「保持档位/多字体行为」)。如需「零驻留」,须把这几个核心路径改为
* 直接读 index(较大重构,超出本批边界,留待后续)。 */
void meow_font_scan_presets_root(const char *card_root)
{
font_presets_n = 0;
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] = "", 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, wt0, sizeof wt0);
char mk[24]; font_tier_marker(cs, gc, mk, sizeof 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;
}
font_presets_n = 1;
char root[340];
snprintf(root, sizeof root, "%s/%s", card_root, MEOW_FONTS_CFG);
DIR *d = opendir(root);
if (d) { /* 无卡/无字库目录 → 跳过扫描,仅默认 */
struct dirent *de;
while ((de = readdir(d)) && font_presets_n < FONT_PRESETS_MAX) {
if (de->d_name[0] == '.') continue;
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);
}
/* M2.58:扫描结果落盘为 <card_root>/MeowOS/config/fonts/index(含默认行 index0),
* 作为 SDK meow_font_get_available_count()/enumerate() 的持久数据源;无卡时仅默认 1 行,
* 由 meow_font_write_index 内部 mkdir-fail 静默放弃(此路径不刷屏)。 */
meow_font_write_index(card_root);
}
/* 设备默认卡根扫描(等价现状:无卡时 opendir 失败 → 仅默认字体)。 */
void meow_font_scan_presets(void)
{
meow_font_scan_presets_root("/usr/data/mnt/sd_0");
}
/* MD3 tonal palette(565 参考值见 MD3_UI_SPEC §1;此处存 24-bit RGB,#hex) */
static const meow_ui_theme_t meow_ui_theme_dark = {
0x141824, 0x1E2436, 0x263038, 0x4A6A9A, 0xFFFFFF,
0x9AA4C8, 0x2A2E38, 0xE57373, 0x4A6A9A
};
static const meow_ui_theme_t meow_ui_theme_light = {
0xE8EAF0, 0xD5D9E4, 0xCFD4E0, 0x2A5A8A, 0x1A1A2A,
0x4A4F5A, 0x9AA4C8, 0xB3261E, 0x2A5A8A
};
/* 获取当前本地时刻的「当天第几分钟」(tm_hour*60+tm_min);供时段判定用 */
static int s_now_min_of_day(void)
{
time_t t = time(NULL);
struct tm tm;
localtime_r(&t, &tm);
return tm.tm_hour * 60 + tm.tm_min;
}
/* 深色时段判定(纯逻辑,便于单测):now_min ∈ [start, end) → 深色。
* - start==end 视为未配置,返回 -1(由调用方决定回退策略)。
* - start<end:非跨天,如 720→1080 = 12:00→18:00,now ∈ [720,1080) 为深色。
* - start>end:跨午夜,如 1140→420 = 19:00→次日07:00,now∈[1140,1440)∪[0,420) 为深色。
* - 边界:end 半开(恰好 end 分回到浅色),start 闭(恰好 start 分进入深色)。 */
static int theme_should_dark(int now_min, int start, int end)
{
if (start == end) return -1; /* 未配置 */
if (start < end)
return (now_min >= start && now_min < end) ? 1 : 0;
return (now_min >= start || now_min < end) ? 1 : 0; /* 跨午夜 */
}
/* 当前主题(dark/light 两套;接 display.theme_mode + display.theme)。
* manual(mode==THEME_MODE_MANUAL):直读 display.theme(dark/light),保持原有行为。
* auto(mode==THEME_MODE_AUTO):读深色时段两键 + 当前本地分钟;start==end 未配置 →
* 回退 manual(读 display.theme,避免误判全天深色)。
* 读取失败一律回退 dark。 */
const meow_ui_theme_t *s_ui_theme(void)
{
int mode = THEME_MODE_MANUAL;
if (meow_settings_get_enum("display.theme_mode", &mode) != 0) mode = THEME_MODE_MANUAL;
int v = 0; /* display.theme(manual)当前索引 */
int have_manual = (meow_settings_get_enum("display.theme", &v) == 0);
if (mode == THEME_MODE_AUTO) {
int start = 0, end = 0;
meow_settings_get_int("display.theme_dark_start", &start);
meow_settings_get_int("display.theme_dark_end", &end);
int d = theme_should_dark(s_now_min_of_day(), start, end);
if (d < 0) { /* 未配置:回退 manual(display.theme) */
if (have_manual && v == 1) return &meow_ui_theme_light;
return &meow_ui_theme_dark;
}
return d ? &meow_ui_theme_dark : &meow_ui_theme_light;
}
/* manual:照旧读 display.theme */
if (have_manual && v == 1) return &meow_ui_theme_light;
return &meow_ui_theme_dark;
}