fix(reader): txt编码误判(UTF-8前窗截断判GB18030致乱码) + build_index无持久化(整本重排慢/UI阻塞卡) — ①rd_utf8_sample_ok结尾不完整序列按合法(中间非法仍拒,GBK不误判,新增t13回归) ②索引落盘MeowData/os.meow.reader/index/*.idx(失效键size/mtime/font/fs/ls/wh/enc,命中秒开,原子写+强自检弃坏缓存,编码/字号/行距重排重写) 重开<0.3s/首开建cache; test_reader_core 72-0, 0warning
This commit is contained in:
1 parent
68710d7ed8
commit
a64ae06eb6
5 files changed
+257
-4
No files matched your search
@@ -14,6 +14,7 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
typedef struct seg {
|
||||
int page_start; /* 全局页表起点 */
|
||||
@@ -45,6 +46,42 @@ struct rd_book {
|
||||
int seg_cache_idx;
|
||||
};
|
||||
|
||||
/* ---- 页索引持久化(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;
|
||||
}
|
||||
|
||||
#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)
|
||||
{
|
||||
@@ -71,6 +108,135 @@ static int book_meas(uint32_t cp, int fs, int *adv, void *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)
|
||||
{
|
||||
@@ -182,7 +348,11 @@ int rd_book_open(rd_book **b, const char *path, const char *font_path,
|
||||
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;
|
||||
/* 页索引缓存命中(书+字号+版面+编码+字体未变)→ 免整书重排,秒开;未命中 → 重排并落盘。 */
|
||||
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;
|
||||
@@ -250,6 +420,7 @@ int rd_book_set_enc(rd_book *b, rd_enc_t enc)
|
||||
free(b->segs); free(b->pages); b->segs = NULL; b->pages = NULL;
|
||||
b->nsegs = b->npages = 0;
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -374,6 +545,7 @@ int rd_book_jump_to_anchor(rd_book *b, int seg, int32_t ch, int fs, int ls)
|
||||
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_oldsegs; b->npages = n_oldpages; return -5; }
|
||||
free(nos); free(nop);
|
||||
index_cache_save(b); /* 字号/行距改变后重新落盘(键含 fs/ls) */
|
||||
}
|
||||
int target = 0;
|
||||
for (int p = 0; p < b->npages; p++) {
|
||||
|
||||
Reference in new issue
Block a user