- 条漫: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 一键运行
221 lines
9.1 KiB
C
221 lines
9.1 KiB
C
/* 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;
|
||
}
|