Files
MeowOS-SDK/tuku/logic/reader/nav.h
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

130 lines
6.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.
/* logic/reader/nav.h — 阅读导航纯逻辑(方向语义 / tap 分区 / fit / 平移 clamp)。
*
* 纯 C、无 SDK 依赖:reader.c 与宿主测试共用同一实现(logic/reader/nav.c)。
*
* 方向语义(mr_reader_get_direction,默认 RTL):
* - LTR(左→右读):页码沿屏幕向右递增;屏左 1/3 = 上一页,屏右 1/3 = 下一页;
* 手指向右拖露出左边那页 = 上一页。
* - RTL(日漫,默认):页码沿屏幕向左递增;屏左 1/3 = **下一页**,屏右 1/3 = 上一页;
* 手指向右拖露出左边那页 = 下一页。
* 图像本身不做镜像(RTL 只影响翻页语义)。
*/
#ifndef MR_READER_NAV_H
#define MR_READER_NAV_H
#ifdef __cplusplus
extern "C" {
#endif
/* ---- fit 三档 ---- */
#define MR_FIT_WIDTH 0 /* 适宽:撑满屏宽 */
#define MR_FIT_HEIGHT 1 /* 适高:撑满屏高 */
#define MR_FIT_ORIGINAL 2 /* 原始:1:1 像素 */
#define MR_FIT_COUNT 3
/* ---- tap 分区(按屏幕横向三分) ---- */
#define MR_ZONE_LEFT 0
#define MR_ZONE_MIDDLE 1
#define MR_ZONE_RIGHT 2
/* 屏宽三分:左/中/右。view_w<=0 返回 MIDDLE。 */
int mr_nav_tap_zone(int x, int view_w);
/* tap 分区的页增量:+1=下一页 / -1=上一页 / 0=chrome 显隐(中区)。 */
int mr_nav_tap_delta(int rtl, int zone);
/* 中部横向拖动的页增量。dx>0 = 手指向右(露出屏幕左侧那页)。
* 首/末页不在这里判断——调用方用 mr_nav_clamp_page 落到实际目标页。 */
int mr_nav_swipe_delta(int rtl, int dx);
/* 页下标 clamp;count<=0 返回 -1。 */
int mr_nav_clamp_page(int page, int count);
/* 翻页提交阈值:视口宽的 1/4,夹在 [60,160] px。 */
int mr_nav_commit_dist(int view_w);
/* 松手位移是否达到翻页阈值。 */
int mr_nav_commit(int dx, int view_w);
/* fit 目标尺寸(未含 zoom)。0=成功;参数非法 -1。 */
int mr_nav_fit_size(int fit, int src_w, int src_h, int vw, int vh, int *out_w, int *out_h);
/* 按百分比放大(zoom_pct=100 原样;200=2×)。0=成功。 */
int mr_nav_zoom_size(int w, int h, int zoom_pct, int *out_w, int *out_h);
/* 平移半幅:内容比视口每侧多出的像素(>=0)。内容不溢出该轴 → 0(该轴不可平移)。 */
void mr_nav_pan_limit(int content_w, int content_h, int vw, int vh, int *lim_x, int *lim_y);
/* 把平移量夹到 ±半幅。 */
void mr_nav_pan_clamp(int content_w, int content_h, int vw, int vh, int *pan_x, int *pan_y);
/* 翻页动画的进场起点(px):下一页在 RTL 从左侧进、LTR 从右侧进。delta=0 返回 0。 */
int mr_nav_slide_from(int rtl, int delta, int view_w);
/* fit 档循环:适宽 → 适高 → 原始 → 适宽。 */
int mr_nav_fit_next(int fit);
/* ============================================================ */
/* 条漫(M7):竖向连续滚动的排版 / 滚动窗口 / 跳页状态 */
/* ------------------------------------------------------------
* 排版用页源契约的 meta()(原始像素尺寸,不解码):每页按屏宽等比换算显示高度,
* 累加成「整条」的 y 偏移表。未知尺寸的页按 2:3 竖图占位(排版稳定,解码后不改布局)。
* 全部纯整数运算、无 SDK 依赖;reader.c 与宿主测试共用(logic/reader/nav.c)。
* ============================================================ */
#define MR_STRIP_MAX_PAGE_H 200000 /* 单页显示高度上限(防坏头/极端长图撑爆累计偏移) */
typedef struct mr_strip {
int count;
int vw, vh;
int *top; /* count+1 个:top[i]=第 i 页顶端偏移;top[count]=整条总高 */
int *height; /* count 个:每页显示高度(>=1) */
int total;
} mr_strip;
/* 页尺寸回调(通常是 mr_book_meta):1=已知原始尺寸,0=未知。 */
typedef int (*mr_strip_meta_fn)(void *ctx, int index, int *w, int *h);
/* 建排版表。0=成功;参数非法/内存不足 -1(已清空、可安全 free)。 */
int mr_strip_build(mr_strip *s, int count, int vw, int vh,
mr_strip_meta_fn meta, void *ctx);
/* 释放偏移表并清零(可重复调用)。 */
void mr_strip_free(mr_strip *s);
int mr_strip_page_h(const mr_strip *s, int index); /* 越界 0 */
int mr_strip_top(const mr_strip *s, int index); /* index==count 返回总高;越界 0 */
int mr_strip_total(const mr_strip *s);
int mr_strip_max_offset(const mr_strip *s); /* max(0, total-vh) */
/* 视口顶端(off)落在哪一页;空册 -1。 */
int mr_strip_page_at(const mr_strip *s, int off);
/* 落位到某页顶端所需的滚动量(夹到 [0,max_offset]);空册 -1。 */
int mr_strip_offset_for_page(const mr_strip *s, int page);
/* 视口可见页范围 [first,last] + ±1 解码窗,条数不超过 max_pages。
* 返回窗口页数;空册 0。first/last 可 NULL。 */
int mr_strip_window(const mr_strip *s, int off, int max_pages,
int *first, int *last);
int mr_strip_at_end(const mr_strip *s, int off); /* 1=已到条底 */
int mr_strip_at_start(const mr_strip *s, int off); /* 1=已在条顶 */
/* ---- 跳页(chrome 滑块):目标页预解码完成后再落位,避免白屏/闪烁 ---- */
typedef struct mr_jump {
int pending; /* 1=正在等目标页 */
int target; /* 目标页;pending==0 时无意义 */
unsigned long long t0; /* 开始等待的时刻(ms) */
} mr_jump;
/* 开始跳页:page 夹到 [0,count);count<=0 或 page<0 时取消。 */
void mr_jump_begin(mr_jump *j, int page, int count, unsigned long long now);
int mr_jump_active(const mr_jump *j);
int mr_jump_target(const mr_jump *j); /* 无 pending 返回 -1 */
void mr_jump_cancel(mr_jump *j);
/* 轮询:have_img=目标页是否已解码。1=可以落位(已清除 pending);
* timed_out 非 NULL 时写入「是否因超时落位」(调用方据此提示降级)。
* 未就绪且未超时返回 0(继续等)。 */
int mr_jump_poll(mr_jump *j, int have_img, unsigned long long now,
unsigned long long timeout_ms, int *timed_out);
#ifdef __cplusplus
}
#endif
#endif /* MR_READER_NAV_H */