Files
MeowOS-SDK/tuku/logic/reader/page_source.h
T
AskaEth 562cc44931 feat(tuku): CBZ 档案册集成——档案页源/书库列册/解压缓存
- logic/reader/archive_source.c:档案册页源实现 M6 mr_book 契约
  (mz_open/mz_count/mz_entry_at/mz_read_alloc),条目自然序 + 图片后缀 +
  路径安全(`..`/绝对路径/反斜杠/隐藏段/内嵌 NUL)+ 单页尺寸上限过滤;
  meta 按需解压只读图片头(不落缓存,结果按页记忆);mr_book_open_auto
  统一目录册/档案册开册,翻页与条漫两套消费代码零改动。
- logic/reader/page_cache.c/h:解压页落 MeowData/<pkg>/cache,LRU 64 页或
  32MiB,启动清 mrpage_* 孤儿,写入 .part + rename;与缩略图缓存同目录、
  前缀隔离;互斥量保护,解码/缩略图线程可并发。
- logic/library.c/h:扫描直接子文件里的 .cbz/.zip 成册(行=文件名+页数,
  点入直接阅读);档案探测由调用方注入(logic 不依赖 zip 容器),不注入时
  与 M7 行为逐字一致;册间衔接兄弟表=含图目录∪含页档案册,同列自然序。
  条目构造/副标题/角标/兄弟判据拆成小函数,便于 M13 书库重写拼接。
- ui/library:档案册行 + 封面缩略图(解压档内首图)、扫描注入探测、缓存
  目录统一由 page_cache 推导(一份规则)。
- ui/reader:open_book 走 mr_book_open_auto;跨册衔接纳入档案册。
- build_tuku.sh 补 zip/archive/cache 源与 sysroot zlib 头(zip.c include
  <zlib.h>);run_tests.sh 纳入 zip+cbz 用例;run_webtoon_tests.sh 的宿主
  集成构建补新源与 -lz;.gitignore 同步测试产物。
- test/test_cbz.c(267 项断言)+ make_cbz_samples.py 真实 cbz 夹具:
  列档/自然序/解压字节 CRC/路径安全/坏档与 zip 炸弹容错/LRU 与 32MiB 上限/
  启动清孤儿/续读角标/封面首图。
2026-11-05 11:58:12 +08:00

