Files
MeowOS-SDK/tuku/test/test_webtoon_strip.c
T
AskaEth ff97f3cee7 feat(tuku): M7 条漫模式与续读——模式一键切换/每册记忆、视口±1 懒解码、滑块跳页、册间衔接
- 条漫:meow_scroll 跟手+fling,按滚动位置懒解码视口±1 页(驻留上限 4 页);
  排版用 mr_book_meta 累加适宽高度(nav.c 纯逻辑,宿主可测)
- 模式:阅读 chrome 图标一键切翻页/条漫;每册模式与 last page 经 meow_config 持久化
  (reader.mode/page.<FNV1a64(册路径)>),重进该册恢复模式与页码
- 续读:离开/翻页/滚动停止节流落盘;书库扫描给含图册副标题追加「· 续读 Pn」
- 册间衔接:末页前进→同父目录下一含图册首页,首页后退→上一册末页;无相邻册轻提示不退出
- 跳页:chrome 内 meow_slider,目标页预解码完成后落位(2.5s 超时降级提示)
- 解码:改走 M12 预算解码 meow_img_load_file_dim;超大 PNG/BMP 先 probe 预判,
  超 40MiB 原始 RGBA 跳过并显示「图片无法显示」,12MiB 以上记 WARN(如实保留残余风险)
- 测试:新增条漫排版/跳页状态/模式续读持久化/册间边界宿主测试,以及真实 SDK 源码树上的
  阅读器集成测试(模式切换/跳页落位/续读/跨册),test/run_webtoon_tests.sh 一键运行
2026-11-04 21:49:33 +08:00

