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

+170 -118
View File
@@ -1,101 +1,105 @@
/* app/book.c — 书桥实现(见 app/book.h)。 */
/* app/book.c — 书桥实现(见 app/book.h):把 rd_src(TXT/FB2/EPUB/MOBI 段契约 + CBZ 图页)
* + engine(FreeType/rdimg) 串成「一本书」。
*
* 文本格式:build_index 逐段 rd_src_next → 每段 utf8→码点→rd_layout → 页索引(flat:段+段内字符偏移)
* (即 rdtxt 同款 UTF-8 段契约喂排版器);渲染按段重显。锚定 (段,字符偏移) 与字号无关。
* CBZ:纯图页,npages=图片数,render 用 rdimg 解码+降采样写内容区。
*/
#include "book.h"
#include "rdsrc.h"
#include "rdtxt.h"
#include "rdlayout.h"
#include "rdimg.h"
#include "../engine/rdft.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct seg {
size_t file_off, file_len;
int page_start; /* 全局页表起点 */
int page_count; /* 本段页数 */
int page_start; /* 全局页表起点 */
int page_count; /* 本段页数 */
} seg_t;
struct rd_book {
char path[400];
char font[400];
char base[256]; /* 文件名 */
const char *encname;
rdft *ft;
rd_enc_t enc;
char path[400];
char font[400];
char base[256];
const char *encname;
rdft *ft;
rd_src *src;
rd_fmt_t fmt;
int is_img; /* 1=CBZ 图片书 */
int fs, ls, pw, ph, mx, my, top;
int content_w, content_h;
seg_t *segs; int nsegs;
rd_book_page *pages; int npages; /* flat 页表 */
int cur; /* 当前页 */
seg_t *segs; int nsegs;
rd_book_page *pages; int npages; /* flat 页表(文本) */
int cur;
/* 当前渲染段的复用缓冲 */
/* 当前渲染段复用缓冲 */
uint8_t *seg_utf8; size_t seg_utf8_cap;
uint32_t *seg_cps; size_t seg_cps_n, seg_cps_cap;
/* 当前段布局 */
rd_line *seg_lines; rd_page *seg_pages; int seg_nlines, seg_npages;
int seg_cache_idx; /* 当前缓冲对应段;-1=无 */
rd_line *seg_lines; rd_page *seg_pages; int seg_nlines, seg_npages;
int seg_cache_idx;
};
static const char *encname_of(rd_enc_t e)
static const char *fmt_encname(rd_fmt_t f, const char *path)
{
switch (e) {
case RD_ENC_UTF8: return "UTF-8";
case RD_ENC_UTF16LE: return "UTF-16LE";
case RD_ENC_UTF16BE: return "UTF-16BE";
case RD_ENC_GB18030: return "GB18030";
case RD_ENC_BIG5: return "Big5(按GB18030)";
case RD_ENC_ASCII: return "ASCII";
if (f == RD_FMT_TXT) {
rdtxt_info info;
if (rdtxt_probe(path, &info) == 0) {
switch (info.enc) {
case RD_ENC_UTF8: return "UTF-8";
case RD_ENC_UTF16LE: return "UTF-16LE";
case RD_ENC_UTF16BE: return "UTF-16BE";
case RD_ENC_GB18030: return "GB18030";
case RD_ENC_BIG5: return "Big5(按GB18030)";
case RD_ENC_ASCII: return "ASCII";
}
}
}
return "?";
return "";
}
/* 测量回调:排版器逐字测 advance → rdft_advance(不栅格化)。 */
static int book_meas(uint32_t cp, int fs, int *adv, void *ctx)
{
(void)fs;
rd_book *b = (rd_book *)ctx;
/* advance 受字号影响:rdft 已 set_px(fs),故用当前 rdft 度量即可 */
return rdft_advance(b->ft, cp, adv);
}
/* ---- 文本:建索引(逐段 layout) ---- */
static int build_index(rd_book *b)
{
int rc = 0;
rdtxt_reader *r = NULL;
if (rdtxt_reader_open(b->path, &(rdtxt_info){ .enc = b->enc, .file_size = 0, .path = "" },
&r) != 0) return -1;
/* rdtxt_reader_open 以 info 的 enc 为准(path 域不读) */
rd_src_reset(b->src);
size_t cap_segs = 64, cap_pages = 1024;
b->segs = (seg_t *)malloc(cap_segs * sizeof(seg_t));
b->pages = (rd_book_page *)malloc(cap_pages * sizeof(rd_book_page));
if (!b->segs || !b->pages) { rd_book_close(b); return -6; }
if (!b->segs || !b->pages) return -6;
b->nsegs = b->npages = 0;
uint8_t *utf8 = (uint8_t *)malloc(RDTXT_SEG_UTF8_MAX + 8);
if (!utf8) { rd_book_close(b); return -6; }
if (!utf8) return -6;
size_t off = 0, flen = 0, ulen = 0;
int seg_idx = 0;
while (rdtxt_reader_next(r, utf8, RDTXT_SEG_UTF8_MAX + 8, &ulen, &off, &flen) == 0) {
size_t ulen = 0;
while (rd_src_next(b->src, utf8, RDTXT_SEG_UTF8_MAX + 8, &ulen) == 0) {
if (seg_idx >= (int)cap_segs) { cap_segs *= 2; b->segs = (seg_t *)realloc(b->segs, cap_segs * sizeof(seg_t)); }
uint32_t *cps = NULL; size_t n = 0;
rd_utf8_to_cp(utf8, ulen, &cps, &n); /* rd_utf8_to_cp 内部 malloc */
rd_utf8_to_cp(utf8, ulen, &cps, &n);
rd_layout_in in;
rd_layout_out out;
rd_line *lines = (rd_line *)malloc((n ? n : 1) * sizeof(rd_line));
rd_page *pages = (rd_page *)malloc((n ? n : 1) * sizeof(rd_page));
if (!lines || !pages) { free(lines); free(pages); free(cps); free(utf8); rd_book_close(b); return -6; }
if (!lines || !pages) { free(lines); free(pages); free(cps); free(utf8); return -6; }
in.chars = cps; in.n = (int32_t)n; in.fs = b->fs; in.line_scale = b->ls;
in.page_w = b->content_w; in.page_h = b->content_h; in.indent = b->fs;
in.meas = book_meas; in.mctx = b;
out.lines = lines; out.pages = pages; out.max_lines = (int)(n ? n : 1); out.max_pages = (int)(n ? n : 1);
/* 布局容量不足时翻倍重试(罕见)。 */
int lrc = rd_layout(&in, &out);
free(cps);
if (lrc != RD_LAYOUT_OK) { free(lines); free(pages); break; } /* 段为空/异常:跳过 */
/* 登记段 + 页 */
b->segs[seg_idx].file_off = off;
b->segs[seg_idx].file_len = flen;
if (lrc != RD_LAYOUT_OK) { free(lines); free(pages); break; }
b->segs[seg_idx].page_start = b->npages;
b->segs[seg_idx].page_count = out.npages;
for (int p = 0; p < out.npages; p++) {
@@ -108,10 +112,39 @@ static int build_index(rd_book *b)
seg_idx++;
}
free(utf8);
rdtxt_reader_free(r);
b->nsegs = seg_idx;
rc = 0;
return rc;
return 0;
}
/* ---- 正文重显 + 布局到复用缓冲 ---- */
static int load_seg(rd_book *b, int seg_idx)
{
if (seg_idx == b->seg_cache_idx) return 0;
size_t cap = RDTXT_SEG_UTF8_MAX + 8;
if (!b->seg_utf8) b->seg_utf8 = (uint8_t *)malloc(cap);
if (!b->seg_utf8) return -6;
size_t ulen = 0;
if (rd_src_seg(b->src, seg_idx, b->seg_utf8, cap, &ulen) != 0) return -4;
size_t n = 0;
free(b->seg_cps); b->seg_cps = NULL;
if (rd_utf8_to_cp(b->seg_utf8, ulen, &b->seg_cps, &n) != RD_ENC_OK) return -5;
b->seg_cps_n = n;
if (b->seg_lines) free(b->seg_lines);
if (b->seg_pages) free(b->seg_pages);
b->seg_lines = (rd_line *)malloc((n ? n : 1) * sizeof(rd_line));
b->seg_pages = (rd_page *)malloc((n ? n : 1) * sizeof(rd_page));
if (!b->seg_lines || !b->seg_pages) return -6;
rd_layout_in in;
rd_layout_out out;
in.chars = b->seg_cps; in.n = (int32_t)n; in.fs = b->fs; in.line_scale = b->ls;
in.page_w = b->content_w; in.page_h = b->content_h; in.indent = b->fs;
in.meas = book_meas; in.mctx = b;
out.lines = b->seg_lines; out.pages = b->seg_pages; out.max_lines = (int)(n ? n : 1); out.max_pages = (int)(n ? n : 1);
int lrc = rd_layout(&in, &out);
if (lrc != RD_LAYOUT_OK) { b->seg_nlines = b->seg_npages = 0; b->seg_cache_idx = -1; return -4; }
b->seg_nlines = out.nlines; b->seg_npages = out.npages;
b->seg_cache_idx = seg_idx;
return 0;
}
int rd_book_open(rd_book **b, const char *path, const char *font_path,
@@ -123,31 +156,38 @@ int rd_book_open(rd_book **b, const char *path, const char *font_path,
if (strlen(path) >= sizeof x->path) { free(x); return -2; }
strcpy(x->path, path);
if (font_path) { if (strlen(font_path) >= sizeof x->font) { free(x); return -2; } strcpy(x->font, font_path); }
/* 文件名 */
const char *sl = strrchr(path, '/');
strncpy(x->base, sl ? sl + 1 : path, sizeof x->base - 1); x->base[sizeof x->base - 1] = 0;
char *dot = strrchr(x->base, '.'); if (dot) *dot = 0;
if (fs <= 0) fs = 20;
if (ls <= 0) ls = 130;
x->fs = fs; x->ls = ls; x->pw = pw; x->ph = ph; x->mx = mx; x->my = my; x->top = top;
x->content_w = pw - 2 * mx;
x->content_h = ph - top - my;
if (x->content_w < 10 || x->content_h < 10) { close: free(x); return -3; }
if (x->content_w < 10 || x->content_h < 10) { goto close; }
rdtxt_info info;
if (rdtxt_probe(path, &info) != 0) goto close;
x->enc = info.enc;
x->encname = encname_of(info.enc);
if (rd_src_open(&x->src, path) != 0) goto close;
x->fmt = rd_src_fmt(x->src);
x->encname = fmt_encname(x->fmt, path);
x->is_img = (x->fmt == RD_FMT_CBZ);
if (rdft_init(&x->ft) != 0) goto close;
if (font_path && rdft_load_font(x->ft, x->font) != 0) { rdft_free(x->ft); goto close; }
if (x->ft) rdft_set_px(x->ft, fs);
x->seg_cache_idx = -1;
if (build_index(x) != 0) { rd_book_close(x); return -5; }
if (!x->is_img) {
if (rdft_init(&x->ft) != 0) goto close;
if (font_path && rdft_load_font(x->ft, x->font) != 0) { rdft_free(x->ft); x->ft = NULL; goto close; }
if (x->ft) rdft_set_px(x->ft, fs);
x->seg_cache_idx = -1;
if (build_index(x) != 0) goto close;
} else {
int pages = rd_src_pages(x->src);
x->npages = pages > 0 ? pages : 0;
}
x->cur = 0;
*b = x;
return 0;
close:
rd_book_close(x);
return -1;
}
void rd_book_close(rd_book *b)
@@ -160,6 +200,7 @@ void rd_book_close(rd_book *b)
free(b->seg_lines);
free(b->seg_pages);
if (b->ft) rdft_free(b->ft);
if (b->src) rd_src_free(b->src);
free(b);
}
@@ -167,6 +208,9 @@ int rd_book_pages(rd_book *b) { return b ? b->npages : 0; }
int rd_book_current(rd_book *b) { return b ? b->cur : 0; }
int rd_book_font(rd_book *b) { return b ? b->fs : 0; }
int rd_book_ls(rd_book *b) { return b ? b->ls : 0; }
int rd_book_is_image(rd_book *b) { return b ? b->is_img : 0; }
const char *rd_book_encname(const rd_book *b) { return b ? (b->encname ? b->encname : "") : ""; }
void rd_book_geom(rd_book *b, int *fs, int *ls, int *pw, int *ph, int *mx, int *my, int *top)
{
if (!b) return;
@@ -179,7 +223,6 @@ void rd_book_geom(rd_book *b, int *fs, int *ls, int *pw, int *ph, int *mx, int *
if (top) *top = b->top;
}
const char *rd_book_basename(const rd_book *b) { return b ? b->base : ""; }
const char *rd_book_encname(const rd_book *b) { return b ? b->encname : ""; }
const char *rd_book_path(const rd_book *b) { return b ? b->path : ""; }
void rd_book_anchor(rd_book *b, int *seg, int32_t *ch)
@@ -200,55 +243,18 @@ int rd_book_turn(rd_book *b, int delta)
return np;
}
/* --- 读回一段并布局到复用缓冲(渲染用)--- */
static int load_seg(rd_book *b, int seg_idx)
/* ---- 文本页渲染(FreeType 字形 → 全屏 565) ---- */
static int render_text_page(rd_book *b, int page_no, uint16_t *out, int pitch, uint16_t bg565, uint16_t fg565)
{
if (seg_idx == b->seg_cache_idx) return 0;
seg_t *s = &b->segs[seg_idx];
size_t cap = RDTXT_SEG_UTF8_MAX + 8;
if (!b->seg_utf8) b->seg_utf8 = (uint8_t *)malloc(cap);
if (!b->seg_utf8) return -6;
size_t ulen = 0;
if (rdtxt_convert_span(b->path, b->enc, s->file_off, s->file_len, b->seg_utf8, cap, &ulen) != RD_ENC_OK)
return -4;
size_t n = 0;
free(b->seg_cps); /* rd_utf8_to_cp 内部 malloc 新数组 */
b->seg_cps = NULL;
if (rd_utf8_to_cp(b->seg_utf8, ulen, &b->seg_cps, &n) != RD_ENC_OK) return -5;
b->seg_cps_n = n;
/* 布局(容量:段码点数为上界) */
if (b->seg_lines) free(b->seg_lines);
if (b->seg_pages) free(b->seg_pages);
b->seg_lines = (rd_line *)malloc((n ? n : 1) * sizeof(rd_line));
b->seg_pages = (rd_page *)malloc((n ? n : 1) * sizeof(rd_page));
if (!b->seg_lines || !b->seg_pages) return -6;
rd_layout_in in;
rd_layout_out out;
in.chars = b->seg_cps; in.n = (int32_t)n; in.fs = b->fs; in.line_scale = b->ls;
in.page_w = b->content_w; in.page_h = b->content_h; in.indent = b->fs;
in.meas = book_meas; in.mctx = b;
out.lines = b->seg_lines; out.pages = b->seg_pages; out.max_lines = (int)(n ? n : 1); out.max_pages = (int)(n ? n : 1);
int lrc = rd_layout(&in, &out);
if (lrc != RD_LAYOUT_OK) { b->seg_nlines = b->seg_npages = 0; b->seg_cache_idx = -1; return -4; }
b->seg_nlines = out.nlines; b->seg_npages = out.npages;
b->seg_cache_idx = seg_idx;
return 0;
}
int rd_book_render(rd_book *b, int page_no, uint16_t *out, int pitch, uint16_t bg565, uint16_t fg565)
{
if (!b || !out || b->npages <= 0 || page_no < 0 || page_no >= b->npages) return -1;
int npix = b->pw * b->ph;
for (int i = 0; i < npix; i++) out[i] = bg565;
rd_book_page *pg = &b->pages[page_no];
int seg_idx = (int)pg->seg;
if (seg_idx < 0 || seg_idx >= b->nsegs) return -1;
if (load_seg(b, seg_idx) != 0) return -2;
int local_page = page_no - b->segs[seg_idx].page_start;
if (local_page < 0 || local_page >= b->seg_npages) return 1; /* 空页 */
if (local_page < 0 || local_page >= b->seg_npages) return 1;
rd_page *pp = &b->seg_pages[local_page];
int asc = rdft_ascender(b->ft);
int screen_x0 = b->mx;
int screen_y0 = b->top;
@@ -258,22 +264,55 @@ int rd_book_render(rd_book *b, int page_no, uint16_t *out, int pitch, uint16_t b
int baseline = screen_y0 + ln->y + asc;
for (int32_t ci = ln->s; ci < ln->e; ci++) {
uint32_t cp = b->seg_cps[ci];
int adv = 0, bw = 0, bh = 0, left = 0, top = 0;
int adv = 0, bw = 0, bh = 0, left = 0, btop = 0;
const uint8_t *bmp = NULL;
rdft_glyph(b->ft, cp, &adv, &bmp, &bw, &bh, &left, &top);
rdft_glyph(b->ft, cp, &adv, &bmp, &bw, &bh, &left, &btop);
if (bmp && bw > 0)
rdft_blend565(out, pitch, b->pw, b->ph, pen, baseline, bmp, bw, bh, left, top, fg565, bg565);
rdft_blend565(out, pitch, b->pw, b->ph, pen, baseline, bmp, bw, bh, left, btop, fg565, bg565);
pen += adv;
if (cp == '\t') pen += adv; /* 制表符已由布局按空格 advance 计量,此处保持游标 */
}
}
return 0;
}
/* 重排 + 回锚:以 (seg,ch) 为锚重排全书;返回重排后的页(0=成功,负值=错误)。 */
/* ---- CBZ 图片页渲染(rdimg 解码+降采样 → 内容区居中) ---- */
static int render_img_page(rd_book *b, int page_no, uint16_t *out, int pitch, uint16_t bg565)
{
int npix = b->pw * b->ph;
for (int i = 0; i < npix; i++) out[i] = bg565;
uint8_t *data = NULL; size_t size = 0;
if (rd_src_page(b->src, page_no, &data, &size) != 0) return -1;
uint16_t *scaled = NULL; int sw = 0, sh = 0;
if (rdimg_decode_scale(data, size, b->content_w, b->content_h, &scaled, &sw, &sh) != 0) {
free(data); return -2;
}
int x0 = b->mx + (b->content_w - sw) / 2;
int y0 = b->top + (b->content_h - sh) / 2;
int pitch_px = pitch / 2;
for (int y = 0; y < sh; y++) {
int ty = y0 + y; if (ty < 0 || ty >= b->ph) continue;
for (int x = 0; x < sw; x++) {
int tx = x0 + x; if (tx < 0 || tx >= b->pw) continue;
out[(size_t)ty * pitch_px + tx] = scaled[(size_t)y * sw + x];
}
}
free(scaled);
free(data);
return 0;
}
int rd_book_render(rd_book *b, int page_no, uint16_t *out, int pitch, uint16_t bg565, uint16_t fg565)
{
if (!b || !out || b->npages <= 0 || page_no < 0 || page_no >= b->npages) return -1;
if (b->is_img) return render_img_page(b, page_no, out, pitch, bg565);
return render_text_page(b, page_no, out, pitch, bg565, fg565);
}
/* ---- 重排 + 回锚 ---- */
int rd_book_set_size(rd_book *b, int fs, int ls)
{
if (!b) return -1;
if (b->is_img) { b->fs = fs; b->ls = ls; return 0; }
if (fs <= 0) fs = b->fs;
if (ls <= 0) ls = b->ls;
int anchor_seg, anchor_ch;
@@ -284,27 +323,26 @@ int rd_book_set_size(rd_book *b, int fs, int ls)
int rd_book_jump_to_anchor(rd_book *b, int seg, int32_t ch, int fs, int ls)
{
if (!b) return -1;
if (b->is_img) {
if (seg >= 0 && seg < b->npages) b->cur = seg;
return b->cur;
}
if (fs != b->fs || ls != b->ls) {
b->fs = fs; b->ls = ls;
b->content_w = b->pw - 2 * b->mx;
b->content_h = b->ph - b->top - b->my;
if (b->ft) rdft_set_px(b->ft, fs);
/* 重建索引 */
seg_t *nos = b->segs; int n_old_segs = b->nsegs;
rd_book_page *nop = b->pages; int n_old_pages = b->npages;
seg_t *nos = b->segs; int n_oldsegs = b->nsegs;
rd_book_page *nop = b->pages; int n_oldpages = b->npages;
b->segs = NULL; b->pages = NULL; b->nsegs = b->npages = 0;
if (build_index(b) != 0) {
b->segs = nos; b->pages = nop; b->nsegs = n_old_segs; b->npages = n_old_pages;
return -5;
}
if (build_index(b) != 0) { b->segs = nos; b->pages = nop; b->nsegs = n_oldsegs; b->npages = n_oldpages; return -5; }
free(nos); free(nop);
}
/* 回锚:找第一个 (seg==anchorSeg && seg_char>=anchorCh) 的页,或段内第一个 seg_char>=ch;跨段按 seg 序 */
int target = 0;
for (int p = 0; p < b->npages; p++) {
int ps = b->pages[p].seg;
if (ps < seg) { target = p + 1; continue; }
if (ps > seg) { break; }
if (ps > seg) break;
if (b->pages[p].seg_char >= ch) { target = p; break; }
target = p;
}
@@ -314,10 +352,10 @@ int rd_book_jump_to_anchor(rd_book *b, int seg, int32_t ch, int fs, int ls)
return target;
}
/* SDK 字库兜底:逐行回调正文文本(无 TTF/OTF 时用 meow_ui_text 渲染)。 */
/* ---- SDK 字库兜底(文本格式) ---- */
int rd_book_render_text(rd_book *b, int page_no, rd_book_line_fn fn, void *ctx)
{
if (!b || !fn || b->npages <= 0 || page_no < 0 || page_no >= b->npages) return -1;
if (!b || !fn || b->is_img || b->npages <= 0 || page_no < 0 || page_no >= b->npages) return -1;
rd_book_page *pg = &b->pages[page_no];
int seg_idx = (int)pg->seg;
if (seg_idx < 0 || seg_idx >= b->nsegs) return -1;
@@ -325,13 +363,11 @@ int rd_book_render_text(rd_book *b, int page_no, rd_book_line_fn fn, void *ctx)
int local_page = page_no - b->segs[seg_idx].page_start;
if (local_page < 0 || local_page >= b->seg_npages) return 1;
rd_page *pp = &b->seg_pages[local_page];
/* 单线程渲染:每行最多 content_w 个单宽字符 → ≤4B/字 UTF-8 */
uint8_t *line_utf8 = (uint8_t *)malloc((size_t)(b->content_w * 4 + 32));
if (!line_utf8) return -6;
for (int li = 0; li < pp->nlines; li++) {
rd_line *ln = &b->seg_lines[pp->first_line + li];
int x = b->mx + ln->x, y = b->top + ln->y;
/* 组 utf8 */
size_t o = 0;
for (int32_t ci = ln->s; ci < ln->e; ci++) {
uint8_t tmp[4];
@@ -344,3 +380,19 @@ int rd_book_render_text(rd_book *b, int page_no, rd_book_line_fn fn, void *ctx)
free(line_utf8);
return 0;
}
/* ---- 目录(转 rd_src) ---- */
int rd_book_toc_n(const rd_book *b)
{
return (b && b->src && !b->is_img) ? rd_src_toc_n(b->src) : 0;
}
int rd_book_toc(const rd_book *b, int i, char *title, int tsz, int *seg, int32_t *ch)
{
if (!b || !b->src || b->is_img || !title) return -1;
rd_toc_entry t;
if (rd_src_toc(b->src, i, &t) != 0) return -1;
strncpy(title, t.title, (size_t)tsz - 1); title[tsz - 1] = 0;
if (seg) *seg = t.seg;
if (ch) *ch = t.ch;
return 0;
}
+5
View File
@@ -35,6 +35,11 @@ void rd_book_close(rd_book *b);
int rd_book_pages(rd_book *b);
int rd_book_current(rd_book *b); /* 当前页 */
int rd_book_turn(rd_book *b, int delta); /* +1/-1;clamp 到 [0,npages-1] */
int rd_book_is_image(rd_book *b); /* 1=CBZ 图片页书 */
/* 目录:0=无;i 项标题写到 title,目标段/字符偏移填 *seg 与 *ch。 */
int rd_book_toc_n(const rd_book *b);
int rd_book_toc(const rd_book *b, int i, char *title, int tsz, int *seg, int32_t *ch);
void rd_book_anchor(rd_book *b, int *seg, int32_t *ch);/* 当前页锚(段,字符偏移) */
int rd_book_jump_to_anchor(rd_book *b, int seg, int32_t ch, int fs, int ls); /* 重排 + 回锚 */
int rd_book_set_size(rd_book *b, int fs, int ls); /* 重排 + 回锚(anchor=当前页) */
+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;
}
+2 -4
View File
@@ -115,9 +115,7 @@ static void shelf_list_tapcb(int idx, int op, void *ctx)
{
(void)op; (void)ctx;
if (idx < 0 || idx >= g_nbooks) return;
const shelf_book *b = &g_books[idx];
if (b->fmt == 0) { reader_open(b->path); }
else { snprintf(g_notice, sizeof g_notice, "%s 格式 R2 支持(R1 先看 TXT)", fmt_name(b->fmt)); g_notice_ms = 2500; }
reader_open(g_books[idx].path);
}
/* view 生命周期 */
@@ -165,7 +163,7 @@ void shelf_redraw(void)
if (g_list) meow_widget_draw(g_list);
if (g_nbooks == 0) {
meow_ui_text(30, 320, "书架为空:请往 TF 卡 Books/ 放书籍", 20, g_dim);
meow_ui_text(30, 348, "支持 TXT(可读) / FB2 / EPUB / MOBI / CBZ(R2)", 20, g_dim);
meow_ui_text(30, 348, "支持 TXT / FB2 / EPUB / MOBI / CBZ", 20, g_dim);
}
if (g_notice_ms > 0) {
meow_ui_fill_rect(0, 620, 480, 44, g_bg);