/* app/book.c — 书桥实现(见 app/book.h)。 */ #include "book.h" #include "rdtxt.h" #include "rdlayout.h" #include "../engine/rdft.h" #include #include #include typedef struct seg { size_t file_off, file_len; 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; 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; /* 当前缓冲对应段;-1=无 */ }; static const char *encname_of(rd_enc_t e) { 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"; } 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); } 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 域不读) */ 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; } b->nsegs = b->npages = 0; uint8_t *utf8 = (uint8_t *)malloc(RDTXT_SEG_UTF8_MAX + 8); if (!utf8) { rd_book_close(b); 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) { 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_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; } 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; 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); rdtxt_reader_free(r); b->nsegs = seg_idx; rc = 0; return rc; } 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; 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; } rdtxt_info info; if (rdtxt_probe(path, &info) != 0) goto close; x->enc = info.enc; x->encname = encname_of(info.enc); 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; } x->cur = 0; *b = x; return 0; } 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); 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; } 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_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) { 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; if (np >= b->npages) np = b->npages - 1; b->cur = np; return np; } /* --- 读回一段并布局到复用缓冲(渲染用)--- */ static int load_seg(rd_book *b, int seg_idx) { 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; /* 空页 */ 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, top = 0; const uint8_t *bmp = NULL; rdft_glyph(b->ft, cp, &adv, &bmp, &bw, &bh, &left, &top); if (bmp && bw > 0) rdft_blend565(out, pitch, b->pw, b->ph, pen, baseline, bmp, bw, bh, left, top, fg565, bg565); pen += adv; if (cp == '\t') pen += adv; /* 制表符已由布局按空格 advance 计量,此处保持游标 */ } } return 0; } /* 重排 + 回锚:以 (seg,ch) 为锚重排全书;返回重排后的页(0=成功,负值=错误)。 */ int rd_book_set_size(rd_book *b, int fs, int ls) { if (!b) return -1; 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 (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; 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; } 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 (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 字库兜底:逐行回调正文文本(无 TTF/OTF 时用 meow_ui_text 渲染)。 */ 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; 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]; /* 单线程渲染:每行最多 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]; 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; }