221 lines
9.1 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.
/* test_webtoon_strip.c — 条漫排版/滚动窗口纯逻辑宿主测试(无 SDK/硬件)。
*
* 覆盖:
* W1 单页:高度=适宽、总高、max_offset=0、窗口只有本页、顶/底同时成立;
* W2 多页:按页高累加 offsets;page_at 二分定位(页边界/末页/越界);
* W3 可见窗口:视口±1 页;max_pages 截断;条底/条顶窗口不越界;
* W4 尺寸兜底:未知 meta → 2:3 占位;横图适宽;极端长图夹到上限;
* W5 参数/生命周期:空册、非法视口、重复 free、重建;
* W6 跳页落位:offset_for_page 与 page_at 互逆(含末页 max 夹取)。
*
* 编译(在 tuku/ 下):
* gcc -Wall -Wextra -O1 -I. test/test_webtoon_strip.c logic/reader/nav.c \
* -o test/test_webtoon_strip && ./test/test_webtoon_strip
* 或 test/run_webtoon_tests.sh。
*/
#include "logic/reader/nav.h"
#include <stdio.h>
#include <string.h>
static int g_pass, g_fail;
#define CHECK(cond, ...) do { \
if (cond) { g_pass++; } \
else { g_fail++; printf("FAIL %s:%d ", __FILE__, __LINE__); printf(__VA_ARGS__); printf("\n"); } \
} while (0)
#define VW 480
#define VH 720
typedef struct fake_meta {
int n;
int w[16];
int h[16];
int known[16];
} fake_meta;
static int meta_cb(void *ctx, int i, int *w, int *h)
{
fake_meta *m = (fake_meta *)ctx;
if (!m || i < 0 || i >= m->n) return 0;
*w = m->w[i];
*h = m->h[i];
return m->known[i];
}
static void meta_page(fake_meta *m, int i, int w, int h, int known)
{
m->w[i] = w;
m->h[i] = h;
m->known[i] = known;
if (i + 1 > m->n) m->n = i + 1;
}
static void test_single_page(void)
{
fake_meta m;
memset(&m, 0, sizeof m);
meta_page(&m, 0, 800, 1200, 1);
mr_strip s;
memset(&s, 0, sizeof s);
CHECK(mr_strip_build(&s, 1, VW, VH, meta_cb, &m) == 0, "build single");
CHECK(s.count == 1, "count=%d", s.count);
CHECK(mr_strip_page_h(&s, 0) == 720, "single h=%d", mr_strip_page_h(&s, 0));
CHECK(mr_strip_total(&s) == 720, "single total=%d", mr_strip_total(&s));
CHECK(mr_strip_max_offset(&s) == 0, "single max=%d", mr_strip_max_offset(&s));
CHECK(mr_strip_page_at(&s, 0) == 0, "single page at 0");
CHECK(mr_strip_page_at(&s, 719) == 0, "single page at 719");
CHECK(mr_strip_page_at(&s, 720) == 0, "single page at total clamps");
CHECK(mr_strip_at_end(&s, 0) == 1, "single at end");
CHECK(mr_strip_at_start(&s, 0) == 1, "single at start");
int f = -1, l = -1;
CHECK(mr_strip_window(&s, 0, 4, &f, &l) == 1 && f == 0 && l == 0,
"single window %d..%d", f, l);
CHECK(mr_strip_offset_for_page(&s, 0) == 0, "single offset page0");
CHECK(mr_strip_offset_for_page(&s, 9) == 0, "single offset OOB clamps");
mr_strip_free(&s);
}
static void test_multi_pages(void)
{
fake_meta m;
memset(&m, 0, sizeof m);
/* 0: 800x1200 -> 720;1: 未知 -> 720 占位;2: 横图 1500x1000 -> 320;
* 3: 方图 400x400 -> 480;4: 极端长图 1x2000 -> 夹到 200000。 */
meta_page(&m, 0, 800, 1200, 1);
meta_page(&m, 1, 0, 0, 0);
meta_page(&m, 2, 1500, 1000, 1);
meta_page(&m, 3, 400, 400, 1);
meta_page(&m, 4, 1, 2000, 1);
mr_strip s;
memset(&s, 0, sizeof s);
CHECK(mr_strip_build(&s, 5, VW, VH, meta_cb, &m) == 0, "build multi");
CHECK(mr_strip_page_h(&s, 0) == 720, "h0=%d", mr_strip_page_h(&s, 0));
CHECK(mr_strip_page_h(&s, 1) == 720, "h1(unknown)=%d", mr_strip_page_h(&s, 1));
CHECK(mr_strip_page_h(&s, 2) == 320, "h2(landscape)=%d", mr_strip_page_h(&s, 2));
CHECK(mr_strip_page_h(&s, 3) == 480, "h3(square)=%d", mr_strip_page_h(&s, 3));
CHECK(mr_strip_page_h(&s, 4) == MR_STRIP_MAX_PAGE_H, "h4(extreme)=%d",
mr_strip_page_h(&s, 4));
CHECK(mr_strip_top(&s, 0) == 0, "top0=%d", mr_strip_top(&s, 0));
CHECK(mr_strip_top(&s, 1) == 720, "top1=%d", mr_strip_top(&s, 1));
CHECK(mr_strip_top(&s, 2) == 1440, "top2=%d", mr_strip_top(&s, 2));
CHECK(mr_strip_top(&s, 3) == 1760, "top3=%d", mr_strip_top(&s, 3));
CHECK(mr_strip_top(&s, 4) == 2240, "top4=%d", mr_strip_top(&s, 4));
CHECK(mr_strip_top(&s, 5) == 202240, "top[count]=%d", mr_strip_top(&s, 5));
CHECK(mr_strip_total(&s) == 202240, "total=%d", mr_strip_total(&s));
CHECK(mr_strip_max_offset(&s) == 201520, "max=%d", mr_strip_max_offset(&s));
CHECK(mr_strip_page_at(&s, 0) == 0, "page_at 0");
CHECK(mr_strip_page_at(&s, 719) == 0, "page_at 719");
CHECK(mr_strip_page_at(&s, 720) == 1, "page_at 720");
CHECK(mr_strip_page_at(&s, 1439) == 1, "page_at 1439");
CHECK(mr_strip_page_at(&s, 1440) == 2, "page_at 1440");
CHECK(mr_strip_page_at(&s, 1759) == 2, "page_at 1759");
CHECK(mr_strip_page_at(&s, 1760) == 3, "page_at 1760");
CHECK(mr_strip_page_at(&s, 2239) == 3, "page_at 2239");
CHECK(mr_strip_page_at(&s, 2240) == 4, "page_at 2240");
CHECK(mr_strip_page_at(&s, 9999999) == 4, "page_at huge -> last");
CHECK(mr_strip_page_at(&s, -50) == 0, "page_at negative -> first");
CHECK(mr_strip_page_at(&s, 0) == 0 && mr_strip_page_at(NULL, 0) == -1, "page_at NULL");
/* 视口窗口:off=0 只看得到页 0(页 1 从 720 开始)→ ±1 = [0,1] */
int f = -1, l = -1;
CHECK(mr_strip_window(&s, 0, 4, &f, &l) == 2 && f == 0 && l == 1,
"window top %d..%d", f, l);
/* off=1000:可见页 1..2 → 窗口 [0,3] */
CHECK(mr_strip_window(&s, 1000, 4, &f, &l) == 4 && f == 0 && l == 3,
"window mid %d..%d", f, l);
/* off=max:可见页 4 → 窗口 [3,4] */
CHECK(mr_strip_window(&s, 201520, 4, &f, &l) == 2 && f == 3 && l == 4,
"window bottom %d..%d", f, l);
/* 只用 1 个槽位:窗口收缩到焦点页 */
CHECK(mr_strip_window(&s, 1000, 1, &f, &l) == 1 && f >= 1 && f <= 2 && f == l,
"window max_pages=1 %d..%d", f, l);
CHECK(mr_strip_window(&s, -100, 4, &f, &l) == 2 && f == 0 && l == 1,
"window negative off %d..%d", f, l);
CHECK(mr_strip_at_end(&s, 0) == 0, "not at end at 0");
CHECK(mr_strip_at_end(&s, 201519) == 0, "not at end just before max");
CHECK(mr_strip_at_end(&s, 201520) == 1, "at end at max");
CHECK(mr_strip_at_start(&s, 0) == 1, "at start 0");
CHECK(mr_strip_at_start(&s, 1) == 0, "not at start 1");
/* 跳页落位:每页 offset 回读一致(末页夹到 max) */
for (int i = 0; i < 5; i++) {
int off = mr_strip_offset_for_page(&s, i);
int back = mr_strip_page_at(&s, off);
CHECK(back == i, "roundtrip page %d -> off %d -> page %d", i, off, back);
}
CHECK(mr_strip_offset_for_page(&s, 4) == 2240, "last page offset = its top");
/* 短册(内容不足一屏):任何页都夹到 max=0 */
fake_meta m2;
memset(&m2, 0, sizeof m2);
meta_page(&m2, 0, 800, 300, 1); /* 高 180 */
meta_page(&m2, 1, 800, 300, 1);
mr_strip s2;
memset(&s2, 0, sizeof s2);
CHECK(mr_strip_build(&s2, 2, VW, VH, meta_cb, &m2) == 0, "build short");
CHECK(mr_strip_total(&s2) == 360 && mr_strip_max_offset(&s2) == 0, "short totals");
CHECK(mr_strip_offset_for_page(&s2, 1) == 0, "short last page clamps to 0");
CHECK(mr_strip_at_end(&s2, 0) == 1 && mr_strip_at_start(&s2, 0) == 1, "short edges");
mr_strip_free(&s2);
/* 越界参数 */
CHECK(mr_strip_page_h(&s, -1) == 0 && mr_strip_page_h(&s, 5) == 0, "page_h OOB");
CHECK(mr_strip_top(&s, -1) == 0 && mr_strip_top(&s, 6) == 0, "top OOB");
mr_strip_free(&s);
CHECK(s.top == NULL && s.height == NULL && s.count == 0, "free clears");
mr_strip_free(&s);
}
static void test_build_contract(void)
{
fake_meta m;
memset(&m, 0, sizeof m);
meta_page(&m, 0, 800, 1200, 1);
mr_strip s;
memset(&s, 0, sizeof s);
CHECK(mr_strip_build(NULL, 1, VW, VH, meta_cb, &m) == -1, "NULL strip");
CHECK(mr_strip_build(&s, 0, VW, VH, meta_cb, &m) == -1, "count 0 -> -1");
CHECK(s.top == NULL && s.count == 0, "failed build clears");
CHECK(mr_strip_build(&s, 1, 0, VH, meta_cb, &m) == -1, "vw 0 -> -1");
CHECK(mr_strip_build(&s, 1, VW, 0, meta_cb, &m) == -1, "vh 0 -> -1");
/* meta 回调为 NULL → 全部按 2:3 占位 */
CHECK(mr_strip_build(&s, 2, VW, VH, NULL, NULL) == 0, "NULL meta build");
CHECK(mr_strip_page_h(&s, 0) == 720 && mr_strip_page_h(&s, 1) == 720,
"NULL meta fallback heights");
mr_strip_free(&s);
/* meta 返回失败(0)也按占位处理 */
CHECK(mr_strip_build(&s, 1, VW, VH, meta_cb, &m) == 0, "rebuild after free");
CHECK(mr_strip_page_h(&s, 0) == 720, "rebuild height");
mr_strip_free(&s);
/* 空排版查询全部安全 */
mr_strip e;
memset(&e, 0, sizeof e);
CHECK(mr_strip_total(&e) == 0 && mr_strip_max_offset(&e) == 0, "empty totals");
CHECK(mr_strip_page_at(&e, 0) == -1, "empty page_at");
CHECK(mr_strip_offset_for_page(&e, 0) == -1, "empty offset_for_page");
int f = 9, l = 9;
CHECK(mr_strip_window(&e, 0, 4, &f, &l) == 0 && l == -1, "empty window");
CHECK(mr_strip_at_end(&e, 0) == 0 && mr_strip_at_start(&e, 0) == 0, "empty edges");
}
int main(void)
{
test_single_page();
test_multi_pages();
test_build_contract();
printf("%s: %d passed, %d failed\n", g_fail ? "FAIL" : "PASS", g_pass, g_fail);
return g_fail ? 1 : 0;
}