Files
MeowOS-For-R3P2/patch_v2/reader_app/app/reader.c
T
AskaEth 0294553096 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
2026-09-02 03:11:55 +08:00

391 lines
15 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.
/* app/reader.c — 阅读页:W_CUSTOM 画布 + 三栏交互 + 中央菜单占位 + 顶栏进度/章节名 + 进度持久化。
*
* 渲染:W_CUSTOM 画布覆盖全屏;render_cb 画帧 = 填底色 → 翻页 blit(FreeType 页位图缓存 /
* 无字体则 SDK 位图字库兜底直绘)→ 顶栏 → 菜单覆盖层。翻页 = 局部脏区(flush_region)。
* 交互:点击左/右 1/3 翻页;中央唤菜单(字号±/行距±/返回);左滑=下一页、右滑=上一页。
* 进度:打开自动续读;换字号重排回锚(段,字符偏移);离开/翻页时保存。
*/
#include "../reader_internal.h"
#include "meow_ui_internal.h" /* back_buf/pitch/bpp 访问器(决策见报告) */
#include "book.h"
#include "progress.h"
#include <dirent.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <time.h>
#define RD_MENU_H 64
#define RD_MENU_BTN 6
static rd_book *g_book;
static int g_reader_active;
static meow_widget *g_canvas;
static int g_pw, g_ph, g_top, g_mx, g_my;
static int g_fs, g_ls;
#define RD_PAGE_CACHE 3
#define RD_PAGE_SLOT_CUR 1
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;
static uint32_t g_page_bg, g_page_fg; /* 正文页底/字色(24bit) */
static char g_chapter[128]; /* 顶栏章节名(TXT 无章节=文件名) */
#define RD_READER_DEFAULT_FS 20
#define RD_READER_DEFAULT_LS 130
/* 前向声明(reader_reflow* 依赖 reader_refill/redraw,定义在后) */
static void reader_redraw(void);
static int reader_refill_page(void);
static void reader_reflow_persist(void);
/* ---------- 工具 ---------- */
static uint16_t rgb565(uint32_t c)
{
int r = (c >> 16) & 0xFF, g = (c >> 8) & 0xFF, b = c & 0xFF;
return (uint16_t)(((r >> 3) << 11) | ((g >> 2) << 5) | (b >> 3));
}
/* 递归(深度≤3)扫 MeowOS/fonts/ 找第一个 .otf/.ttf。0=找到。 */
static int find_font_rec(const char *dir, int depth)
{
if (depth > 3) return 0;
DIR *dp = opendir(dir);
if (!dp) return 0;
struct dirent *de;
int found = 0;
while ((de = readdir(dp)) && !found) {
if (de->d_name[0] == '.') continue;
char full[480];
if (snprintf(full, sizeof full, "%s/%s", dir, de->d_name) >= (int)sizeof full) continue;
struct stat st;
if (stat(full, &st) != 0) continue;
if (S_ISDIR(st.st_mode)) { found = find_font_rec(full, depth + 1); }
else {
const char *d = strrchr(de->d_name, '.');
if (d && (strcasecmp(d, ".otf") == 0 || strcasecmp(d, ".ttf") == 0)) {
if (strlen(full) < sizeof g_font_path) {
strcpy(g_font_path, full);
found = 1;
}
}
}
}
closedir(dp);
return found;
}
static const char *resolve_font(void)
{
g_font_path[0] = 0;
if (find_font_rec(READER_SD_ROOT "/MeowOS/fonts", 0)) return g_font_path;
return NULL;
}
static void chapter_name(void)
{
/* TXT 无章节 → 文件名;R1 固定。 */
snprintf(g_chapter, sizeof g_chapter, "%s", rd_book_basename(g_book));
}
static void reader_reflow(void)
{
if (!g_book) return;
int seg = 0; int32_t ch = 0;
rd_book_anchor(g_book, &seg, &ch); /* 记录当前锚,重排后回锚 */
rd_book_jump_to_anchor(g_book, seg, ch, g_fs, g_ls);
reader_refill_page();
reader_redraw();
reader_reflow_persist();
}
static void reader_reflow_persist(void)
{
if (!g_book) return;
int seg = 0; int32_t ch = 0;
rd_book_anchor(g_book, &seg, &ch);
rdprog p;
p.valid = 1; p.seg = seg; p.ch = ch; p.fs = g_fs; p.ls = g_ls; p.ts = (uint64_t)time(NULL);
rdprog_save(rd_book_path(g_book), &p);
}
/* ---------- 渲染(W_CUSTOM render_cb) ---------- */
static void reader_draw_line_atlas(int x, int y, const char *utf8, int len, void *ctx)
{
(void)ctx; (void)len;
meow_ui_text(x, y, utf8, (g_fs <= 20) ? 20 : 28, g_page_fg);
}
static void reader_canvas_render(meow_widget *w, const meow_theme *th, void *ctx)
{
(void)w; (void)ctx;
meow_ui_clear(g_page_bg);
if (g_book) {
int page = rd_book_current(g_book);
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();
int bpp = meow_ui_back_buf_bpp();
uint16_t *src = g_page_buf[RD_PAGE_SLOT_CUR];
if (bb && pitch > 0 && bpp == 16) {
for (int y = 0; y < g_ph; y++)
memcpy(bb + (size_t)y * pitch, src + (size_t)y * g_pw, (size_t)g_pw * 2);
}
} else {
rd_book_render_text(g_book, page, reader_draw_line_atlas, NULL);
}
}
/* 顶栏:进度% + 章节名(避开系统状态栏) */
int sbh = meow_ui_statusbar_height();
int pages = rd_book_pages(g_book);
int page = rd_book_current(g_book);
char pct[16];
int p = pages > 0 ? (page * 100 / pages) : 0;
snprintf(pct, sizeof pct, "%d%%", p);
meow_ui_fill_rect(0, sbh, g_pw, (g_top - 4 - sbh), g_page_bg);
meow_ui_text(16, sbh + 4, g_chapter[0] ? g_chapter : "阅读", 20, th->fg);
meow_ui_text_right(g_pw - 16, sbh + 4, pct, 20, th->fg);
meow_ui_draw_rect(0, g_top - 5, g_pw, 1, th->outline);
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);
/* 6 按钮:返回 | 目录 | 字− | 字+ | 距− | 距+ */
int bw = g_pw / 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)
{
if (!g_canvas) return;
meow_widget_draw(g_canvas); /* → render_cb 画帧 */
meow_ui_flush_region(0, 0, g_pw, g_ph);
}
static void reader_free_page_bufs(void)
{
for (int i = 0; i < RD_PAGE_CACHE; i++) { free(g_page_buf[i]); g_page_buf[i] = NULL; }
}
/* 渲染当前页 + 前后各一页到 3 槽缓存(对齐设计「当前±前±后」;动画档为 无/滑动/淡入 R1 默认无)。 */
static int reader_refill_page(void)
{
if (!g_book) return -1;
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);
int slots[RD_PAGE_CACHE] = { cur - 1, cur, cur + 1 };
for (int i = 0; i < RD_PAGE_CACHE; i++) {
int p = slots[i];
if (p >= 0 && p < np) rd_book_render(g_book, p, g_page_buf[i], g_pw * 2, bg, fg);
else for (int y = 0; y < g_ph; y++) /* 空白槽填背景 */
for (int x = 0; x < g_pw; x++) g_page_buf[i][(size_t)y * g_pw + x] = bg;
}
return 0;
}
return 0; /* 兜底路径:render_text 直绘 */
}
/* ---------- 打开 ---------- */
void reader_open(const char *path)
{
if (!path || !path[0]) return;
if (g_book) { rd_book_close(g_book); g_book = NULL; }
reader_free_page_bufs();
const char *fp = resolve_font();
g_has_font = (fp != NULL);
g_fs = RD_READER_DEFAULT_FS; g_ls = RD_READER_DEFAULT_LS;
/* 进度(含字号/行距偏好) */
rdprog prog;
int hasprog = (rdprog_load(path, &prog) == 1) && prog.valid;
if (hasprog) { if (prog.fs >= 14 && prog.fs <= 40) g_fs = prog.fs; if (prog.ls >= 100 && prog.ls <= 200) g_ls = prog.ls; }
int sbh = meow_ui_statusbar_height();
g_pw = meow_ui_width(); g_ph = meow_ui_height();
g_top = sbh + 34; g_mx = 16; g_my = 20;
const meow_theme *th = meow_theme_get();
g_page_bg = th->surface; g_page_fg = th->onSurface;
/* 开书(FreeType 路径带齐几何;无字体则 g_has_font=0 → atlas 兜底) */
int rc = rd_book_open(&g_book, path, fp, g_fs, g_ls, g_pw, g_ph, g_mx, g_my, g_top);
if (rc != 0) {
meow_log(MEOW_LOG_WARN, "book open failed rc=%d path=%s", rc, path);
g_book = NULL; g_has_font = 0;
g_notice_ms = 3000;
return;
}
chapter_name();
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);
if (!g_page_buf[RD_PAGE_SLOT_CUR]) { reader_free_page_bufs(); g_has_font = 0; }
}
/* 续读:进度锚(段,字符偏移)→ 跳页 */
if (hasprog && g_book) {
rd_book_jump_to_anchor(g_book, prog.seg, prog.ch, g_fs, g_ls);
}
reader_refill_page();
g_menu_open = 0; g_dir_open = 0;
if (g_reader_active) reader_redraw();
}
/* ---------- 视图生命周期 ---------- */
static void reader_on_create(meow_view *v, void *ctx) { (void)v; (void)ctx; }
static void reader_on_enter(meow_view *v, void *ctx)
{
(void)v; (void)ctx;
g_reader_active = 1;
if (g_book) { reader_refill_page(); chapter_name(); }
reader_redraw();
}
static void reader_on_leave(meow_view *v, void *ctx)
{
(void)v; (void)ctx;
reader_reflow_persist(); /* 离开保存进度 */
g_reader_active = 0;
}
static void reader_on_destroy(meow_view *v, void *ctx)
{
(void)v; (void)ctx;
if (g_book) rd_book_close(g_book);
g_book = NULL;
reader_free_page_bufs();
g_menu_open = 0; g_dir_open = 0; g_reader_active = 0;
}
meow_view *reader_view_create(void)
{
static meow_view_hooks hooks = { reader_on_create, reader_on_enter, reader_on_leave,
reader_on_destroy, NULL, NULL, NULL };
meow_view *v = meow_view_create(ACT_READER, MEOW_VIEW_ACTIVITY, &hooks, NULL);
g_canvas = meow_widget_custom_create(0, 0, 480, 720, reader_canvas_render, NULL, NULL);
meow_view_add_widget(v, g_canvas);
return v;
}
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;
if (y >= y0) {
int bw = g_pw / RD_MENU_BTN;
int idx = x / bw;
switch (idx) {
case 0: /* 返回书架 */
g_menu_open = 0;
reader_reflow_persist();
meow_view_page_pop();
return;
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;
}
g_menu_open = 0; /* 点菜单外关闭 */
reader_redraw();
return;
}
/* 三栏:左 1/3 上一页、右 1/3 下一页、中央唤菜单 */
if (x < g_pw / 3) { rd_book_turn(g_book, -1); reader_refill_page(); reader_redraw(); }
else if (x > g_pw * 2 / 3) { rd_book_turn(g_book, 1); reader_refill_page(); reader_redraw(); }
else { g_menu_open = 1; reader_redraw(); }
}
void reader_on_swipe(int dir, int dx, int dy)
{
(void)dx; (void)dy;
if (!g_book) return;
if (g_menu_open) { g_menu_open = 0; reader_redraw(); return; }
if (dir == MEOW_SWIPE_LEFT) { rd_book_turn(g_book, 1); }
else if (dir == MEOW_SWIPE_RIGHT) { rd_book_turn(g_book, -1); }
else return;
reader_refill_page(); reader_redraw();
}
void reader_on_tick(uint64_t now)
{
(void)now;
if (g_notice_ms > 0) { g_notice_ms--; if (g_notice_ms == 0) reader_redraw(); }
}