reader(R2): EPUB/FB2/MOBI/CBZ 加载器落地

- rdsrc 统一文档源抽象:TXT/FB2/EPUB/MOBI/CBZ 都产出「UTF-8 段序列+目录+元数据+图片」,
  与 R1 rdtxt 段契约一致;book.c 改喂 rd_src(文本)或图页(CBZ)。
- EPUB:miniz 解 ZIP → 自写 rdxml 回调解析 OPF(spine/manifest)+NCX/nav → 正文 XHTML 用
  rdhtml 逐行粗解析(坏标签/未闭合/实体容错,不建 DOM),章节按 RDSRC_SEG_MAX 分片。
- FB2:rdxml 严格解析抽 section 段落 + base64 内嵌图;段题进正文+目录。
- MOBI/AZW3:vendor libmobi 最小调用面(mobi_init→load→parse_kf7/8→parse_rawml_opt→
  flow/resources),HTML 走 rdhtml 同款粗解析;LGPL-3.0 合规见 NOTICE(版权+全文+锁定 commit)。
- CBZ:miniz 解 ZIP → 自然序图片序列,rdimg(stb_image) 解码+降采样,阅读页图页模式。
- 目录显示与跳转(EPUB NCX/FB2 section/MOBI 标题);书架开放四格式;进度按
  (段,字符偏移)(TXT/FB2/EPUB/MOBI)/页号(CBZ)。
- 宿主单测 loaders_smoke(四格式抽取/目录/图片/base64/页序)+ qemu-mipsel
  formats_smoke/book_smoke;R1 test_reader_core 58/0 不回归。
- build_reader.sh 集成 miniz/libmobi/stb/zlib 头;NOTICE 入仓;交叉 -Wall -Wextra 0 warning。

新 reader md5: e29036599300eb277b1c960a75b65dbf
This commit is contained in:
AskaEth committed 2026-09-02 03:11:55 +08:00
1 parent 10690bc348
commit 0294553096
27 files changed
+3440 -193

No files matched your search

