Files
MeowOS-For-R3P2/patch_v2/reader_app/app/book.c
T
MeowOS 553a7d5d9e reader R1: os.meow.reader 书架+TXT 垂直链路骨架
- core: 编码检测(BOM→UTF-8严格→GB18030/Big5统计, Big5与GB重叠按GB18030兜底, §3.5风险注明)
  + GB18030双字节码表(uint16[126][190]=47880B, tools/gen_gb18030_table.py 生成, 生成物入仓)
  + 流式排版器(贪心断行+Kinsoku避头尾+分页+重排锚定, rd_meas_fn 测量回调替 FreeType, 宿主可测)
  + TXT 流式加载器(按段读, 不整文件入内存, \n 恒独立换行可安全分段)
- engine: FreeType 封装(FT_New_Face 按需读文件, NO_HINTING/LIGHT 灰阶, LRU 512 key=码点+字号,
  blend565, rdft_advance 只测宽不栅格化)
- app: 书架(扫 Books/ 识别 5 后缀, 最近读置顶, 空态提示) + 阅读页(W_CUSTOM 画布 3 栏翻页
  +中央菜单占位(字号±/行距±/返回) +顶栏进度%/章节名) + 书桥(段表+flat 页索引, 渲染, 重排回锚)
  + 进度持久化(MeowData/os.meow.reader/progress/, path hash→seg/ch/fs/ls/ts)
- 取舍: 正文字体 FreeType 直读 TF MeowOS/fonts/ TTF/OTF; 无字体回退 SDK 位图字库(meow_ui_text,
  未打包字体(许可/体积), 全 glyph 兜底 R2); 页位图缓存当前+前后各1页(3×691KB), 翻页动画默认无
- 内存实测(宿主 --wrap malloc, 73页书): 书结构 130KB + 当前段刮擦~1.6MB(行/页容量按 n 偏高,R2 收紧)
  + 字形缓存~650KB + 3页位图 2.07MB ≈ 4.45MB 动态, 在 ≤8MB 阅读态预算内
- 测试: 宿主 core 58/58(enc/gb18030金标准/kinsoku/page/reflow/txt); qemu-mipsel ft_smoke PASS
  + book_smoke(12页/渲染有墨/翻页/重排回锚) PASS; reader 交叉 -Wall -Wextra 0 warning
- 产物: reader(mipsel 静态1.2MB)+build_reader.sh(vendor FreeType 可复现); vendor/二进制 gitignore
2026-09-01 23:26:42 +08:00

347 lines
13 KiB
C

/* app/book.c — 书桥实现(见 app/book.h)。 */
#include "book.h"
#include "rdtxt.h"
#include "rdlayout.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; /* 本段页数 */
} 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;
}