/* 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 "seg.h" #include "rdlayout.h" #include "rdimg.h" #include "../engine/rdft.h" #include #include #include #include typedef struct seg { int page_start; /* 全局页表起点 */ int page_count; /* 本段页数 */ } seg_t; struct rd_book { char path[400]; char font[400]; char base[256]; const char *encname; /* TXT 有效编码可读名(含覆盖后);非 TXT = "" */ rd_enc_t enc; /* TXT 有效编码;非 TXT = RD_ENC_UTF8 */ 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; /* 当前渲染段复用缓冲 */ 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; /* R3.2 分段流式(TXT):段目录 + 全局段数 + 已建段数 + 段/页表容量(增量补齐用) */ char seg_dir[400]; int seg_n; /* 段文件总数(manifest);fallback = nsegs(全建) */ int seg_built; /* 已布局段数;fallback = nsegs */ int segs_cap, pages_cap; /* segs[]/pages[] 当前容量(只在 seg 路径增量增长) */ }; /* ---- 页索引持久化(MeowData;见 rd_book_set_index_dir) ---- * build_index 对整书逐段 reflow 建 flat 页表,是开大书最贵的步骤(无持久化时每次打开都重跑)。 * 宿主快 ≠ 真机快:同一书 MIPS 上仍需数秒。这里把 (segs + pages) 落盘成 {path 哈希}.idx, * 以 (file_size, file_mtime, font path+mt, fs, ls, content_w, content_h, enc) 为失效键—— * 书/字号/行距/版面/编码/字体任一变了即自动重排并重写,其余情况命中缓存秒开。 * 纯 libc(无 SDK):reader.c 用 meow_config_dir 推导 MeowData dir 注入;冒烟/无卡时 dir 为空 → 直排不持久。 */ static char g_index_dir[400]; /* 索引缓存目录; 空 = 禁用 */ void rd_book_set_index_dir(const char *dir) { if (dir && dir[0]) { strncpy(g_index_dir, dir, sizeof g_index_dir - 1); g_index_dir[sizeof g_index_dir - 1] = 0; } else g_index_dir[0] = 0; } /* R3.2:分段流式缓存根(MeowData/os.meow.reader)。存到 g_seg_base 并同步 rdseg。 */ static char g_seg_base[400]; void rd_book_set_seg_base(const char *base) { if (base && base[0]) { strncpy(g_seg_base, base, sizeof g_seg_base - 1); g_seg_base[sizeof g_seg_base - 1] = 0; } else g_seg_base[0] = 0; rdseg_set_base(g_seg_base); } #define IDX_MAGIC0 'R' #define IDX_MAGIC1 'I' #define IDX_MAGIC2 'D' #define IDX_MAGIC3 'X' #define IDX_VER 1u static int fio_rd(FILE *f, void *p, size_t n) { return fread(p, 1, n, f) == n; } static int fio_wr(FILE *f, const void *p, size_t n) { return fwrite(p, 1, n, f) == n; } static uint32_t idx_fnv1a(const char *s) { uint32_t h = 2166136261u; for (const unsigned char *p = (const unsigned char *)s; *p; p++) { h ^= *p; h *= 16777619u; } return h; } static int index_cache_path(const rd_book *b, char *out, int outsz) { if (!g_index_dir[0]) return -1; if (snprintf(out, (size_t)outsz, "%s/%08x.idx", g_index_dir, idx_fnv1a(b->path)) >= outsz) return -1; return 0; } /* 编码枚举 → 可读名(R3:Big5 正确解码后名实相符)。 */ static const char *enc_to_name(rd_enc_t enc) { switch (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"; case RD_ENC_ASCII: return "ASCII"; default: return ""; } } static const char *fmt_encname(rd_fmt_t f, rd_enc_t enc) { if (f == RD_FMT_TXT) return enc_to_name(enc); return ""; } static int book_meas(uint32_t cp, int fs, int *adv, void *ctx) { (void)fs; rd_book *b = (rd_book *)ctx; return rdft_advance(b->ft, cp, adv); } /* 失效键:书 + 版面 + 编码 + 字体。任一变化 → 缓存不命中(重排)。 */ typedef struct { uint64_t file_size; int64_t file_mtime; int64_t font_mtime; /* 字体文件 mtime;无字体(近似测宽) = 0 */ int32_t fs, ls, cw, ch, enc; char font[320]; } idx_key; static int index_key_fill(rd_book *b, idx_key *k) { memset(k, 0, sizeof *k); struct stat st; if (stat(b->path, &st) != 0) { k->file_size = 0; k->file_mtime = 0; } else { k->file_size = (uint64_t)st.st_size; k->file_mtime = (int64_t)st.st_mtime; } if (b->font[0]) { /* b->font[400] 已 NUL 结尾(open 时 <400 才收);memcpy 319B 恒在界内且 k 已清零 → 余下为 0 */ memcpy(k->font, b->font, sizeof k->font - 1); struct stat fs2; if (stat(b->font, &fs2) == 0) k->font_mtime = (int64_t)fs2.st_mtime; } k->fs = b->fs; k->ls = b->ls; k->cw = b->content_w; k->ch = b->content_h; k->enc = (int32_t)b->enc; return 0; } static int index_key_write(const idx_key *k, FILE *f) { const uint8_t magic[4] = { IDX_MAGIC0, IDX_MAGIC1, IDX_MAGIC2, IDX_MAGIC3 }; uint16_t ver = IDX_VER; return fio_wr(f, magic, sizeof magic) && fio_wr(f, &ver, sizeof ver) && fio_wr(f, &k->file_size, sizeof k->file_size) && fio_wr(f, &k->file_mtime, sizeof k->file_mtime) && fio_wr(f, &k->font_mtime, sizeof k->font_mtime) && fio_wr(f, &k->fs, sizeof k->fs) && fio_wr(f, &k->ls, sizeof k->ls) && fio_wr(f, &k->cw, sizeof k->cw) && fio_wr(f, &k->ch, sizeof k->ch) && fio_wr(f, &k->enc, sizeof k->enc) && fio_wr(f, k->font, sizeof k->font); } static int index_key_read(idx_key *k, FILE *f) { uint8_t magic[4]; if (!fio_rd(f, magic, sizeof magic)) return 0; if (magic[0] != IDX_MAGIC0 || magic[1] != IDX_MAGIC1 || magic[2] != IDX_MAGIC2 || magic[3] != IDX_MAGIC3) return 0; uint16_t ver = 0; if (!fio_rd(f, &ver, sizeof ver) || ver != IDX_VER) return 0; if (!fio_rd(f, &k->file_size, sizeof k->file_size) || !fio_rd(f, &k->file_mtime, sizeof k->file_mtime) || !fio_rd(f, &k->font_mtime, sizeof k->font_mtime) || !fio_rd(f, &k->fs, sizeof k->fs) || !fio_rd(f, &k->ls, sizeof k->ls) || !fio_rd(f, &k->cw, sizeof k->cw) || !fio_rd(f, &k->ch, sizeof k->ch) || !fio_rd(f, &k->enc, sizeof k->enc) || !fio_rd(f, k->font, sizeof k->font)) return 0; k->font[sizeof k->font - 1] = 0; return 1; } static int index_key_eq(const idx_key *a, const idx_key *b) { return a->file_size == b->file_size && a->file_mtime == b->file_mtime && a->font_mtime == b->font_mtime && a->fs == b->fs && a->ls == b->ls && a->cw == b->cw && a->ch == b->ch && a->enc == b->enc && strncmp(a->font, b->font, sizeof a->font) == 0; } /* 读缓存:头部与当前键一致则载入 (segs,pages) 并自检一致;返回 1=命中,0=未命中/损坏(调用方重排)。 */ static int index_cache_load(rd_book *b) { char path[440]; if (index_cache_path(b, path, sizeof path) != 0) return 0; FILE *f = fopen(path, "rb"); if (!f) return 0; idx_key key, cur; int rc = 0; if (index_key_read(&key, f)) { index_key_fill(b, &cur); if (index_key_eq(&key, &cur)) { int32_t nsegs = 0, npages = 0; if (fio_rd(f, &nsegs, sizeof nsegs) && fio_rd(f, &npages, sizeof npages) && nsegs >= 1 && npages >= 1 && nsegs <= 1000000 && npages <= 8000000 && (uint64_t)npages <= cur.file_size + 1 && (uint64_t)nsegs <= cur.file_size + 1) { seg_t *segs = (seg_t *)malloc((size_t)nsegs * sizeof(seg_t)); rd_book_page *pages = (rd_book_page *)malloc((size_t)npages * sizeof(rd_book_page)); if (segs && pages && fio_rd(f, segs, (size_t)nsegs * sizeof(seg_t)) && fio_rd(f, pages, (size_t)npages * sizeof(rd_book_page))) { /* 自检:page_count 求和 == npages 且 page_start 链正确;每页 seg 落在所属段区间。 */ int ok = 1, run = 0; for (int32_t i = 0; i < nsegs && ok; i++) { if (segs[i].page_count < 0 || segs[i].page_start != run) ok = 0; else run += segs[i].page_count; } if (ok && run == npages) { for (int32_t p = 0; p < npages && ok; p++) { int32_t sg = pages[p].seg; if (sg < 0 || sg >= nsegs || pages[p].seg_char < 0 || p < segs[sg].page_start || p >= segs[sg].page_start + segs[sg].page_count) ok = 0; } } if (ok) { b->segs = segs; b->pages = pages; b->nsegs = (int)nsegs; b->npages = (int)npages; rc = 1; /* 命中:调用方免 build_index */ } else { free(segs); free(pages); } } else { free(segs); free(pages); } } } } fclose(f); return rc; } /* 写缓存:先写 .tmp 再 rename(原子替换);失败静默(下次重排)。 */ static void index_cache_save(rd_book *b) { char path[440]; if (b->nsegs <= 0 || b->npages <= 0) return; /* 空书无页表可缓存 */ if (index_cache_path(b, path, sizeof path) != 0) return; /* 目录由 reader.c 在 set_index_dir 前建好;这里 fopen 失败(无目录/只读)→ 静默跳过(下次重排)。 */ char tmp[480]; if (snprintf(tmp, sizeof tmp, "%s.tmp", path) >= (int)sizeof tmp) return; FILE *f = fopen(tmp, "wb"); if (!f) return; idx_key key; int ok = 1; index_key_fill(b, &key); ok = ok && index_key_write(&key, f); ok = ok && fio_wr(f, &b->nsegs, sizeof b->nsegs) && fio_wr(f, &b->npages, sizeof b->npages); if (ok && b->nsegs > 0) ok = fio_wr(f, b->segs, (size_t)b->nsegs * sizeof(seg_t)); if (ok && b->npages > 0) ok = fio_wr(f, b->pages, (size_t)b->npages * sizeof(rd_book_page)); fclose(f); if (ok) { rename(tmp, path); } else { remove(tmp); } } /* ---- 文本:建索引(逐段 layout) ---- */ static int build_index(rd_book *b) { 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) return -6; b->nsegs = b->npages = 0; uint8_t *utf8 = (uint8_t *)malloc(RDTXT_SEG_UTF8_MAX + 8); if (!utf8) return -6; int seg_idx = 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_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); 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].page_start = b->npages; b->segs[seg_idx].page_count = out.npages; for (int p = 0; p < out.npages; p++) { if (b->npages >= (int)cap_pages) { cap_pages *= 2; b->pages = (rd_book_page *)realloc(b->pages, cap_pages * sizeof(rd_book_page)); } b->pages[b->npages].seg = seg_idx; b->pages[b->npages].seg_char = out.pages[p].first_char; b->npages++; } free(lines); free(pages); seg_idx++; } free(utf8); b->nsegs = seg_idx; return 0; } /* ---- R3.2 分段流式(TXT):把一段 UTF-8 布局并追加到 flat 页表 ---- */ static int append_seg_pages(rd_book *b, int seg_idx, const uint8_t *utf8, size_t ulen) { uint32_t *cps = NULL; size_t n = 0; if (rd_utf8_to_cp(utf8, ulen, &cps, &n) != RD_ENC_OK) return -5; 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); return -6; } rd_layout_in in; rd_layout_out out; 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); return -4; } b->segs[seg_idx].page_start = b->npages; b->segs[seg_idx].page_count = out.npages; for (int p = 0; p < out.npages; p++) { if (b->npages >= b->pages_cap) { int nc = b->pages_cap ? b->pages_cap * 2 : 1024; rd_book_page *np = (rd_book_page *)realloc(b->pages, (size_t)nc * sizeof(rd_book_page)); if (!np) { free(lines); free(pages); return -6; } b->pages = np; b->pages_cap = nc; } b->pages[b->npages].seg = seg_idx; b->pages[b->npages].seg_char = out.pages[p].first_char; b->npages++; } free(lines); free(pages); return 0; } /* ---- R3.2 段路径:把 [seg_built, upto_seg] 逐段(512KB 段文件 → utf8 → 布局)补齐进 flat 页表。 * 幂等:已建段跳过。开书只建到目标(页 0/保存进度),翻页/跳转再按需补齐 —— 免整书一次性布局。 */ static int seg_ensure_built(rd_book *b, int upto_seg) { if (!b->seg_dir[0] || b->fmt != RD_FMT_TXT) return 0; int total = b->seg_n > 0 ? b->seg_n : 1; if (upto_seg >= total) upto_seg = total - 1; if (upto_seg < 0) upto_seg = 0; int built_any = 0; while (b->seg_built <= upto_seg) { int idx = b->seg_built; size_t rawlen = 0; if (rdseg_rawlen(b->seg_dir, idx, &rawlen) != 0) break; if (b->segs_cap <= idx) { int nc = b->segs_cap ? b->segs_cap * 2 : 64; if (nc <= idx) nc = idx + 1; seg_t *ns = (seg_t *)realloc(b->segs, (size_t)nc * sizeof(seg_t)); if (!ns) return -6; b->segs = ns; b->segs_cap = nc; } size_t cap = rawlen * 3 / 2 + 16; if (cap < RDTXT_SEG_UTF8_MAX + 8) cap = RDTXT_SEG_UTF8_MAX + 8; if (!b->seg_utf8 || b->seg_utf8_cap < cap) { uint8_t *t = (uint8_t *)malloc(cap); if (!t) return -6; free(b->seg_utf8); b->seg_utf8 = t; b->seg_utf8_cap = cap; } uint8_t *raw = (uint8_t *)malloc(rawlen ? rawlen : 1); if (!raw) return -6; size_t rn = 0; if (rdseg_load(b->seg_dir, idx, raw, rawlen, &rn) != 0) { free(raw); return -4; } size_t ulen = 0; int rc = rd_to_utf8(b->enc, raw, rn, b->seg_utf8, cap, &ulen); free(raw); if (rc == RD_ENC_ERR_BUFSZ) return -2; if (rc != RD_ENC_OK) return -3; int prc = append_seg_pages(b, idx, b->seg_utf8, ulen); if (prc != 0) return prc; b->seg_built++; built_any = 1; } b->nsegs = b->seg_built; if (built_any) index_cache_save(b); return 0; } /* ---- 正文重显 + 布局到复用缓冲 ---- */ static int load_seg(rd_book *b, int seg_idx) { if (seg_idx == b->seg_cache_idx) return 0; size_t ulen = 0; if (b->seg_dir[0] && b->fmt == RD_FMT_TXT) { /* R3.2 段路径:只读当前页所在 512KB 段文件(有界,不全量读整书) */ size_t rawlen = 0; if (rdseg_rawlen(b->seg_dir, seg_idx, &rawlen) != 0) return -4; size_t cap = rawlen * 3 / 2 + 16; if (cap < RDTXT_SEG_UTF8_MAX + 8) cap = RDTXT_SEG_UTF8_MAX + 8; if (!b->seg_utf8 || b->seg_utf8_cap < cap) { uint8_t *t = (uint8_t *)malloc(cap); if (!t) return -6; free(b->seg_utf8); b->seg_utf8 = t; b->seg_utf8_cap = cap; } uint8_t *raw = (uint8_t *)malloc(rawlen ? rawlen : 1); if (!raw) return -6; size_t rn = 0; int rc = rdseg_load(b->seg_dir, seg_idx, raw, rawlen, &rn); if (rc != 0) { free(raw); return -4; } int r2 = rd_to_utf8(b->enc, raw, rn, b->seg_utf8, cap, &ulen); free(raw); if (r2 == RD_ENC_ERR_BUFSZ) return -2; if (r2 != RD_ENC_OK) return -3; } else { 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; 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, int fs, int ls, int pw, int ph, int mx, int my, int top) { if (!b || !path) return -1; rd_book *x = (rd_book *)calloc(1, sizeof *x); if (!x) return -6; 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) { goto close; } if (rd_src_open(&x->src, path) != 0) goto close; x->fmt = rd_src_fmt(x->src); x->enc = RD_ENC_UTF8; rd_enc_t tmp_enc = RD_ENC_UTF8; if (rd_src_txt_enc(x->src, &tmp_enc) == 0) x->enc = tmp_enc; x->encname = fmt_encname(x->fmt, x->enc); x->is_img = (x->fmt == RD_FMT_CBZ); 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; int seg_mode = 0; /* R3.2 分段流式(TXT 且段 base 已配置):开书切 512KB 段文件,逐段按需布局(不全量)。 */ if (g_seg_base[0] && x->fmt == RD_FMT_TXT) { char sdir[600]; if (rdseg_dir_for(x->path, sdir, sizeof sdir) == 0) { strncpy(x->seg_dir, sdir, sizeof x->seg_dir - 1); x->seg_dir[sizeof x->seg_dir - 1] = 0; int ns = rdseg_build(x->path, x->enc, x->seg_dir); if (ns > 0) { x->seg_n = ns; seg_mode = 1; } } } if (seg_mode) { /* 命中缓存(书+版面+编码+字体未变)→ 载入已建 (segs,pages);否则仅布局段 0 起步, * 翻页/跳转再 seg_ensure_built 补齐(治大书开书整表布局卡 UI 线程)。 */ x->segs = NULL; x->pages = NULL; x->nsegs = x->npages = 0; x->seg_built = 0; x->segs_cap = x->pages_cap = 0; if (index_cache_load(x) != 1) { if (seg_ensure_built(x, 0) != 0) goto close; } if (x->nsegs < 1) { /* 段 0 空(理论)→ 兜底再建 */ if (seg_ensure_built(x, 0) != 0) goto close; } x->segs_cap = x->nsegs; x->pages_cap = x->npages; x->seg_built = x->nsegs; /* 缓存命中时恢复到已建段数 */ } else { /* 页索引缓存命中(书+字号+版面+编码+字体未变)→ 免整书重排,秒开;未命中 → 重排并落盘。 */ if (index_cache_load(x) != 1) { if (build_index(x) != 0) goto close; index_cache_save(x); } } } 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) { if (!b) return; free(b->segs); free(b->pages); free(b->seg_utf8); free(b->seg_cps); 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); } 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 : "") : ""; } rd_enc_t rd_book_enc_get(const rd_book *b) { return b ? b->enc : RD_ENC_UTF8; } void rd_book_geom(rd_book *b, int *fs, int *ls, int *pw, int *ph, int *mx, int *my, int *top) { if (!b) return; if (fs) *fs = b->fs; if (ls) *ls = b->ls; if (pw) *pw = b->pw; if (ph) *ph = b->ph; if (mx) *mx = b->mx; if (my) *my = b->my; if (top) *top = b->top; } const char *rd_book_basename(const rd_book *b) { return b ? b->base : ""; } const char *rd_book_path(const rd_book *b) { return b ? b->path : ""; } /* R3:TXT 编码切换——重开 src(带覆盖编码)、重建页索引、回锚当前页。非 TXT/CBZ 返回 -1。 * 事务式:先开新 src 成功才换(失败保留旧 src,书仍可用,返回负值)。 */ int rd_book_set_enc(rd_book *b, rd_enc_t enc) { if (!b || b->is_img || b->fmt != RD_FMT_TXT) return -1; if (enc == b->enc) return b->cur; /* 同编码,无需重建 */ int seg; int32_t ch; rd_book_anchor(b, &seg, &ch); rd_enc_t oldenc = b->enc; rd_src *ns = NULL; if (rd_src_open_with_enc(&ns, b->path, &enc) != 0) return -2; /* 旧 src 未动 */ rd_src_free(b->src); b->src = ns; /* 换新 */ b->enc = enc; b->encname = fmt_encname(b->fmt, b->enc); /* 重建索引(复用现值 fs/ls/content_*),再回锚 */ free(b->segs); free(b->pages); b->segs = NULL; b->pages = NULL; b->nsegs = b->npages = 0; b->seg_built = 0; b->segs_cap = b->pages_cap = 0; if (b->seg_dir[0]) { /* R3.2 段路径:编码变了 → 段文件边界/解码不同 → 重建段文件 + 按需布局(段 0 起步) */ if (oldenc != b->enc) rdseg_clear(b->seg_dir); b->seg_n = rdseg_build(b->path, b->enc, b->seg_dir); if (seg_ensure_built(b, 0) != 0) return -3; index_cache_save(b); } else { if (build_index(b) != 0) return -3; /* 新 src 有效但索引空 → 渲染返回 -1,安全降级 */ index_cache_save(b); /* 编码切换后重新落盘(键含 enc,与旧缓存并存不冲突) */ } return rd_book_jump_to_anchor(b, seg, ch, b->fs, b->ls); } void rd_book_anchor(rd_book *b, int *seg, int32_t *ch) { if (!b || b->npages <= 0) { if (seg) *seg = 0; if (ch) *ch = 0; return; } int p = b->cur < 0 ? 0 : (b->cur >= b->npages ? b->npages - 1 : b->cur); if (seg) *seg = b->pages[p].seg; if (ch) *ch = b->pages[p].seg_char; } int rd_book_turn(rd_book *b, int delta) { if (!b || b->npages <= 0) return 0; int np = b->cur + delta; if (np < 0) np = 0; /* R3.2 段路径:翻到已建表尾之外 → 按需补下一段(一次翻页只补到够用,不全量) */ if (b->seg_dir[0] && b->fmt == RD_FMT_TXT && np >= b->npages && b->seg_built < b->seg_n) { while (np >= b->npages && b->seg_built < b->seg_n) { if (seg_ensure_built(b, b->seg_built) != 0) break; } } if (np >= b->npages) np = b->npages - 1; b->cur = np; return np; } int rd_book_set_page(rd_book *b, int page) { if (!b || b->npages <= 0) return 0; /* R3.2 段路径:跳到已建表外 → 按需补段到目标页(跳到哪建到哪) */ if (b->seg_dir[0] && b->fmt == RD_FMT_TXT && page >= b->npages && b->seg_built < b->seg_n) { while (page >= b->npages && b->seg_built < b->seg_n) { if (seg_ensure_built(b, b->seg_built) != 0) break; } } if (page < 0) page = 0; if (page >= b->npages) page = b->npages - 1; b->cur = page; return page; } /* ---- 文本页渲染(FreeType 字形 → 全屏 565) ---- */ static int render_text_page(rd_book *b, int page_no, uint16_t *out, int pitch, uint16_t bg565, uint16_t fg565) { 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; rd_page *pp = &b->seg_pages[local_page]; int asc = rdft_ascender(b->ft); int screen_x0 = b->mx; int screen_y0 = b->top; for (int li = 0; li < pp->nlines; li++) { rd_line *ln = &b->seg_lines[pp->first_line + li]; int pen = screen_x0 + ln->x; 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, btop = 0; const uint8_t *bmp = NULL; 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, btop, fg565, bg565); pen += adv; } } return 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; rd_book_anchor(b, &anchor_seg, &anchor_ch); return rd_book_jump_to_anchor(b, anchor_seg, anchor_ch, fs, 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_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 (b->seg_dir[0] && b->fmt == RD_FMT_TXT) { /* R3.2 段路径:只重布局到含锚的段(不全量;段文件不变,仅页表变) */ b->seg_built = 0; b->segs_cap = b->pages_cap = 0; if (seg_ensure_built(b, seg) != 0) { b->segs = nos; b->pages = nop; b->nsegs = n_oldsegs; b->npages = n_oldpages; return -5; } free(nos); free(nop); index_cache_save(b); } else { 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); index_cache_save(b); /* 字号/行距改变后重新落盘(键含 fs/ls) */ } } else if (b->seg_dir[0] && b->fmt == RD_FMT_TXT && seg >= b->seg_built) { /* 锚在未建段 → 按需补齐(重开续读/目录跳转到后部) */ if (seg_ensure_built(b, seg) != 0) return -5; } 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 (b->pages[p].seg_char >= ch) { target = p; break; } target = p; } if (target >= b->npages) target = b->npages - 1; if (target < 0) target = 0; b->cur = target; return target; } /* ---- SDK 字库兜底(文本格式) ---- */ int rd_book_render_text(rd_book *b, int page_no, rd_book_line_fn fn, void *ctx) { 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; 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; rd_page *pp = &b->seg_pages[local_page]; 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; size_t o = 0; for (int32_t ci = ln->s; ci < ln->e; ci++) { uint8_t tmp[4]; int n = rd_cp_to_utf8(b->seg_cps[ci], tmp); memcpy(line_utf8 + o, tmp, (size_t)n); o += (size_t)n; } line_utf8[o] = 0; fn(x, y, (const char *)line_utf8, (int)o, 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; }