170 lines
8.8 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/page_source.h — 页源契约(纯 C,不依赖 meow_ui.h)。
*
* 一册 = 有序页序列。契约只有四件事:
* count 总页数
* meta(i) 页的原始像素尺寸(可选;条漫连续滚动先用它排版,未知时返回 0)
* open(i) 取第 i 页:解码 + downscale 后返回 meow_img*(调用方拥有,必须 close)
* close(img) 释放 open 返回的图像
*
* 两种消费方式共用同一契约,后续实现零改动:
* - 左右翻页(本任务):预解码窗口 = 当前 ±1 页,翻页只 blit 窗口内已备好的图;
* - 条漫连续滚动(M7):先 meta(i) 累加高度排版,再按滚动位置 open(视口±1 页)。
* zip 册(M9/M11)只需另写一套 mr_book_ops,目录册是第一个实现。
*
* meow_img 在 logic 层是前向声明的不透明句柄;解码/释放由 UI 层注入回调
* (logic 不 include SDK,宿主测试用假回调即可覆盖契约边界)。
*/
#ifndef MR_PAGE_SOURCE_H
#define MR_PAGE_SOURCE_H
#include "logic/library.h" /* mr_path_is_image / mr_natural_cmp / MR_PATH_MAX / MR_NAME_MAX */
#include <stddef.h> /* size_t(mr_probe_size_mem) */
#ifdef __cplusplus
extern "C" {
#endif
struct meow_img;
typedef struct meow_img meow_img; /* SDK 不透明句柄;logic 层只透传 */
#define MR_BOOK_MAX_PAGES 4096 /* 单册页数上限(防坏卡/巨目录撑爆内存) */
/* 解码回调(UI 层注入):解码 path,长边 >maxdim 时等比 downscale;失败返回 NULL。
* 必须可在后台线程调用(宿主测试用假实现)。 */
typedef meow_img *(*mr_book_load_fn)(const char *path, void *ud, int maxdim);
/* 释放 open 返回的图像(UI 层注入 meow_img_free;宿主测试用假实现计数)。 */
typedef void (*mr_book_unload_fn)(meow_img *img);
typedef struct mr_book_ops {
int (*count)(void *ctx);
/* 页原始尺寸(未 downscale);1=已知,0=未知。可为 NULL。 */
int (*meta)(void *ctx, int index, int *w, int *h);
/* 取页:越界/解码失败返回 NULL。返回值的所有权交给调用方。 */
meow_img *(*open)(void *ctx, int index);
/* 释放 open 返回的图像;须容忍 NULL。 */
void (*close)(void *ctx, meow_img *img);
/* 释放 provider 自身(册内索引等)。可为 NULL。 */
void (*destroy)(void *ctx);
} mr_book_ops;
typedef struct mr_book {
const mr_book_ops *ops;
void *ctx;
} mr_book;
/* ---- 契约包装:所有入口对 NULL/越界安全(越界 open 返回 NULL) ---- */
int mr_book_valid(const mr_book *b);
int mr_book_count(const mr_book *b);
int mr_book_meta(const mr_book *b, int index, int *w, int *h);
meow_img *mr_book_open(mr_book *b, int index);
void mr_book_close(mr_book *b, meow_img *img);
/* 释放 provider 自身(不含已 open 的图像——那些由调用方先 close)。幂等。 */
void mr_book_destroy(mr_book *b);
/* ---- 目录册(文件夹即书):目录直接子文件里的图片按自然序成册 ---- */
typedef struct mr_dir_book {
char dir[MR_PATH_MAX];
char **names; /* 自然序文件名(堆分配) */
int count;
int maxdim; /* open 时传给解码回调的长边上限 */
mr_book_load_fn load;
mr_book_unload_fn unload;
void *ud; /* 解码回调的宿主上下文 */
} mr_dir_book;
/* 扫描 dir 的直接子文件,收图片名并自然排序。
* 返回页数(>=0);参数非法 / 目录不可读 / 内存不足返回 -1(db 已清空、可安全 free)。 */
int mr_dir_book_init(mr_dir_book *db, const char *dir, int maxdim,
mr_book_load_fn load, mr_book_unload_fn unload, void *ud);
void mr_dir_book_free(mr_dir_book *db);
/* 第 index 页的完整路径;越界时输出空串。 */
void mr_dir_book_path(const mr_dir_book *db, int index, char *out, int outsz);
/* 把目录册绑定到通用 mr_book(db 生命周期由调用方持有)。 */
void mr_dir_book_bind(mr_book *b, mr_dir_book *db);
/* 纯 C 头解析:取图片原始像素尺寸(不解码像素)。
* PNG(IHDR)/ JPEG(SOF0-15)/ BMP(BITMAPINFOHEADER+)。1=已知,0=未知。 */
int mr_probe_size(const char *path, int *w, int *h);
/* 同上,但从内存缓冲探测(档案页解压后不需要落盘再读一遍)。 */
int mr_probe_size_mem(const void *buf, size_t len, int *w, int *h);
/* ============================================================ */
/* 档案册(M11):.cbz / .zip 当一本书 */
/* ------------------------------------------------------------
* 目录册之外的第二种 mr_book 实现,契约完全相同。条目名按 zip 原始字节
* 透传(通常 UTF-8):只收「图片后缀 + 路径安全 + 非隐藏 + 非目录」的条目,
* 跨子目录按 mr_natural_cmp 自然排序。open(i) 先把条目解压进
* logic/reader/page_cache(磁盘 LRU),再用注入的解码回调按路径解码——
* 与目录册走同一条 UI 解码/预算路径,翻页与条漫两种模式零改动复用。
*
* 条目名永远不会被拼成文件系统路径(缓存文件名是哈希),路径安全过滤是
* 纵深防御:拒绝绝对路径、'..' 段、反斜杠与内嵌 NUL。
* ============================================================ */
#define MR_ZIP_MAX_ENTRY_NAME 1024 /* 超长条目名跳过(防病态包) */
#define MR_ZIP_PAGE_MAX_BYTES (64LL * 1024 * 1024) /* 单页解压上限(防 zip 炸弹) */
typedef struct mr_zip_book {
char path[MR_PATH_MAX];
void *arch; /* mz_archive*(zip 层句柄;不对外暴露) */
void *lock; /* pthread_mutex_t*:mz_read* 串行化(open/meta 共用) */
int *order; /* 页 i → zip 条目下标(堆分配) */
int *meta_w; /* 页原始宽/高(按需解压头探测;NULL=未探测) */
int *meta_h;
int count;
int maxdim;
long long mtime; /* 档案 mtime/size:解压缓存键的一部分 */
long long size;
mr_book_load_fn load; /* 可为 NULL(只取封面/页路径时不需要解码) */
mr_book_unload_fn unload;
void *ud;
} mr_zip_book;
/* 打开档案并建立页表。返回页数(>=0);参数非法/坏档/空档案不可读返回 -1
* (zb 已清空、可安全 free)。0 页的合法档案返回 0(zb 仍持有打开的 archive)。 */
int mr_zip_book_init(mr_zip_book *zb, const char *path, int maxdim,
mr_book_load_fn load, mr_book_unload_fn unload, void *ud);
void mr_zip_book_free(mr_zip_book *zb);
/* 把档案册绑定到通用 mr_book(zb 生命周期由调用方持有)。 */
void mr_zip_book_bind(mr_book *b, mr_zip_book *zb);
/* 第 index 页在 zip 内的条目名(原始字节);越界输出空串。 */
void mr_zip_book_page_name(const mr_zip_book *zb, int index, char *out, int outsz);
/* 确保第 index 页已解压到缓存并返回缓存文件路径;越界/坏档/缓存不可用 -1。 */
int mr_zip_book_page_path(mr_zip_book *zb, int index, char *out, int outsz);
/* 只数页(书库扫描/邻册判断用;不解压、不建缓存)。0=不是档案或没有图片页。 */
int mr_zip_count_pages(const char *path);
/* 档案封面:档内自然序第一张图解到缓存,路径写入 out。1=成功。 */
int mr_zip_cover_path(const char *path, char *out, int outsz);
/* 条目是否算一页(图片后缀 + 路径安全 + 非隐藏 + 非目录)。1=是。 */
int mr_zip_entry_is_page(const char *name, unsigned name_len);
/* ============================================================ */
/* 统一开册(目录册 / 档案册) */
/* ------------------------------------------------------------
* 书库点入的行既可能是目录也可能是 .cbz/.zip;阅读器只调这里,不关心 provider。
* 句柄由调用方提供(阅读器用静态变量),生命周期 = 打开到 close_auto。
* ============================================================ */
#define MR_BOOK_KIND_NONE 0
#define MR_BOOK_KIND_DIR 1
#define MR_BOOK_KIND_ZIP 2
typedef struct mr_book_handle {
int kind;
union {
mr_dir_book dir;
mr_zip_book zip;
} u;
} mr_book_handle;
/* 按路径自动选 provider 并绑定 b。返回页数(>=0)或 -1(打不开)。
* 页数为 0 时 b 不绑定(空册),句柄仍可安全 mr_book_close_auto。 */
int mr_book_open_auto(mr_book *b, mr_book_handle *h, const char *path, int maxdim,
mr_book_load_fn load, mr_book_unload_fn unload, void *ud);
/* 释放 provider(先 mr_book_destroy 再释放各自索引);可重复调用。 */
void mr_book_close_auto(mr_book *b, mr_book_handle *h);
#ifdef __cplusplus
}
#endif
#endif /* MR_PAGE_SOURCE_H */