fix(ncm): 列表首行标题不显示 — 行高按字体行高派生(M12)

根因(真机 seated,非数据/颜色/遮挡):SDK meow_listview 双行渲染把
「2*行高 + 2」的文本块在行内垂直居中,且标题只在 ty >= 视口顶时才画
(meow_ui_widget.c:2061 的越界守卫)。真机 source_han_serif 20px 面
ascent 24 + descent 6 → 行高 30 → 文本块 62px,而 ncm 行高 APP_ROW_H=56
→ ty = 行顶 - 3;首行行顶 == 视口顶 → ty < 视口顶 → 标题被整条跳过,
副标题(ty+32)仍在视口内照画。两类列表同用该路径 + 同一行高,故症状一致。

修复(不动 SDK):ncm_app_internal.h 新增 ncm_row_h()/ncm_row_h_for_line_h(),
行高 = max(56, 2*字体行高 + 2 + 8)(偶数;本机 30 → 70,卡高 62 == 文本块);
home/playlist/queue/cache/search 在 page create 取一次 g_item_h,统一用于
set_item_h 与卡片几何/可见行/页脚/补载视口换算。

回归:
- app/test/test_meow_row_text_fit.c(新):真机字库行高矩阵 14/21/26/27/30
  48 断言(文本块 ≤ 行高、首行 ty ≥ 0、卡高 ≥ 文本块、旧 56 哨兵)。
- app/test/test_first_row_title_render.c(新):--wrap 行高=30 驱动真实 SDK
  渲染 + 读回 PPM 像素计数:旧 56 → 首行标题 0 像素/副标题 186;新 70 →
  标题 84/副标题 100。
- test_meow_row_gap、plpage_sim 全 PASS;ncm_host 7 页出图与改动前逐字节相同
  (宿主行高 14 → 行高仍 56,证重构无视觉回归);5 页自检 PASS;0 warning。
- plpage_sim 临时目录改 $TMPDIR(Termux /tmp 不可写,原写死无法运行)。
- REPORT §43:根因/排除方向/修复/验证/真机验收要点。
This commit is contained in:
AskaEth committed 2026-11-05 19:51:01 +08:00
1 parent 98b9169b86
commit 22f9dab2ce
11 files changed
+440 -34

No files matched your search

+9 -5
View File
@@ -161,14 +161,14 @@ static int cond_first_screen(void)
return g_st.track_ids_count == SIM_TOTAL && g_song_calls >= 1;
}
/* 滚动到指定首行(行高 APP_ROW_H px;swipe 距离按当前行差换算) */
/* 滚动到指定首行(行高 ncm_row_h() px;swipe 距离按当前行差换算) */
static void scroll_to_row(int row)
{
static int cur;
int d = row - cur;
cur = row;
playlist_swipe(d >= 0 ? MEOW_SWIPE_UP : MEOW_SWIPE_DOWN,
(d >= 0 ? d : -d) * APP_ROW_H);
(d >= 0 ? d : -d) * ncm_row_h());
}
int main(void)
@@ -176,15 +176,19 @@ int main(void)
printf("== plpage_sim: 懒加载分页 + 加载后列表刷新(%d 首,窗口 %d,批 %d,触底余量 20)==\n",
SIM_TOTAL, NCM_SONG_WINDOW, NCM_SONG_BATCH);
/* 宿主 SDK 离屏初始化(与 ncm_host 同口径) */
setenv("MEOW_UI_DUMP", "/tmp/plpage_sim.ppm", 0);
/* 宿主 SDK 离屏初始化(与 ncm_host 同口径)。临时目录优先 $TMPDIR(Termux/容器里 /tmp 常不可写) */
const char *td = getenv("TMPDIR");
if (!td || !td[0]) td = "/tmp";
char dmp[256], cdt[256];
snprintf(dmp, sizeof dmp, "%s/plpage_sim.ppm", td);
setenv("MEOW_UI_DUMP", dmp, 0);
meow_ui_opts o = { APP_W, APP_H };
if (meow_ui_init(&o) != 0) { fprintf(stderr, "init failed\n"); return 1; }
meow_theme_set_mode(MEOW_THEME_DARK);
ncm_on_theme(MEOW_THEME_DARK);
/* 歌单 mock:1 张 SIM_TOTAL 首歌单;songmeta 盘缓存用临时目录 */
char cdt[] = "/tmp/plpage_cache_XXXXXX";
snprintf(cdt, sizeof cdt, "%s/plpage_cache_XXXXXX", td);
if (!mkdtemp(cdt)) { fprintf(stderr, "mkdtemp failed\n"); return 1; }
snprintf(g_st.cache_dir, sizeof g_st.cache_dir, "%s", cdt);
g_st.playlist_count = 1;