798 lines
42 KiB
C
798 lines
42 KiB
C
/* font_theme.c — MeowOS launcher 拆分模块(LAUNCHER_SPLIT_PLAN 拆分)。*/
|
||
/* 本文件由拆分工具从 meow_launcherd.c 按职责搬移;逻辑与原文 0-diff(仅 static 可见性按跨模块需求调整)。*/
|
||
#include "meow_launcher_internal.h"
|
||
|
||
/* M2.6 字重结构 + M2.8 三级定案(FONT_SYSTEM):卡内字体 =
|
||
* <卡根>/MeowOS/fonts/<family>/<weight>/(两级,向后兼容)或
|
||
* <卡根>/MeowOS/fonts/<family>/<weight>/<tier>/(三级,tier=common/gbk/full)。
|
||
* 每个 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_preset_labels[FONT_PRESETS_MAX]; /* SC_PRESET 中文标签(含字重+档位大小标记) */
|
||
|
||
/* M2.5x/M2.8 字体档位:每套的档("common"/"gbk"/"full";两级取 font.json charset,缺省
|
||
* "common";三级取 tier 目录名,权威)与 glyph_count(信息用)。供档位分层装载
|
||
* (meow_font_effective_paths_tiered)与设置页标注/选档。 */
|
||
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 → 在已扫描的 preset(两级或三级,值路径 = MeowOS/fonts/<family>/<weight>[/<tier>]/font.png)
|
||
* 里找档位匹配(gbk 请求接受 gbk/full;full 请求接受 full)的那套,以其 font.png/idx 绝对路径返回
|
||
* (family = 一级目录名)。
|
||
* - M2.6 字重:优先选「与用户当前 font.file 同 family(其次同 weight)」的大字库,避免
|
||
* 大字库跳到另一家字体(三级同 family 下按 tier 找 gbk/full,见下);找不到匹配 →
|
||
* 回退 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)
|
||
{
|
||
if (!out || olen <= 0) return -1;
|
||
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);
|
||
/* M2.84-bugfix:禁止裸 popen+fgets——sha256sum 在卡慢/挂起时可能长时间不返回,
|
||
* fgets 会阻塞在 pipe_wait(主循环卡死、电源键无响应)。改用有界 popen_read_tmo
|
||
* (O_NONBLOCK + poll + 超时,超时 SIGKILL 子进程),绝不阻塞主循环。 */
|
||
char *o = popen_read_tmo(1500, cmd);
|
||
if (!o) return -1;
|
||
int i = 0, k = 0;
|
||
while (o[i] == ' ' || o[i] == '\t') i++; /* 跳前导空白 */
|
||
while (o[i] && k < 64 && k + 1 < olen) {
|
||
char c = o[i++];
|
||
if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'))
|
||
out[k++] = c;
|
||
else break;
|
||
}
|
||
out[k] = 0;
|
||
free(o);
|
||
return (k == 64) ? 0 : -1;
|
||
}
|
||
|
||
|
||
/* 比较两个 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);
|
||
}
|
||
|
||
|
||
/* 探测一套件是否合法:三件套齐全 → 解析 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。
|
||
* 输出: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,
|
||
const char *tier_dir,
|
||
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);
|
||
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) {
|
||
logmsg("fonts: skip '%s/%s%s%s' (缺 font.png/font.idx/font.json)", family, weight, tsep, tiernm);
|
||
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");
|
||
/* 档位:三级定案 = tier 目录名(权威);两级(向后兼容)= font.json charset(缺省 common)。 */
|
||
const char *tier = is3 ? tier_dir : (cs[0] ? cs : "common");
|
||
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(tier, gc, marker, sizeof marker);
|
||
snprintf(label, llen, "%s %s(%s)", name[0] ? name : family, wd, marker);
|
||
if (charset) snprintf(charset, clen, "%s", tier);
|
||
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%s%s' sha256(png) 不可用,跳过校验", family, wt, tsep, tiernm);
|
||
else if (jsha_png[0] && !font_sha_eq(apng, jsha_png))
|
||
logmsg("fonts: '%s/%s%s%s' WARN sha256_png 不匹配(仍可选)", family, wt, tsep, tiernm);
|
||
if (font_sha256_file(ipath, aidx, sizeof aidx) != 0)
|
||
logmsg("fonts: '%s/%s%s%s' sha256(idx) 不可用,跳过校验", family, wt, tsep, tiernm);
|
||
else if (jsha_idx[0] && !font_sha_eq(aidx, jsha_idx))
|
||
logmsg("fonts: '%s/%s%s%s' WARN sha256_idx 不匹配(仍可选)", family, wt, tsep, tiernm);
|
||
logmsg("fonts: add '%s/%s%s%s' (%s) tier=%s glyph_count=%d", family, wt, tsep, tiernm, label, tier, 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);
|
||
}
|
||
|
||
|
||
/* 把一个探测成功的套装登记为 preset i(值路径 = MeowOS/fonts/<family>/<weight>[/<tier>]/font.png)。
|
||
* tier 非空 → 三级路径;否则 → 两级路径(旧结构向后兼容)。返回 0 成功,-1 已满。 */
|
||
static int font_presets_add(const char *family, const char *weight, const char *tier,
|
||
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)」结论):
|
||
* - 持久来源 = <card_root>/MeowOS/config/fonts/index(扫描后由 meow_font_write_index 落盘)。
|
||
* SDK 只读该文件,从不依赖本内存数组。
|
||
* - font_presets* / *_buf 均为「静态、有界(FONT_PRESETS_MAX=64)、非堆」数组:无内存泄漏,
|
||
* 故无运行期 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], pngcheck[420];
|
||
snprintf(dirpath, sizeof dirpath, "%s/%s", fampath, we->d_name);
|
||
snprintf(pngcheck, sizeof pngcheck, "%s/font.png", dirpath);
|
||
char label[96] = "", cs[8] = ""; int gc = 0; char wt[32] = "", nm[64] = "";
|
||
if (access(pngcheck, R_OK) == 0) {
|
||
/* 两级(旧结构向后兼容):<family>/<weight>/font.png,tier 读 font.json charset。 */
|
||
if (meow_font_probe_weight(dirpath, de->d_name, we->d_name, NULL,
|
||
label, sizeof label, cs, sizeof cs, &gc,
|
||
wt, sizeof wt, nm, sizeof nm) == 0)
|
||
continue; /* 缺组件/损坏 → 跳过该套 */
|
||
font_presets_add(de->d_name, we->d_name, NULL, label, cs, gc, wt, nm);
|
||
continue;
|
||
}
|
||
/* 三级定案:进入 <weight>/<tier>/ 逐套探测,tier=目录名(权威档位)。 */
|
||
DIR *t = opendir(dirpath);
|
||
if (!t) continue;
|
||
struct dirent *te;
|
||
while ((te = readdir(t)) && font_presets_n < FONT_PRESETS_MAX) {
|
||
if (te->d_name[0] == '.') continue;
|
||
/* tier 仅接受 common/gbk/full(FONT_SYSTEM §1);杂散子目录不进列表。 */
|
||
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(d);
|
||
}
|
||
/* M2.58:扫描结果落盘为 <card_root>/MeowOS/config/fonts/index(含默认行 index0),
|
||
* 作为 SDK meow_font_get_available_count()/enumerate() 的持久数据源;无卡时仅默认 1 行,
|
||
* 由 meow_font_write_index 内部 mkdir-fail 静默放弃(此路径不刷屏)。
|
||
* M2.82+ 修补:挂载点目录在无卡时也存在(mkdir 骗过 ENOENT 检查)→ 开机早扫曾把
|
||
* 卡上 18 条 index 覆盖成 1 条默认。故:fonts 根目录根本没打开(opendir 失败)且
|
||
* 只剩默认项时,判定为「卡在但字库目录不可读/未就绪」→ 不写 index,保留旧索引。 */
|
||
if (!d && font_presets_n <= 1) {
|
||
logmsg("fonts: scan root unavailable, keep existing index (%d presets in mem)", font_presets_n);
|
||
return;
|
||
}
|
||
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;
|
||
}
|
||
|