+68 -12
View File
@@ -17,7 +17,7 @@
#include <time.h>
#define RD_MENU_H 64
#define RD_MENU_BTN 5
#define RD_MENU_BTN 6
static rd_book *g_book;
static int g_reader_active;
@@ -29,6 +29,8 @@ static int g_fs, g_ls;
static uint16_t *g_page_buf[RD_PAGE_CACHE]; /* [0]=prev [1]=cur [2]=next(FreeType 页位图) */
static int g_has_font; /* 1=FreeType 路径;0=SDK 字库兜底 */
static int g_menu_open;
static int g_dir_open; /* 目录覆盖层 */
static int g_dir_top, g_dir_row_h; /* 目录列表几何(rdsrc 目录项) */
static char g_font_path[400];
static int g_notice_ms;
@@ -125,7 +127,7 @@ static void reader_canvas_render(meow_widget *w, const meow_theme *th, void *ctx
meow_ui_clear(g_page_bg);
if (g_book) {
int page = rd_book_current(g_book);
if (g_has_font && g_page_buf[RD_PAGE_SLOT_CUR]) {
if ((g_has_font || rd_book_is_image(g_book)) && g_page_buf[RD_PAGE_SLOT_CUR]) {
/* 翻页 blit:行拷贝当前页位图 → back_buf(局部脏区由 flush_region 上屏) */
unsigned char *bb = meow_ui_back_buf();
int pitch = meow_ui_back_buf_pitch();
@@ -154,15 +156,41 @@ static void reader_canvas_render(meow_widget *w, const meow_theme *th, void *ctx
if (g_menu_open) {
int y = g_ph - RD_MENU_H;
meow_ui_fill_rect_alpha(0, y, g_pw, RD_MENU_H, 0x000000, 160);
/* 5 按钮:返回 | 字号− | 字号+ | 行距− | 行距+ */
/* 6 按钮:返回 | 目录 | 字− | 字+ | 距− | 距+ */
int bw = g_pw / RD_MENU_BTN;
const char *labels[RD_MENU_BTN] = { "返回", "字-", "字+", "距-", "距+" };
const char *labels[RD_MENU_BTN] = { "返回", "目录", "字-", "字+", "距-", "距+" };
for (int i = 0; i < RD_MENU_BTN; i++) {
int bx = i * bw;
meow_ui_fill_rect(bx + 4, y + 12, bw - 8, RD_MENU_H - 24, th->surfaceContainer);
meow_ui_text_center(bx + bw / 2, y + (RD_MENU_H - 20) / 2 - 6, labels[i], 20, th->fg);
}
}
if (g_dir_open && g_book) {
/* 目录覆盖层:半透明底 + 条目列表(当前行高亮) */
int n = rd_book_toc_n(g_book);
if (n > 0) {
int top = g_top + 8;
int row = 34;
int maxshow = (g_ph - top - 70) / row;
if (maxshow < 1) maxshow = 1;
if (maxshow > n) maxshow = n;
g_dir_top = top; g_dir_row_h = row;
meow_ui_fill_rect_alpha(0, g_top, g_pw, g_ph - g_top, 0x000000, 150);
char t[96]; int sg; int32_t cch;
int page = rd_book_current(g_book);
for (int i = 0; i < maxshow; i++) {
if (rd_book_toc(g_book, i, t, sizeof t, &sg, &cch) != 0) continue;
int y0 = top + i * row;
if (page == sg) meow_ui_fill_rect(0, y0, g_pw, row - 4, th->surfaceContainer);
meow_ui_text(24, y0, t, 20, th->fg);
meow_ui_draw_rect(12, y0 + row - 6, g_pw - 24, 1, th->outline);
}
meow_ui_text(20, g_ph - 34, "点条目跳转 · 点空白关闭", 16, th->dim);
} else {
g_dir_open = 0;
meow_ui_text(20, g_top + 40, "本书无目录", 20, th->dim);
}
}
}
static void reader_redraw(void)
@@ -181,7 +209,7 @@ static void reader_free_page_bufs(void)
static int reader_refill_page(void)
{
if (!g_book) return -1;
if (g_has_font && g_page_buf[RD_PAGE_SLOT_CUR]) {
if ((g_has_font || rd_book_is_image(g_book)) && g_page_buf[RD_PAGE_SLOT_CUR]) {
uint16_t bg = rgb565(g_page_bg), fg = rgb565(g_page_fg);
int cur = rd_book_current(g_book);
int np = rd_book_pages(g_book);
@@ -230,7 +258,7 @@ void reader_open(const char *path)
}
chapter_name();
if (g_has_font) {
if (g_has_font || rd_book_is_image(g_book)) {
reader_free_page_bufs();
for (int i = 0; i < RD_PAGE_CACHE; i++)
g_page_buf[i] = (uint16_t *)malloc((size_t)g_pw * (size_t)g_ph * 2);
@@ -244,7 +272,7 @@ void reader_open(const char *path)
reader_refill_page();
g_menu_open = 0;
g_menu_open = 0; g_dir_open = 0;
if (g_reader_active) reader_redraw();
}
@@ -269,7 +297,7 @@ static void reader_on_destroy(meow_view *v, void *ctx)
if (g_book) rd_book_close(g_book);
g_book = NULL;
reader_free_page_bufs();
g_menu_open = 0; g_reader_active = 0;
g_menu_open = 0; g_dir_open = 0; g_reader_active = 0;
}
meow_view *reader_view_create(void)
@@ -287,6 +315,30 @@ int reader_is_active(void) { return g_reader_active; }
void reader_on_tap(int x, int y)
{
if (!g_book) { return; }
if (g_dir_open) {
/* 目录覆盖层:点条目跳转,点空白关闭 */
int n = rd_book_toc_n(g_book);
if (n > 0 && x >= 0 && y >= g_dir_top) {
int row = g_dir_row_h > 0 ? g_dir_row_h : 34;
int idx = (y - g_dir_top) / row;
if (idx < n && idx >= 0) {
char t[96]; int sg; int32_t cch;
if (rd_book_toc(g_book, idx, t, sizeof t, &sg, &cch) == 0) {
if (g_book && sg >= 0) {
rd_book_jump_to_anchor(g_book, sg, cch, g_fs, g_ls);
g_dir_open = 0; g_menu_open = 0;
snprintf(g_chapter, sizeof g_chapter, "%s", t);
reader_refill_page();
reader_redraw();
reader_reflow_persist();
return;
}
}
}
}
g_dir_open = 0; reader_redraw();
return;
}
if (g_menu_open) {
/* 菜单按钮命中 */
int y0 = g_ph - RD_MENU_H;
@@ -299,10 +351,14 @@ void reader_on_tap(int x, int y)
reader_reflow_persist();
meow_view_page_pop();
return;
case 1: if (g_fs > 14) { g_fs -= 2; reader_reflow(); } break;
case 2: if (g_fs < 40) { g_fs += 2; reader_reflow(); } break;
case 3: if (g_ls > 100) { g_ls -= 5; reader_reflow(); } break;
case 4: if (g_ls < 200) { g_ls += 5; reader_reflow(); } break;
case 1: /* 目录 */
if (rd_book_toc_n(g_book) > 0) { g_dir_open = 1; g_menu_open = 0; reader_redraw(); }
else { g_menu_open = 0; reader_redraw(); }
return;
case 2: if (g_fs > 14) { g_fs -= 2; reader_reflow(); } break;
case 3: if (g_fs < 40) { g_fs += 2; reader_reflow(); } break;
case 4: if (g_ls > 100) { g_ls -= 5; reader_reflow(); } break;
case 5: if (g_ls < 200) { g_ls += 5; reader_reflow(); } break;
}
if (y >= y0) return;
}