feat: M6.44+M6.45 收编 — 真机 0.8.45 现场保全(reader章节/key_router按键路由/WiFi连接修复/ncm bench+IME修复)
脏工作区现场收编:devdocs/local/m6_44_45_dirty_tracked.patch(46 个已跟踪文本文件,
+3090/-585, 已对 M6.43 基线干净适配) + m6_44_45_untracked_src.tar.gz 中 patch_v2/ 下
13 个新增源文件, 合成单个提交, 内容与容器工作树(R3ProII/patch_v2)逐字节一致。
- reader 章节: 新增 core/rdchapter.{h,c} — TXT 章节规范解析器, 扫描 512KB 码位对齐段文件
逐段流式识别标题行(行首缩进≤4/显示宽≤40/行内标点≤1 三重约束防误判), 输出标题+全书字符
偏移+段号/段内偏移+阿拉伯章号(中文数字归一), 首章前内容归"卷首"; book/reader/rdenc/
rdlayout/settings 接入章节跳转与进度锚点, 附 chapter_smoke/turn_repro/build_turn_repro.sh
- key_router 按键路由: 新增 meow_launcherd/key_router.{h,c} — launcher-UI 页物理键统一路由
(P1 仅音量键), 把读取边沿+防抖+长按 held+ramp 推进从 5 个会话收敛为进程内单开;
各页每圈 key_router_pump() 取防抖后边沿, 动作(调音量/光标导航)仍由调用页决定,
保留 run_menu 的 meow_mode 分支; App 前台 volume_page 武装时边沿抑制;
main_menu/now_playing/notif_center/settings_page/subpage 接入, 附 test_key_router_vol/test_np_keys
- WiFi 连接修复: meow_netmgrd connect 有界等待 wpa_state=COMPLETED(普通 10s/隐藏网络 15s,
env 可调)修设置页连接无响应; list_networks/scan_results 的 \xNN 转义还原始字节, 修中文 SSID
永不命中致新建同名 open 记录顶影; launcher netmgr_ipc 配读超时; 附 test_wifi_connect(桩
wpa_cli 驱动 static wifi_connect_ex), test_netmgrd.sh 补用例; wifi_page 同步
- ncm bench: 新增 bench/lyric_sim.c、bench/plpage_sim.c, vol_sync_sim 与 ncm core/app 同步
- SDK IME 修复: shift 大小写、K_ENTER 只提交不关、阻塞 IME 循环喂 on_tick 不被遮挡、
IME 打开 ~200ms TAP 冷却窗吞 stray-TAP; 附 sdk/test_ime_fix.c
- 同期: meow_audiod 音量异步 ACK 线程(代际防 fd/槽复用竞态)、watchdog 音量收编(首选 audiod
权威, settings.conf 覆盖路径)、panel 文件搜索(F1-④)与蓝牙只读详情(F1-③)、各类 build 脚本同步
范围红线: third_party/legado-E-main 原开发者未跟踪, 本次不入库(保留 devdocs/local tar 参考);
二进制不入库(后续重编); devdocs/ 不入库。59 文件全部在 patch_v2/ 下, 无 third_party/devdocs。
验证: git apply --check 干净; 59 文件 md5 与容器 patch_v2 逐一比对 59/59 一致(含
key_router.c/rdchapter.c/meow_ui_ime.c/test_wifi_connect.c 抽查); 可执行位保守
(check_lgpl_release.sh 100755, build_turn_repro.sh 100644 与容器一致)。
This commit is contained in:
1 parent
e87ccdf061
commit
7c7d891570
59 files changed
+5305
-590
No files matched your search
+606
-129
@@ -9,6 +9,7 @@
|
||||
#include "rdsrc.h"
|
||||
#include "rdtxt.h"
|
||||
#include "seg.h"
|
||||
#include "rdchapter.h"
|
||||
#include "rdlayout.h"
|
||||
#include "rdimg.h"
|
||||
#include "../engine/rdft.h"
|
||||
@@ -22,6 +23,17 @@ typedef struct seg {
|
||||
int page_count; /* 本段页数 */
|
||||
} seg_t;
|
||||
|
||||
/* Phase 2C:双段 cps 窗口槽——只保留段解码码点(utf8 转完即 free,不再驻留整段 lines/pages)。 */
|
||||
typedef struct cps_slot {
|
||||
int seg; /* 段下标;-1 = 空槽 */
|
||||
uint32_t *cps;
|
||||
size_t n;
|
||||
} cps_slot;
|
||||
|
||||
/* 滞回释放/预取阈值(按全书页数百分比;真机可调)。 */
|
||||
#define RD_WIN_NEAR_PCT 2 /* 距下一段界 ≤2%:预取下一段 */
|
||||
#define RD_WIN_FAR_PCT 4 /* >4% 滑出:远槽可被下一段预取替换(由距离最近者策略体现) */
|
||||
|
||||
struct rd_book {
|
||||
char path[400];
|
||||
char font[400];
|
||||
@@ -40,17 +52,28 @@ struct rd_book {
|
||||
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;
|
||||
/* Phase 2C:双段 cps 窗口(渲染微排版取码点用;替换旧单段 seg_lines/seg_pages 缓存) */
|
||||
cps_slot win[2];
|
||||
|
||||
/* Phase 2A:拼接页表滚动状态(上一段的「末页页首→段尾」码点 tail;建下一段时拼接) */
|
||||
uint32_t *tail_cps; size_t tail_n;
|
||||
int tail_seg; /* tail 所属段;-1 = 无 */
|
||||
int32_t tail_seg_char; /* tail 在所属段的起始字符偏移 */
|
||||
|
||||
/* Phase 2C:渲染微排版复用缓冲(拼接页临时码点 + 一页行缓冲,避免每帧 malloc) */
|
||||
uint32_t *stitch_cps; size_t stitch_cap;
|
||||
rd_line *render_lines; int render_lines_cap;
|
||||
|
||||
/* 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 路径增量增长) */
|
||||
|
||||
/* R3.3 章节(TXT):解析结果 + 就绪/无章节标志(延迟解析,不拖慢首屏) */
|
||||
rdchapter chaps;
|
||||
int chaps_ready; /* 1=chaps 已填充(含确无章节) */
|
||||
int chaps_none; /* 1=确无章节(扫描过 n==0)或该书不可用 */
|
||||
};
|
||||
|
||||
/* ---- 页索引持久化(MeowData;见 rd_book_set_index_dir) ----
|
||||
@@ -61,6 +84,7 @@ struct rd_book {
|
||||
* 纯 libc(无 SDK):reader.c 用 meow_config_dir 推导 MeowData dir 注入;冒烟/无卡时 dir 为空 → 直排不持久。 */
|
||||
|
||||
static char g_index_dir[400]; /* 索引缓存目录; 空 = 禁用 */
|
||||
static int seg_ensure_built(rd_book *b, int upto_seg); /* 段路径补齐(下文定义) */
|
||||
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; }
|
||||
@@ -80,7 +104,7 @@ void rd_book_set_seg_base(const char *base)
|
||||
#define IDX_MAGIC1 'I'
|
||||
#define IDX_MAGIC2 'D'
|
||||
#define IDX_MAGIC3 'X'
|
||||
#define IDX_VER 1u
|
||||
#define IDX_VER 2u
|
||||
|
||||
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; }
|
||||
@@ -253,6 +277,267 @@ static void index_cache_save(rd_book *b)
|
||||
if (ok) { rename(tmp, path); } else { remove(tmp); }
|
||||
}
|
||||
|
||||
/* ---- R3.3 章节缓存(MeowData/os.meow.reader/index/<hash>.chap,键=书字节+编码) ----
|
||||
* 章节 char_off/seg_idx/seg_char 仅依赖书字节与编码(不含字号/行距/字体/版面),故失效键
|
||||
* (file_size, file_mtime, enc) 即够,与页索引(含 font/fs/ls/cw/ch)分开、独立秒开。
|
||||
* 目录为 index 同父;g_index_dir 为空(宿主/无卡)→ 不持久,仅内存。 */
|
||||
#define CHAP_MAGIC0 'R'
|
||||
#define CHAP_MAGIC1 'C'
|
||||
#define CHAP_MAGIC2 'H'
|
||||
#define CHAP_MAGIC3 'P'
|
||||
#define CHAP_VER 1u
|
||||
|
||||
static int chap_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.chap", g_index_dir, idx_fnv1a(b->path)) >= outsz) return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 章节缓存键:书字节 + 编码(含 BOM/编码覆盖),跟页索引键不同(无字体/字号/版面)。 */
|
||||
static int chap_cache_load(rd_book *b)
|
||||
{
|
||||
char path[440];
|
||||
if (chap_cache_path(b, path, sizeof path) != 0) return 0;
|
||||
FILE *f = fopen(path, "rb");
|
||||
if (!f) return 0;
|
||||
uint8_t magic[4];
|
||||
uint16_t ver = 0;
|
||||
uint64_t fsize = 0; int64_t fmtime = 0; int32_t fenc = 0;
|
||||
int32_t nsegs = 0, nch = 0; int64_t total = 0;
|
||||
int ok = 0;
|
||||
if (fio_rd(f, magic, sizeof magic) && magic[0] == CHAP_MAGIC0 && magic[1] == CHAP_MAGIC1 &&
|
||||
magic[2] == CHAP_MAGIC2 && magic[3] == CHAP_MAGIC3 &&
|
||||
fio_rd(f, &ver, sizeof ver) && ver == CHAP_VER &&
|
||||
fio_rd(f, &fsize, sizeof fsize) && fio_rd(f, &fmtime, sizeof fmtime) &&
|
||||
fio_rd(f, &fenc, sizeof fenc) &&
|
||||
fio_rd(f, &nsegs, sizeof nsegs) && fio_rd(f, &total, sizeof total) &&
|
||||
fio_rd(f, &nch, sizeof nch) &&
|
||||
nsegs >= 0 && nch >= 0 && nsegs <= 1000000 && nch <= RDCHAP_MAX_ENTRIES) {
|
||||
struct stat st;
|
||||
if (stat(b->path, &st) == 0 && (uint64_t)st.st_size == fsize &&
|
||||
(int64_t)st.st_mtime == fmtime && (int32_t)b->enc == fenc) {
|
||||
rdchapter c; rdchapter_init(&c);
|
||||
c.seg_char_start = (int64_t *)malloc((size_t)(nsegs + 1) * sizeof(int64_t));
|
||||
c.entries = (rdchapter_entry *)malloc((size_t)(nch ? nch : 1) * sizeof(rdchapter_entry));
|
||||
if (c.seg_char_start && c.entries &&
|
||||
fio_rd(f, c.seg_char_start, (size_t)(nsegs + 1) * sizeof(int64_t))) {
|
||||
int eok = 1;
|
||||
for (int i = 0; i < nch && eok; i++) {
|
||||
rdchapter_entry *e = &c.entries[i];
|
||||
if (!fio_rd(f, &e->char_off, sizeof e->char_off) ||
|
||||
!fio_rd(f, &e->seg_idx, sizeof e->seg_idx) ||
|
||||
!fio_rd(f, &e->seg_char, sizeof e->seg_char) ||
|
||||
!fio_rd(f, &e->number, sizeof e->number) ||
|
||||
!fio_rd(f, e->title, sizeof e->title)) eok = 0;
|
||||
}
|
||||
if (eok) {
|
||||
c.n = nch; c.nsegs = nsegs;
|
||||
c.has_pre = (nch > 0 && c.entries[0].char_off == 0 && c.entries[0].number == -1) ? 1 : 0;
|
||||
snprintf(c.pre_title, sizeof c.pre_title, "%s", (nch > 0) ? c.entries[0].title : "卷首");
|
||||
b->chaps = c;
|
||||
b->chaps_ready = 1;
|
||||
b->chaps_none = (nch == 0);
|
||||
ok = 1;
|
||||
} else { rdchapter_free(&c); }
|
||||
} else { rdchapter_free(&c); }
|
||||
}
|
||||
}
|
||||
fclose(f);
|
||||
return ok;
|
||||
}
|
||||
|
||||
static void chap_cache_save(rd_book *b)
|
||||
{
|
||||
if (!b->chaps_ready || b->chaps.nsegs <= 0) return;
|
||||
char path[440];
|
||||
if (chap_cache_path(b, path, sizeof path) != 0) return;
|
||||
char tmp[480];
|
||||
if (snprintf(tmp, sizeof tmp, "%s.tmp", path) >= (int)sizeof tmp) return;
|
||||
FILE *f = fopen(tmp, "wb");
|
||||
if (!f) return;
|
||||
struct stat st;
|
||||
uint64_t fsize = 0; int64_t fmtime = 0;
|
||||
if (stat(b->path, &st) == 0) { fsize = (uint64_t)st.st_size; fmtime = (int64_t)st.st_mtime; }
|
||||
const uint8_t magic[4] = { CHAP_MAGIC0, CHAP_MAGIC1, CHAP_MAGIC2, CHAP_MAGIC3 };
|
||||
uint16_t ver = CHAP_VER;
|
||||
int32_t nsegs = b->chaps.nsegs, fenc = (int32_t)b->enc, nch = b->chaps.n;
|
||||
int64_t total = b->chaps.seg_char_start ? b->chaps.seg_char_start[nsegs] : 0;
|
||||
int ok = 1;
|
||||
ok = ok && fio_wr(f, magic, sizeof magic) && fio_wr(f, &ver, sizeof ver) &&
|
||||
fio_wr(f, &fsize, sizeof fsize) && fio_wr(f, &fmtime, sizeof fmtime) &&
|
||||
fio_wr(f, &fenc, sizeof fenc) &&
|
||||
fio_wr(f, &nsegs, sizeof nsegs) && fio_wr(f, &total, sizeof total) &&
|
||||
fio_wr(f, &nch, sizeof nch);
|
||||
if (ok && nsegs > 0) ok = fio_wr(f, b->chaps.seg_char_start, (size_t)(nsegs + 1) * sizeof(int64_t));
|
||||
if (ok && nch > 0) {
|
||||
for (int i = 0; i < nch; i++) {
|
||||
const rdchapter_entry *e = &b->chaps.entries[i];
|
||||
ok = ok && fio_wr(f, &e->char_off, sizeof e->char_off) &&
|
||||
fio_wr(f, &e->seg_idx, sizeof e->seg_idx) &&
|
||||
fio_wr(f, &e->seg_char, sizeof e->seg_char) &&
|
||||
fio_wr(f, &e->number, sizeof e->number) &&
|
||||
fio_wr(f, e->title, sizeof e->title);
|
||||
}
|
||||
}
|
||||
fclose(f);
|
||||
if (ok) { rename(tmp, path); } else { remove(tmp); }
|
||||
}
|
||||
|
||||
/* 确保章节解析(幂等):命中缓存 → 就绪;未命中 → 扫描(读全部段文件)。
|
||||
* 可靠性(R3.4):仅「扫描全程成功」才落盘(含无章节 n==0);扫描中途出错(段读/解码/内存)
|
||||
* 不落盘、本次降级无章节,下次重试。返回:0=成功(就绪);-1=不可用(非 TXT/无段目录);
|
||||
* -2=扫描出错(不落盘)。 */
|
||||
static int chapter_ensure(rd_book *b)
|
||||
{
|
||||
if (!b) return -1;
|
||||
if (b->chaps_ready) return 0;
|
||||
if (b->is_img || b->fmt != RD_FMT_TXT || !b->seg_dir[0]) {
|
||||
b->chaps_none = 1; b->chaps_ready = 1;
|
||||
return -1;
|
||||
}
|
||||
if (chap_cache_load(b)) return 0;
|
||||
int rc = rdchapter_scan(b->seg_dir, b->enc, b->base, &b->chaps);
|
||||
if (rc != 0) { b->chaps_none = 1; b->chaps_ready = 1; return -2; } /* 扫描失败:不落盘,降级无章节 */
|
||||
b->chaps_none = (b->chaps.n == 0);
|
||||
b->chaps_ready = 1;
|
||||
chap_cache_save(b); /* 可靠(成功扫描,无论有无章节)→ 落盘 */
|
||||
return 0;
|
||||
}
|
||||
|
||||
int rd_book_chapter_ready(rd_book *b) { return (b && b->chaps_ready) ? 1 : 0; }
|
||||
|
||||
/* R3.4 手动重解析本书章节:先扫描到临时结果,成功才「清旧缓存/旧内存 + 换入新结果 + 落盘」;
|
||||
* 失败则保留旧缓存/旧内存(绝不产生错位章节),不落盘、返回 -2。返回:n=章节数 / 0=无章节(成功)/
|
||||
* -1=不可用(非 TXT+seg)/ -2=扫描失败。幂等。 */
|
||||
int rd_book_reparse_chapters(rd_book *b)
|
||||
{
|
||||
if (!b) return -1;
|
||||
if (b->is_img || b->fmt != RD_FMT_TXT || !b->seg_dir[0]) return -1;
|
||||
rdchapter tmp; rdchapter_init(&tmp);
|
||||
int rc = rdchapter_scan(b->seg_dir, b->enc, b->base, &tmp);
|
||||
if (rc != 0) { rdchapter_free(&tmp); return -2; } /* 失败:保留旧缓存/旧内存,未落盘 */
|
||||
rdchapter_free(&b->chaps); /* 清除内存旧解析结果 */
|
||||
b->chaps = tmp;
|
||||
b->chaps_none = (b->chaps.n == 0);
|
||||
b->chaps_ready = 1;
|
||||
char path[440];
|
||||
if (chap_cache_path(b, path, sizeof path) == 0) remove(path); /* 清缓存文件 */
|
||||
chap_cache_save(b); /* 可靠结果 → 落盘 */
|
||||
return b->chaps.n; /* >=0 */
|
||||
}
|
||||
|
||||
int rd_book_chapter_n(rd_book *b)
|
||||
{
|
||||
if (!b) return -1;
|
||||
if (b->is_img || b->fmt != RD_FMT_TXT) return -1;
|
||||
if (chapter_ensure(b) != 0) return -1;
|
||||
return b->chaps.n;
|
||||
}
|
||||
|
||||
int rd_book_chapter(rd_book *b, int i, char *title, int tsz,
|
||||
int64_t *char_off, int *seg, int32_t *seg_char, int *number)
|
||||
{
|
||||
if (!b || !title || i < 0) return -1;
|
||||
if (chapter_ensure(b) != 0) return -1;
|
||||
if (i >= b->chaps.n || !b->chaps.entries) return -1;
|
||||
const rdchapter_entry *e = &b->chaps.entries[i];
|
||||
if (tsz > 0) { snprintf(title, (size_t)tsz, "%s", e->title); }
|
||||
if (char_off) *char_off = e->char_off;
|
||||
if (seg) *seg = e->seg_idx;
|
||||
if (seg_char) *seg_char = e->seg_char;
|
||||
if (number) *number = e->number;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int64_t rd_book_page_char_off(rd_book *b, int page)
|
||||
{
|
||||
if (!b || b->npages <= 0 || page < 0 || page >= b->npages) return -1;
|
||||
if (chapter_ensure(b) != 0) return -1;
|
||||
if (!b->chaps.seg_char_start || b->chaps.nsegs <= 0) return -1;
|
||||
int seg = b->pages[page].seg;
|
||||
if (seg < 0 || seg >= b->chaps.nsegs) return -1;
|
||||
return b->chaps.seg_char_start[seg] + b->pages[page].seg_char;
|
||||
}
|
||||
|
||||
int rd_book_page_for_char(rd_book *b, int64_t char_off)
|
||||
{
|
||||
if (!b || b->npages <= 0) return -1;
|
||||
if (chapter_ensure(b) != 0) return -1;
|
||||
int nseg = b->chaps.nsegs;
|
||||
if (!b->chaps.seg_char_start || nseg <= 0) return -1;
|
||||
int64_t total = b->chaps.seg_char_start[nseg];
|
||||
if (total <= 0) return 0;
|
||||
if (char_off < 0) char_off = 0;
|
||||
if (char_off >= total) char_off = total - 1;
|
||||
/* 二分所在段:最大的 i 使 seg_char_start[i] <= char_off */
|
||||
int lo = 0, hi = nseg - 1, s = 0;
|
||||
while (lo <= hi) {
|
||||
int mid = (lo + hi) / 2;
|
||||
if (b->chaps.seg_char_start[mid] <= char_off) { s = mid; lo = mid + 1; }
|
||||
else hi = mid - 1;
|
||||
}
|
||||
if (seg_ensure_built(b, s) != 0) return -1;
|
||||
if (s >= b->nsegs) s = b->nsegs - 1;
|
||||
int local = (int)(char_off - b->chaps.seg_char_start[s]);
|
||||
int p0 = b->segs[s].page_start, pc = b->segs[s].page_count;
|
||||
if (pc <= 0) return (p0 < b->npages) ? p0 : (b->npages - 1);
|
||||
/* 段内二分:最后一个 seg_char <= local 的页 = 含该字符的页 */
|
||||
int lo2 = p0, hi2 = p0 + pc - 1, best = p0;
|
||||
while (lo2 <= hi2) {
|
||||
int mid = (lo2 + hi2) / 2;
|
||||
if (b->pages[mid].seg_char <= local) { best = mid; lo2 = mid + 1; }
|
||||
else hi2 = mid - 1;
|
||||
}
|
||||
return (best < b->npages) ? best : (b->npages - 1);
|
||||
}
|
||||
|
||||
int rd_book_chapter_for_page(rd_book *b, int page, int *idx)
|
||||
{
|
||||
if (!b || !idx) return -1;
|
||||
if (chapter_ensure(b) != 0) return -1;
|
||||
if (b->chaps.n <= 0) { *idx = -1; return 0; }
|
||||
if (page < 0 || page >= b->npages) return -1;
|
||||
int64_t po = rd_book_page_char_off(b, page);
|
||||
if (po < 0) { *idx = -1; return 0; }
|
||||
int lo = 0, hi = b->chaps.n - 1, best = -1;
|
||||
while (lo <= hi) {
|
||||
int mid = (lo + hi) / 2;
|
||||
if (b->chaps.entries[mid].char_off <= po) { best = mid; lo = mid + 1; }
|
||||
else hi = mid - 1;
|
||||
}
|
||||
*idx = best;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ---- 段访问(文件/段模式跳转) ---- */
|
||||
int rd_book_seg_n(rd_book *b)
|
||||
{
|
||||
if (!b || b->is_img || b->fmt != RD_FMT_TXT || !b->seg_dir[0]) return -1;
|
||||
return (b->seg_n > 0) ? b->seg_n : b->nsegs;
|
||||
}
|
||||
int rd_book_seg_page(rd_book *b, int seg)
|
||||
{
|
||||
if (!b || seg < 0) return -1;
|
||||
if (b->seg_dir[0] && b->fmt == RD_FMT_TXT) {
|
||||
if (seg >= b->seg_n) return -1;
|
||||
if (seg_ensure_built(b, seg) != 0) return -1;
|
||||
if (seg >= b->nsegs) return -1;
|
||||
return b->segs[seg].page_start;
|
||||
}
|
||||
return (seg < b->nsegs) ? b->segs[seg].page_start : -1;
|
||||
}
|
||||
int rd_book_seg_page_count(rd_book *b, int seg)
|
||||
{
|
||||
if (!b || seg < 0 || seg >= b->nsegs) return -1;
|
||||
return b->segs[seg].page_count;
|
||||
}
|
||||
int rd_book_seg_of_page(rd_book *b, int page)
|
||||
{
|
||||
if (!b || b->npages <= 0 || page < 0 || page >= b->npages) return -1;
|
||||
return b->pages[page].seg;
|
||||
}
|
||||
|
||||
/* ---- 文本:建索引(逐段 layout) ---- */
|
||||
static int build_index(rd_book *b)
|
||||
{
|
||||
@@ -288,8 +573,10 @@ static int build_index(rd_book *b)
|
||||
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)); }
|
||||
memset(&b->pages[b->npages], 0, sizeof b->pages[b->npages]);
|
||||
b->pages[b->npages].seg = seg_idx;
|
||||
b->pages[b->npages].seg_char = out.pages[p].first_char;
|
||||
b->pages[b->npages].flags = (uint8_t)(out.lines[out.pages[p].first_line].para_start ? 1u : 0u);
|
||||
b->npages++;
|
||||
}
|
||||
free(lines); free(pages);
|
||||
@@ -300,41 +587,169 @@ static int build_index(rd_book *b)
|
||||
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)
|
||||
/* ---- 段解码:把段 idx 的原始字节 → utf8 → 码点(malloc 返回,调用方 free)。
|
||||
* seg 路径读 512KB 段文件;非 seg 路径按 rd_src_seg 重显。窗口/建表/tail 重建共用。 ---- */
|
||||
static int seg_load_cps(rd_book *b, int seg_idx, uint32_t **cps_out, size_t *n_out)
|
||||
{
|
||||
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++;
|
||||
if (!cps_out || !n_out) return -1;
|
||||
*cps_out = NULL; *n_out = 0;
|
||||
uint8_t *utf8 = NULL; size_t cap = 0, ulen = 0;
|
||||
if (b->seg_dir[0] && b->fmt == RD_FMT_TXT) {
|
||||
size_t rawlen = 0;
|
||||
if (rdseg_rawlen(b->seg_dir, seg_idx, &rawlen) != 0) return -4;
|
||||
cap = rawlen * 3 / 2 + 16;
|
||||
if (cap < RDTXT_SEG_UTF8_MAX + 8) cap = RDTXT_SEG_UTF8_MAX + 8;
|
||||
utf8 = (uint8_t *)malloc(cap);
|
||||
if (!utf8) return -6;
|
||||
uint8_t *raw = (uint8_t *)malloc(rawlen ? rawlen : 1);
|
||||
if (!raw) { free(utf8); return -6; }
|
||||
size_t rn = 0;
|
||||
int rc = rdseg_load(b->seg_dir, seg_idx, raw, rawlen, &rn);
|
||||
if (rc != 0) { free(raw); free(utf8); return -4; }
|
||||
int r2 = rd_to_utf8(b->enc, raw, rn, utf8, cap, &ulen);
|
||||
free(raw);
|
||||
if (r2 == RD_ENC_ERR_BUFSZ) { free(utf8); return -2; }
|
||||
if (r2 != RD_ENC_OK) { free(utf8); return -3; }
|
||||
} else {
|
||||
cap = RDTXT_SEG_UTF8_MAX + 8;
|
||||
utf8 = (uint8_t *)malloc(cap);
|
||||
if (!utf8) return -6;
|
||||
if (rd_src_seg(b->src, seg_idx, utf8, cap, &ulen) != 0) { free(utf8); return -4; }
|
||||
}
|
||||
free(lines); free(pages);
|
||||
uint32_t *cps = NULL; size_t n = 0;
|
||||
int rc = rd_utf8_to_cp(utf8, ulen, &cps, &n);
|
||||
free(utf8);
|
||||
if (rc != RD_ENC_OK) { free(cps); return -5; }
|
||||
*cps_out = cps; *n_out = n;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ---- R3.2 段路径:把 [seg_built, upto_seg] 逐段(512KB 段文件 → utf8 → 布局)补齐进 flat 页表。
|
||||
/* ---- Phase 2A:拼接页表滚动状态(tail) ---- */
|
||||
static void tail_clear(rd_book *b)
|
||||
{
|
||||
free(b->tail_cps);
|
||||
b->tail_cps = NULL;
|
||||
b->tail_n = 0;
|
||||
b->tail_seg = -1;
|
||||
b->tail_seg_char = 0;
|
||||
}
|
||||
|
||||
/* 缓存续建时 tail 不在内存(未持久化)→ 由段 seg 的末页首字符重算(读该段 cps 截取末页 tail)。 */
|
||||
static int tail_rebuild(rd_book *b, int seg)
|
||||
{
|
||||
if (b->tail_seg == seg && b->tail_cps && b->tail_n > 0) return 0;
|
||||
tail_clear(b);
|
||||
if (seg < 0 || seg >= b->seg_built) return -1;
|
||||
int pc = b->segs[seg].page_count;
|
||||
if (pc <= 0) return -1;
|
||||
int lastp = b->segs[seg].page_start + pc - 1;
|
||||
if (lastp < 0 || lastp >= b->npages) return -1;
|
||||
int32_t sc = b->pages[lastp].seg_char;
|
||||
if (sc < 0) return -1;
|
||||
uint32_t *cps = NULL; size_t n = 0;
|
||||
if (seg_load_cps(b, seg, &cps, &n) != 0) return -1;
|
||||
if ((size_t)sc > n) { free(cps); return -1; }
|
||||
b->tail_n = n - (size_t)sc;
|
||||
b->tail_cps = (uint32_t *)malloc((b->tail_n ? b->tail_n : 1) * sizeof(uint32_t));
|
||||
if (!b->tail_cps) { free(cps); return -6; }
|
||||
if (b->tail_n) memcpy(b->tail_cps, cps + sc, b->tail_n * sizeof(uint32_t));
|
||||
b->tail_seg = seg;
|
||||
b->tail_seg_char = sc;
|
||||
free(cps);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pages_push(rd_book *b, int seg, int32_t seg_char, uint8_t flags)
|
||||
{
|
||||
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) return -6;
|
||||
b->pages = np; b->pages_cap = nc;
|
||||
}
|
||||
memset(&b->pages[b->npages], 0, sizeof b->pages[b->npages]);
|
||||
b->pages[b->npages].seg = seg;
|
||||
b->pages[b->npages].seg_char = seg_char;
|
||||
b->pages[b->npages].flags = flags;
|
||||
b->npages++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ---- Phase 2A:滚动拼接。把 seg_idx 段码点布局并追加到 flat 页表。
|
||||
* 建 N+1 时:删 N 的临时末页 → tail_cps + cps_new 拼接重排 → 首张拼接页回填 N(锚不变),
|
||||
* 其余页按 first_char − tail_n 归入 N+1。末段/首段无 tail 时按整段直排。 ---- */
|
||||
static int append_seg_pages(rd_book *b, int seg_idx, const uint32_t *cps, size_t n)
|
||||
{
|
||||
int have_tail = (seg_idx > 0 && b->tail_seg == seg_idx - 1 && b->tail_cps && b->tail_n > 0);
|
||||
if (have_tail) {
|
||||
if (b->segs[seg_idx - 1].page_count <= 0) return -4;
|
||||
b->npages--; /* 删上一段临时末页(拼接页原位回填) */
|
||||
b->segs[seg_idx - 1].page_count--;
|
||||
}
|
||||
|
||||
size_t stitched_n = have_tail ? b->tail_n + n : n;
|
||||
uint32_t *scps = NULL;
|
||||
const uint32_t *chars;
|
||||
if (have_tail) {
|
||||
scps = (uint32_t *)malloc((stitched_n ? stitched_n : 1) * sizeof(uint32_t));
|
||||
if (!scps) return -6;
|
||||
memcpy(scps, b->tail_cps, b->tail_n * sizeof(uint32_t));
|
||||
memcpy(scps + b->tail_n, cps, n * sizeof(uint32_t));
|
||||
chars = scps;
|
||||
} else {
|
||||
chars = cps;
|
||||
}
|
||||
|
||||
rd_line *lines = (rd_line *)malloc((stitched_n ? stitched_n : 1) * sizeof(rd_line));
|
||||
rd_page *pages = (rd_page *)malloc((stitched_n ? stitched_n : 1) * sizeof(rd_page));
|
||||
if (!lines || !pages) { free(lines); free(pages); free(scps); return -6; }
|
||||
rd_layout_in in;
|
||||
rd_layout_out out;
|
||||
in.chars = chars; in.n = (int32_t)stitched_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)(stitched_n ? stitched_n : 1); out.max_pages = (int)(stitched_n ? stitched_n : 1);
|
||||
int lrc = rd_layout(&in, &out);
|
||||
if (lrc != RD_LAYOUT_OK) { free(lines); free(pages); free(scps); return -4; }
|
||||
|
||||
int first_own = 0;
|
||||
if (have_tail) {
|
||||
if (out.npages < 1) { free(lines); free(pages); free(scps); return -4; }
|
||||
int rc = pages_push(b, seg_idx - 1, b->tail_seg_char,
|
||||
(uint8_t)(out.lines[out.pages[0].first_line].para_start ? 1u : 0u));
|
||||
if (rc != 0) { free(lines); free(pages); free(scps); return rc; }
|
||||
b->segs[seg_idx - 1].page_count++;
|
||||
first_own = 1;
|
||||
}
|
||||
b->segs[seg_idx].page_start = b->npages;
|
||||
b->segs[seg_idx].page_count = 0;
|
||||
for (int p = first_own; p < out.npages; p++) {
|
||||
int32_t local = out.pages[p].first_char - (have_tail ? (int32_t)b->tail_n : 0);
|
||||
if (local < 0) local = 0;
|
||||
int rc = pages_push(b, seg_idx, local,
|
||||
(uint8_t)(out.lines[out.pages[p].first_line].para_start ? 1u : 0u));
|
||||
if (rc != 0) { free(lines); free(pages); free(scps); return rc; }
|
||||
b->segs[seg_idx].page_count++;
|
||||
}
|
||||
|
||||
/* 保存本段末页 tail(供建下一段拼接;末段也保存但不会用到)。 */
|
||||
tail_clear(b);
|
||||
if (b->segs[seg_idx].page_count > 0) {
|
||||
int lastp = b->segs[seg_idx].page_start + b->segs[seg_idx].page_count - 1;
|
||||
int32_t sc = b->pages[lastp].seg_char;
|
||||
if (sc < 0 || (size_t)sc > n) { free(lines); free(pages); free(scps); return -4; }
|
||||
b->tail_n = n - (size_t)sc;
|
||||
b->tail_cps = (uint32_t *)malloc((b->tail_n ? b->tail_n : 1) * sizeof(uint32_t));
|
||||
if (!b->tail_cps) { b->tail_n = 0; free(lines); free(pages); free(scps); return -6; }
|
||||
if (b->tail_n) memcpy(b->tail_cps, cps + sc, b->tail_n * sizeof(uint32_t));
|
||||
b->tail_seg = seg_idx;
|
||||
b->tail_seg_char = sc;
|
||||
}
|
||||
free(lines); free(pages); free(scps);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ---- R3.2 段路径:把 [seg_built, upto_seg] 逐段(512KB 段文件 → cps → 拼接布局)补齐进 flat 页表。
|
||||
* 幂等:已建段跳过。开书只建到目标(页 0/保存进度),翻页/跳转再按需补齐 —— 免整书一次性布局。 */
|
||||
static int seg_ensure_built(rd_book *b, int upto_seg)
|
||||
{
|
||||
@@ -345,8 +760,6 @@ static int seg_ensure_built(rd_book *b, int upto_seg)
|
||||
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;
|
||||
@@ -354,24 +767,13 @@ static int seg_ensure_built(rd_book *b, int upto_seg)
|
||||
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;
|
||||
if (idx > 0 && tail_rebuild(b, idx - 1) != 0) return -4;
|
||||
uint32_t *cps = NULL; size_t n = 0;
|
||||
int rc = seg_load_cps(b, idx, &cps, &n);
|
||||
if (rc != 0) return rc;
|
||||
rc = append_seg_pages(b, idx, cps, n);
|
||||
free(cps);
|
||||
if (rc != 0) return rc;
|
||||
b->seg_built++;
|
||||
built_any = 1;
|
||||
}
|
||||
@@ -380,59 +782,53 @@ static int seg_ensure_built(rd_book *b, int upto_seg)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ---- 正文重显 + 布局到复用缓冲 ---- */
|
||||
static int load_seg(rd_book *b, int seg_idx)
|
||||
/* ---- Phase 2C:双段 cps 窗口(渲染微排版取码点;替换旧单段整段 lines/pages 缓存) ---- */
|
||||
static void win_clear(rd_book *b)
|
||||
{
|
||||
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;
|
||||
for (int i = 0; i < 2; i++) {
|
||||
free(b->win[i].cps);
|
||||
b->win[i].cps = NULL;
|
||||
b->win[i].n = 0;
|
||||
b->win[i].seg = -1;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
static cps_slot *win_find(rd_book *b, int seg)
|
||||
{
|
||||
if (b->win[0].seg == seg) return &b->win[0];
|
||||
if (b->win[1].seg == seg) return &b->win[1];
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int win_load_seg(rd_book *b, int seg_idx, cps_slot *slot)
|
||||
{
|
||||
uint32_t *cps = NULL; size_t n = 0;
|
||||
int rc = seg_load_cps(b, seg_idx, &cps, &n);
|
||||
if (rc != 0) return rc;
|
||||
free(slot->cps);
|
||||
slot->cps = cps; slot->n = n; slot->seg = seg_idx;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 确保段 seg_idx 码点在窗口内。双槽都占时,淘汰「离 seg_idx 更远」的槽(距相等淘汰更小段下标,
|
||||
* 倾向保留下一段);滞回释放由 reader_on_tick 的 2%/4% 预取门控体现。 */
|
||||
static int win_ensure(rd_book *b, int seg_idx)
|
||||
{
|
||||
if (seg_idx < 0 || seg_idx >= b->nsegs) return -1;
|
||||
if (win_find(b, seg_idx)) return 0;
|
||||
int victim = -1;
|
||||
if (b->win[0].seg < 0) victim = 0;
|
||||
else if (b->win[1].seg < 0) victim = 1;
|
||||
else {
|
||||
int d0 = b->win[0].seg - seg_idx; if (d0 < 0) d0 = -d0;
|
||||
int d1 = b->win[1].seg - seg_idx; if (d1 < 0) d1 = -d1;
|
||||
if (d0 > d1) victim = 0;
|
||||
else if (d1 > d0) victim = 1;
|
||||
else victim = (b->win[0].seg < b->win[1].seg) ? 0 : 1;
|
||||
}
|
||||
return win_load_seg(b, seg_idx, &b->win[victim]);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
@@ -465,7 +861,8 @@ int rd_book_open(rd_book **b, const char *path, const char *font_path,
|
||||
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;
|
||||
x->win[0].seg = x->win[1].seg = -1;
|
||||
x->tail_seg = -1;
|
||||
int seg_mode = 0;
|
||||
/* R3.2 分段流式(TXT 且段 base 已配置):开书切 512KB 段文件,逐段按需布局(不全量)。 */
|
||||
if (g_seg_base[0] && x->fmt == RD_FMT_TXT) {
|
||||
@@ -513,10 +910,11 @@ 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);
|
||||
win_clear(b);
|
||||
tail_clear(b);
|
||||
free(b->stitch_cps);
|
||||
free(b->render_lines);
|
||||
rdchapter_free(&b->chaps);
|
||||
if (b->ft) rdft_free(b->ft);
|
||||
if (b->src) rd_src_free(b->src);
|
||||
free(b);
|
||||
@@ -563,6 +961,8 @@ int rd_book_set_enc(rd_book *b, rd_enc_t 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;
|
||||
win_clear(b); /* 编码变了 → 码点空间不同,窗口/tail 全失效 */
|
||||
tail_clear(b);
|
||||
if (b->seg_dir[0]) {
|
||||
/* R3.2 段路径:编码变了 → 段文件边界/解码不同 → 重建段文件 + 按需布局(段 0 起步) */
|
||||
if (oldenc != b->enc) rdseg_clear(b->seg_dir);
|
||||
@@ -615,27 +1015,104 @@ int rd_book_set_page(rd_book *b, int page)
|
||||
return page;
|
||||
}
|
||||
|
||||
/* Phase 2C:空闲预取下一段(reader_on_tick 调)。当前页距下一段界 ≤2% 时把 s+1 段码点
|
||||
* 提前解码进窗口(命中/未到阈值则幂等返回),翻到段界即零突发。 */
|
||||
int rd_book_prefetch(rd_book *b)
|
||||
{
|
||||
if (!b || b->is_img || b->npages <= 0) return -1;
|
||||
if (!(b->seg_dir[0] && b->fmt == RD_FMT_TXT)) return 0; /* 仅 TXT 段路径有段界预取意义 */
|
||||
int cur = b->cur; if (cur < 0) cur = 0; if (cur >= b->npages) cur = b->npages - 1;
|
||||
int s = b->pages[cur].seg;
|
||||
if (s < 0 || s + 1 >= b->nsegs) return 0;
|
||||
int bnd = b->segs[s + 1].page_start;
|
||||
int d = cur - bnd; if (d < 0) d = -d;
|
||||
if ((long long)d * 100 / b->npages > RD_WIN_NEAR_PCT) return 0;
|
||||
return win_ensure(b, s + 1);
|
||||
}
|
||||
|
||||
/* ---- Phase 2C:页级微排版。把 page_no 从页首字符起只排一页(复用 rd_layout_page)。
|
||||
* 拼接页(TXT 段界末页)在窗口两段 cps 相接的临时缓冲上排,天然连续跨段。
|
||||
* 输出:*chars 与 *chars_n = 绘制用码点视图(窗口 cps 或 b->stitch_cps,下次调用前勿 free),
|
||||
* b->render_lines 填行;*nlines 行数。返回 0=成功,1=空页,负=错误。 */
|
||||
static int page_micro_layout(rd_book *b, int page_no, const uint32_t **chars, size_t *chars_n, int *nlines)
|
||||
{
|
||||
*chars = NULL; *chars_n = 0; *nlines = 0;
|
||||
rd_book_page *pg = &b->pages[page_no];
|
||||
int seg_idx = (int)pg->seg;
|
||||
if (seg_idx < 0 || seg_idx >= b->nsegs) return -1;
|
||||
|
||||
int stitched = (b->seg_dir[0] && b->fmt == RD_FMT_TXT) &&
|
||||
(page_no == b->segs[seg_idx].page_start + b->segs[seg_idx].page_count - 1) &&
|
||||
(seg_idx + 1 < b->nsegs);
|
||||
if (win_ensure(b, seg_idx) != 0) return -2;
|
||||
if (stitched && win_ensure(b, seg_idx + 1) != 0) return -2;
|
||||
|
||||
int lh = (b->fs * b->ls) / 100; if (lh < 1) lh = 1;
|
||||
int max_lines = b->content_h / lh + 2;
|
||||
if (b->render_lines_cap < max_lines) {
|
||||
rd_line *t = (rd_line *)realloc(b->render_lines, (size_t)max_lines * sizeof(rd_line));
|
||||
if (!t) return -6;
|
||||
b->render_lines = t; b->render_lines_cap = max_lines;
|
||||
}
|
||||
|
||||
cps_slot *s0 = win_find(b, seg_idx);
|
||||
const uint32_t *view; size_t view_n; int32_t start;
|
||||
if (!stitched) {
|
||||
view = s0->cps; view_n = s0->n;
|
||||
start = pg->seg_char;
|
||||
if (start < 0) start = 0;
|
||||
if ((size_t)start > view_n) start = (int32_t)view_n;
|
||||
} else {
|
||||
cps_slot *s1 = win_find(b, seg_idx + 1);
|
||||
int32_t tail = pg->seg_char; if (tail < 0) tail = 0;
|
||||
if ((size_t)tail > s0->n) return 1;
|
||||
size_t tail_n = s0->n - (size_t)tail;
|
||||
/* 拼接页只吃「一页能装下」的下一段前缀;按一页行数×行宽×2(零宽字符余量)估上界。 */
|
||||
size_t budget = (size_t)(max_lines + 2) * (size_t)(b->content_w + 2) * 2u + 64u;
|
||||
size_t take = s1->n < budget ? s1->n : budget;
|
||||
size_t total = tail_n + take;
|
||||
if (b->stitch_cap < total) {
|
||||
uint32_t *t = (uint32_t *)realloc(b->stitch_cps, (total ? total : 1) * sizeof(uint32_t));
|
||||
if (!t) return -6;
|
||||
b->stitch_cps = t; b->stitch_cap = total;
|
||||
}
|
||||
memcpy(b->stitch_cps, s0->cps + tail, tail_n * sizeof(uint32_t));
|
||||
memcpy(b->stitch_cps + tail_n, s1->cps, take * sizeof(uint32_t));
|
||||
view = b->stitch_cps; view_n = total;
|
||||
start = 0;
|
||||
}
|
||||
|
||||
int mid_para = !(pg->flags & 1u);
|
||||
rd_layout_in in;
|
||||
in.chars = view; in.n = (int32_t)view_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;
|
||||
int32_t next_start = 0;
|
||||
int rc = rd_layout_page(&in, start, mid_para, b->render_lines, max_lines, nlines, &next_start);
|
||||
if (rc == RD_LAYOUT_ERR_OVER) return -4;
|
||||
if (rc != RD_LAYOUT_OK) return -3;
|
||||
*chars = view; *chars_n = view_n;
|
||||
return (*nlines > 0) ? 0 : 1;
|
||||
}
|
||||
|
||||
/* ---- 文本页渲染(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];
|
||||
const uint32_t *chars = NULL; size_t chars_n = 0; int nlines = 0;
|
||||
int rc = page_micro_layout(b, page_no, &chars, &chars_n, &nlines);
|
||||
if (rc != 0) return rc;
|
||||
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];
|
||||
for (int li = 0; li < nlines; li++) {
|
||||
rd_line *ln = &b->render_lines[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];
|
||||
if (ci < 0 || (size_t)ci >= chars_n) continue;
|
||||
uint32_t cp = chars[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);
|
||||
@@ -704,6 +1181,8 @@ int rd_book_jump_to_anchor(rd_book *b, int seg, int32_t ch, int fs, int 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);
|
||||
win_clear(b); /* 页表重建 → 窗口/tail 状态整体复位(码点可复用,但按重建统一清) */
|
||||
tail_clear(b);
|
||||
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;
|
||||
@@ -740,22 +1219,20 @@ int rd_book_jump_to_anchor(rd_book *b, int seg, int32_t ch, int fs, int ls)
|
||||
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];
|
||||
const uint32_t *chars = NULL; size_t chars_n = 0; int nlines = 0;
|
||||
int rc = page_micro_layout(b, page_no, &chars, &chars_n, &nlines);
|
||||
if (rc != 0) return rc;
|
||||
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];
|
||||
for (int li = 0; li < nlines; li++) {
|
||||
rd_line *ln = &b->render_lines[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++) {
|
||||
if (ci < 0 || (size_t)ci >= chars_n) continue;
|
||||
uint8_t tmp[4];
|
||||
int n = rd_cp_to_utf8(b->seg_cps[ci], tmp);
|
||||
int n = rd_cp_to_utf8(chars[ci], tmp);
|
||||
if (o + (size_t)n + 1 > (size_t)(b->content_w * 4 + 32)) break;
|
||||
memcpy(line_utf8 + o, tmp, (size_t)n); o += (size_t)n;
|
||||
}
|
||||
line_utf8[o] = 0;
|
||||
|
||||
@@ -24,6 +24,7 @@ typedef struct rd_book rd_book;
|
||||
typedef struct rd_book_page {
|
||||
int32_t seg; /* 段下标 */
|
||||
int32_t seg_char; /* 段内字符偏移(页首) */
|
||||
uint8_t flags; /* bit0 = 首页行是否段首行(缩进/段距判定;Phase 2A/2C) */
|
||||
} rd_book_page;
|
||||
|
||||
/* 打开书:path=TXT;font_path=FreeType 字体;geom=版面(fs 字号 / ls 行距% / pw,ph 全屏 /
|
||||
@@ -36,6 +37,7 @@ 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_set_page(rd_book *b, int page); /* 跳到绝对页;clamp;返回实际页(段刮擦用) */
|
||||
int rd_book_prefetch(rd_book *b); /* 空闲预取下一段(reader_on_tick 调);0=已备/无需 */
|
||||
int rd_book_is_image(rd_book *b); /* 1=CBZ 图片页书 */
|
||||
|
||||
/* 目录:0=无;i 项标题写到 title,目标段/字符偏移填 *seg 与 *ch。 */
|
||||
@@ -73,6 +75,25 @@ void rd_book_set_index_dir(const char *dir);
|
||||
* 码位对齐段文件,渲染只加载当前页所在段(不全量读整书)——治大书开书 CPU100%/返回无效。 */
|
||||
void rd_book_set_seg_base(const char *base);
|
||||
|
||||
/* R3.3(TXT 章节):章节规范解析(core/rdchapter.c)+ 章节→页映射。
|
||||
* 只对 TXT(seg_mode 分段流式路径)生效;其它格式返回「无章节」,不破坏既有路径。
|
||||
* rd_book_chapter_n 触发解析(首次读全部段文件解码扫行,或命中章节缓存秒回),
|
||||
* 解析/缓存后就绪;顶栏章节名与「当前页属于哪章」据此工作。 */
|
||||
int rd_book_chapter_n(rd_book *b); /* 章节数;-1=非 TXT/CBZ(不可用) */
|
||||
int rd_book_chapter_ready(rd_book *b); /* 1=章节已解析(含无章节=0 但已确认) */
|
||||
int rd_book_reparse_chapters(rd_book *b); /* 手动重解析:清除缓存+重扫+落盘;见 book.c */
|
||||
int rd_book_chapter(rd_book *b, int i, char *title, int tsz,
|
||||
int64_t *char_off, int *seg, int32_t *seg_char, int *number);
|
||||
int rd_book_chapter_for_page(rd_book *b, int page, int *idx); /* 当前页所属章节下标;-1=无 */
|
||||
int rd_book_page_for_char(rd_book *b, int64_t char_off); /* 全书字符偏移 → 页号;-1=不可用 */
|
||||
int64_t rd_book_page_char_off(rd_book *b, int page); /* 页首全书字符偏移;-1=不可用 */
|
||||
|
||||
/* 段(文件/段模式跳转用)。非段模式返回 -1。 */
|
||||
int rd_book_seg_n(rd_book *b); /* 段数 */
|
||||
int rd_book_seg_page(rd_book *b, int seg); /* 段首页(ensure built);-1 */
|
||||
int rd_book_seg_page_count(rd_book *b, int seg); /* 段页数;-1 */
|
||||
int rd_book_seg_of_page(rd_book *b, int page); /* 页所属段;-1 */
|
||||
|
||||
/* 字体/几何 */
|
||||
int rd_book_font(rd_book *b); /* 当前字号 */
|
||||
int rd_book_ls(rd_book *b); /* 行距% */
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "book.h"
|
||||
#include "progress.h"
|
||||
#include "reader_conf.h"
|
||||
#include "rdchapter.h" /* RDCHAP_TITLE_MAX(章节标题) */
|
||||
#include <dirent.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
@@ -18,7 +19,7 @@
|
||||
#include <time.h>
|
||||
|
||||
#define RD_MENU_H 64
|
||||
#define RD_MENU_BTN 4 /* 返回 | 目录 | 界面 | 设置(MVP 单条) */
|
||||
#define RD_MENU_BTN 5 /* 返回 | 目录 | 跳转 | 界面 | 设置(R3.3 加「跳转」) */
|
||||
#define RD_HEADER_H 30 /* 页头条高(书名+章节名) */
|
||||
#define RD_FOOT_H 18 /* 页底条高(章节+页码) */
|
||||
#define RD_UI_H 268 /* 「界面」排版面板高 */
|
||||
@@ -36,6 +37,13 @@ static int g_menu_open;
|
||||
static int g_ui_open; /* 「界面」排版面板(B) */
|
||||
static int g_dir_open; /* 目录覆盖层 */
|
||||
static int g_dir_top, g_dir_row_h; /* 目录列表几何(rdsrc 目录项) */
|
||||
static int g_jump_open; /* 跳转覆盖层(R3.3) */
|
||||
static int g_jump_mode; /* 1=章节模式; 2=文件/段模式; 0=未定 */
|
||||
static int g_jump_sel; /* 当前选中章节/段下标 */
|
||||
static int g_jump_scroll; /* 列表滚动(首行项下标) */
|
||||
static char g_jump_buf[8]; /* 章内/段内页码输入缓冲(ASCII 数字) */
|
||||
static int g_chap_cache_idx = -1; /* 章节范围缓存(章起/止页;随字号/行距重排失效) */
|
||||
static int g_chap_cache_start, g_chap_cache_end;
|
||||
static char g_font_path[400];
|
||||
static int g_notice_ms;
|
||||
|
||||
@@ -48,12 +56,13 @@ static int g_anim_dir; /* 1=下一页(旧左移/新右入);-
|
||||
#define RD_SCRUB_W 26 /* 右缘刮擦条宽度 */
|
||||
static int g_scrub; /* 1=刮擦中 */
|
||||
static int g_scrub_page; /* 刮擦目标页(实时预览用) */
|
||||
static int g_scrub_moved; /* 本次刮擦是否发生了拖动(无拖动=点按,不当翻页跳页) */
|
||||
|
||||
/* ---- R3 编码切换(TXT;Big5 繁体书手动选定) ---- */
|
||||
static int g_enc_idx; /* g_enc_cycle[] 下标(当前有效编码) */
|
||||
|
||||
static uint32_t g_page_bg, g_page_fg; /* 正文页底/字色(24bit) */
|
||||
static char g_chapter[128]; /* 顶栏章节名(TXT 无章节=文件名) */
|
||||
static char g_chapter[RDCHAP_TITLE_MAX]; /* 顶栏章节名(TXT 无章节=文件名) */
|
||||
static int g_show_header, g_show_footer; /* 页头条/底条显隐(rdconf.rd.header/footer) */
|
||||
static volatile int g_volpage_pending; /* 系统翻页信号待消费(IPC 线程置位,UI tick 消费,E) */
|
||||
static int g_prog_dirty; /* 阅读进度变更待写(翻页/刮擦/字号/编码;R3.2 定时落盘防崩溃丢进度) */
|
||||
@@ -168,7 +177,21 @@ static const char *resolve_font(void)
|
||||
|
||||
static void chapter_name(void)
|
||||
{
|
||||
/* TXT 无章节 → 文件名;R1 固定。 */
|
||||
/* TXT 有章节(已解析)→ 当前页所属章名;否则文件名(R1 固定)。
|
||||
* 仅在章节已就绪时用章名(rd_book_chapter_ready 不触发扫描),首屏/未解析时用文件名,
|
||||
* 避免开书即整书扫描占 UI 线程。 */
|
||||
if (!g_book) { g_chapter[0] = 0; return; }
|
||||
if (rd_book_chapter_ready(g_book) && !rd_book_is_image(g_book)) {
|
||||
int page = rd_book_current(g_book);
|
||||
int idx = -1;
|
||||
if (rd_book_chapter_for_page(g_book, page, &idx) == 0 && idx >= 0) {
|
||||
char t[RDCHAP_TITLE_MAX];
|
||||
if (rd_book_chapter(g_book, idx, t, sizeof t, NULL, NULL, NULL, NULL) == 0) {
|
||||
snprintf(g_chapter, sizeof g_chapter, "%s", t);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
snprintf(g_chapter, sizeof g_chapter, "%s", rd_book_basename(g_book));
|
||||
}
|
||||
|
||||
@@ -176,6 +199,7 @@ static void reader_reflow(void)
|
||||
{
|
||||
if (!g_book) return;
|
||||
if (g_anim) { meow_anim_cancel(g_anim); g_anim = NULL; } /* 重排/雷字号时终止翻页动画 */
|
||||
g_chap_cache_idx = -1; /* 页索引随字号/行距变 → 章范围缓存失效 */
|
||||
int seg = 0; int32_t ch = 0;
|
||||
rd_book_anchor(g_book, &seg, &ch); /* 记录当前锚,重排后回锚 */
|
||||
g_prog_dirty = 1;
|
||||
@@ -266,6 +290,354 @@ static void reader_draw_scrub(const meow_theme *th)
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------------- R3.3 跳转弹层(章节模式 / 文件·段模式) ----------------
|
||||
* 复用 g_dir 系列「半透明底 + 条目列表 + 当前高亮」;顶部提供:当前章节/段信息、页码输入
|
||||
* (自绘数字键盘 0-9 + ⌫ + 跳)、快捷(首 / ±10 / ±100 / 末)。只对 TXT(seg 模式)启用:
|
||||
* rd_book_chapter_n>0 → 章节模式;否则 rd_book_seg_n>0 → 文件/段模式。 */
|
||||
#define J_SHORTCUT_N 6
|
||||
#define J_SHORTCUT_X0 16
|
||||
#define J_SHORTCUT_GAP 6
|
||||
#define J_SHORTCUT_H 32
|
||||
#define J_KEY_W 64
|
||||
#define J_KEY_H 42
|
||||
#define J_KEY_GAP 8
|
||||
#define J_ROW 30
|
||||
enum {
|
||||
J_NONE = 0,
|
||||
J_GO_FIRST = 1, J_GO_M100 = 2, J_GO_M10 = 3,
|
||||
J_GO_P10 = 4, J_GO_P100 = 5, J_GO_LAST = 6,
|
||||
J_DIG_0 = 10, J_DIG_1 = 11, J_DIG_2 = 12, J_DIG_3 = 13, J_DIG_4 = 14,
|
||||
J_DIG_5 = 15, J_DIG_6 = 16, J_DIG_7 = 17, J_DIG_8 = 18, J_DIG_9 = 19,
|
||||
J_BACK = 30, J_JUMP = 31,
|
||||
J_ITEM = 100, /* 100 + i = 列表第 i 项 */
|
||||
};
|
||||
|
||||
static int jump_hdr_y(void) { return g_top; }
|
||||
static int jump_info_y(void) { return g_top + 30; }
|
||||
static int jump_shortcut_y(void){ return g_top + 56; }
|
||||
static int jump_input_y(void) { return g_top + 92; }
|
||||
static int jump_pad_y(void) { return g_top + 128; }
|
||||
static int jump_list_y(void) { return g_top + 128 + 4 * (J_KEY_H + J_KEY_GAP) + 10; }
|
||||
|
||||
static int chapter_range(int idx, int *start, int *end)
|
||||
{
|
||||
if (!g_book || idx < 0) return -1;
|
||||
if (g_chap_cache_idx == idx) { if (start) *start = g_chap_cache_start; if (end) *end = g_chap_cache_end; return 0; }
|
||||
int64_t co = 0;
|
||||
if (rd_book_chapter(g_book, idx, NULL, 0, &co, NULL, NULL, NULL) != 0) return -1;
|
||||
int s = rd_book_page_for_char(g_book, co);
|
||||
if (s < 0) return -1;
|
||||
int n = rd_book_chapter_n(g_book);
|
||||
int e;
|
||||
if (idx + 1 < n) {
|
||||
int64_t co2 = 0;
|
||||
if (rd_book_chapter(g_book, idx + 1, NULL, 0, &co2, NULL, NULL, NULL) != 0) return -1;
|
||||
e = rd_book_page_for_char(g_book, co2) - 1;
|
||||
} else {
|
||||
e = rd_book_pages(g_book) - 1;
|
||||
}
|
||||
if (e < s) e = s;
|
||||
g_chap_cache_idx = idx; g_chap_cache_start = s; g_chap_cache_end = e;
|
||||
if (start) *start = s;
|
||||
if (end) *end = e;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 跳转选中项 idx 的页区间(章节模式=章范围;文件模式=段范围)。 */
|
||||
static int jump_region(int idx, int *start, int *end, int *inp, int *ofp)
|
||||
{
|
||||
if (!g_book) return -1;
|
||||
int s, e;
|
||||
if (g_jump_mode == 1) {
|
||||
if (chapter_range(idx, &s, &e) != 0) return -1;
|
||||
} else if (g_jump_mode == 2) {
|
||||
int nseg = rd_book_seg_n(g_book);
|
||||
if (idx < 0 || idx >= nseg) return -1;
|
||||
s = rd_book_seg_page(g_book, idx);
|
||||
if (s < 0) return -1;
|
||||
e = s + rd_book_seg_page_count(g_book, idx) - 1;
|
||||
if (e < s) e = s;
|
||||
} else return -1;
|
||||
int cur = rd_book_current(g_book);
|
||||
if (cur < s) cur = s;
|
||||
if (cur > e) cur = e;
|
||||
if (start) *start = s;
|
||||
if (end) *end = e;
|
||||
if (inp) *inp = cur - s + 1;
|
||||
if (ofp) *ofp = e - s + 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 页底条右侧文本:章内 y/Z · 全书 x/M(章节就绪且范围可算时),否则 全书 x/M。 */
|
||||
static void footer_progress(char *out, int outsz)
|
||||
{
|
||||
int np = rd_book_pages(g_book);
|
||||
int page = rd_book_current(g_book);
|
||||
if (np <= 0) { snprintf(out, outsz, "0/0"); return; }
|
||||
if (rd_book_chapter_ready(g_book) && !rd_book_is_image(g_book)) {
|
||||
int idx = -1;
|
||||
if (rd_book_chapter_for_page(g_book, page, &idx) == 0 && idx >= 0) {
|
||||
int s = -1, e = -1;
|
||||
if (chapter_range(idx, &s, &e) == 0 && e >= s) {
|
||||
int inp = page - s + 1, ofp = e - s + 1;
|
||||
if (inp > 0 && ofp > 0) { snprintf(out, outsz, "%d/%d · %d/%d", inp, ofp, page + 1, np); return; }
|
||||
}
|
||||
}
|
||||
}
|
||||
snprintf(out, outsz, "%d/%d", page + 1, np);
|
||||
}
|
||||
|
||||
static void jump_chip(int x, int y, int w, int h, const char *label, int active, const meow_theme *th)
|
||||
{
|
||||
if (active) meow_ui_fill_rect(x, y, w, h, th->primaryContainer);
|
||||
else meow_ui_fill_rect(x, y, w, h, th->surfaceContainerHighest);
|
||||
uint32_t tc = active ? th->onPrimaryContainer : th->fg;
|
||||
meow_ui_text_center(x + w / 2, y + (h - 20) / 2, label, 20, tc);
|
||||
}
|
||||
|
||||
static void reader_draw_jump(const meow_theme *th)
|
||||
{
|
||||
if (!g_book) return;
|
||||
int page = rd_book_current(g_book);
|
||||
int np = rd_book_pages(g_book);
|
||||
int n = (g_jump_mode == 1) ? rd_book_chapter_n(g_book) : rd_book_seg_n(g_book);
|
||||
meow_ui_fill_rect_alpha(0, g_top, g_pw, g_ph - g_top, 0x000000, 150);
|
||||
|
||||
/* 头栏 */
|
||||
int y0 = jump_hdr_y();
|
||||
meow_ui_fill_rect(0, y0, g_pw, 30, th->surfaceContainer);
|
||||
char hdr[48];
|
||||
snprintf(hdr, sizeof hdr, "跳转 · %s", (g_jump_mode == 1) ? "章节" : "文件/段");
|
||||
meow_ui_text(16, y0 + 5, hdr, 20, th->onSurface);
|
||||
meow_ui_draw_rect(0, y0 + 29, g_pw, 1, th->outlineVariant);
|
||||
|
||||
if (n <= 0) {
|
||||
meow_ui_text(20, jump_info_y() + 12, "本书无可用跳转目标", 20, th->dim);
|
||||
meow_ui_text(20, g_ph - 30, "点空白关闭", 16, th->dim);
|
||||
return;
|
||||
}
|
||||
|
||||
/* 信息行 */
|
||||
char info[256]; info[0] = 0;
|
||||
int s, e, inp, ofp;
|
||||
if (g_jump_mode == 1) {
|
||||
char lab[RDCHAP_TITLE_MAX];
|
||||
int num = -1;
|
||||
if (rd_book_chapter(g_book, g_jump_sel, lab, sizeof lab, NULL, NULL, NULL, &num) == 0 &&
|
||||
jump_region(g_jump_sel, &s, &e, &inp, &ofp) == 0) {
|
||||
char prefix[24]; prefix[0] = 0;
|
||||
if (num >= 0) snprintf(prefix, sizeof prefix, "第%d章 ", num);
|
||||
snprintf(info, sizeof info, "%s%s · 章内 %d/%d · 全书 %d/%d", prefix, lab, inp, ofp, page + 1, np);
|
||||
}
|
||||
} else {
|
||||
if (jump_region(g_jump_sel, &s, &e, &inp, &ofp) == 0)
|
||||
snprintf(info, sizeof info, "段 %d/%d · 段内 %d/%d · 全书 %d/%d", g_jump_sel + 1, n, inp, ofp, page + 1, np);
|
||||
}
|
||||
meow_ui_text(16, jump_info_y() + 3, info[0] ? info : "—", 16, th->fg);
|
||||
|
||||
/* 快捷 chips */
|
||||
static const char *sc_lbl[J_SHORTCUT_N] = { "首", "-100", "-10", "+10", "+100", "末" };
|
||||
int x = J_SHORTCUT_X0;
|
||||
int cw = (g_pw - 2 * J_SHORTCUT_X0 - (J_SHORTCUT_N - 1) * J_SHORTCUT_GAP) / J_SHORTCUT_N;
|
||||
for (int i = 0; i < J_SHORTCUT_N; i++) {
|
||||
jump_chip(x, jump_shortcut_y(), cw, J_SHORTCUT_H, sc_lbl[i], 0, th);
|
||||
x += cw + J_SHORTCUT_GAP;
|
||||
}
|
||||
|
||||
/* 输入行 */
|
||||
meow_ui_text(16, jump_input_y() + 5, (g_jump_mode == 1) ? "章内页" : "段内页", 20, th->fg);
|
||||
char ib[48];
|
||||
snprintf(ib, sizeof ib, "%s", g_jump_buf[0] ? g_jump_buf : "_");
|
||||
meow_ui_text(120, jump_input_y() + 3, ib, 24, th->primary);
|
||||
meow_ui_text_right(g_pw - 16, jump_input_y() + 7, "输页码点「跳」 · ⌫退格", 16, th->dim);
|
||||
|
||||
/* 数字键盘 1..9 / ⌫ 0 跳 */
|
||||
static const char *pad[4][3] = {
|
||||
{ "1", "2", "3" }, { "4", "5", "6" }, { "7", "8", "9" }, { "⌫", "0", "跳" },
|
||||
};
|
||||
int pad_w = 3 * J_KEY_W + 2 * J_KEY_GAP;
|
||||
int pad_x = (g_pw - pad_w) / 2;
|
||||
for (int r = 0; r < 4; r++) {
|
||||
for (int c = 0; c < 3; c++) {
|
||||
int kx = pad_x + c * (J_KEY_W + J_KEY_GAP);
|
||||
int ky = jump_pad_y() + r * (J_KEY_H + J_KEY_GAP);
|
||||
jump_chip(kx, ky, J_KEY_W, J_KEY_H, pad[r][c], (r == 3 && c == 2), th);
|
||||
}
|
||||
}
|
||||
|
||||
/* 条目列表(当前选中高亮) */
|
||||
int top = jump_list_y();
|
||||
int maxshow = (g_ph - top - 8) / J_ROW;
|
||||
if (maxshow < 1) maxshow = 1;
|
||||
/* 让当前选中项进入可视区 */
|
||||
if (g_jump_sel >= 0) {
|
||||
if (g_jump_sel < g_jump_scroll) g_jump_scroll = g_jump_sel;
|
||||
else if (g_jump_sel >= g_jump_scroll + maxshow) g_jump_scroll = g_jump_sel - maxshow + 1;
|
||||
}
|
||||
if (g_jump_scroll + maxshow > n) g_jump_scroll = (n - maxshow < 0 ? 0 : n - maxshow);
|
||||
if (g_jump_scroll < 0) g_jump_scroll = 0;
|
||||
for (int i = 0; i < maxshow; i++) {
|
||||
int item = g_jump_scroll + i;
|
||||
if (item >= n) break;
|
||||
char t[96];
|
||||
if (g_jump_mode == 1) {
|
||||
if (rd_book_chapter(g_book, item, t, sizeof t, NULL, NULL, NULL, NULL) != 0) continue;
|
||||
} else {
|
||||
snprintf(t, sizeof t, "段 %d", item + 1);
|
||||
}
|
||||
int yy = top + i * J_ROW;
|
||||
if (item == g_jump_sel) meow_ui_fill_rect(0, yy, g_pw, J_ROW - 4, th->surfaceContainer);
|
||||
meow_ui_text(24, yy + 4, t, 20, th->fg);
|
||||
meow_ui_draw_rect(12, yy + J_ROW - 6, g_pw - 24, 1, th->outline);
|
||||
}
|
||||
meow_ui_text(20, g_ph - 30, "点条目跳转 · 点空白关闭", 16, th->dim);
|
||||
}
|
||||
|
||||
static int reader_jump_hit(int x, int y)
|
||||
{
|
||||
int n = (g_jump_mode == 1) ? rd_book_chapter_n(g_book) : rd_book_seg_n(g_book);
|
||||
if (n <= 0) return J_NONE;
|
||||
/* 快捷 chips */
|
||||
int sy = jump_shortcut_y();
|
||||
if (y >= sy && y < sy + J_SHORTCUT_H) {
|
||||
int cw = (g_pw - 2 * J_SHORTCUT_X0 - (J_SHORTCUT_N - 1) * J_SHORTCUT_GAP) / J_SHORTCUT_N;
|
||||
if (x >= J_SHORTCUT_X0) {
|
||||
int k = (x - J_SHORTCUT_X0) / (cw + J_SHORTCUT_GAP);
|
||||
if (k >= 0 && k < J_SHORTCUT_N) return J_GO_FIRST + k;
|
||||
}
|
||||
return J_NONE;
|
||||
}
|
||||
/* 键盘 */
|
||||
int py = jump_pad_y();
|
||||
int pad_w = 3 * J_KEY_W + 2 * J_KEY_GAP;
|
||||
int pad_x = (g_pw - pad_w) / 2;
|
||||
for (int r = 0; r < 4; r++) {
|
||||
int ky = py + r * (J_KEY_H + J_KEY_GAP);
|
||||
if (y >= ky && y < ky + J_KEY_H) {
|
||||
for (int c = 0; c < 3; c++) {
|
||||
int kx = pad_x + c * (J_KEY_W + J_KEY_GAP);
|
||||
if (x >= kx && x < kx + J_KEY_W) {
|
||||
if (r == 3) {
|
||||
if (c == 2) return J_JUMP;
|
||||
if (c == 0) return J_BACK;
|
||||
return J_DIG_0; /* 第三行 c==1 → 数字 0 */
|
||||
}
|
||||
return J_DIG_0 + (r * 3 + c) + 1; /* 0..2 行:1 2 3 / 4 5 6 / 7 8 9 */
|
||||
}
|
||||
}
|
||||
return J_NONE;
|
||||
}
|
||||
}
|
||||
/* 条目列表 */
|
||||
int top = jump_list_y();
|
||||
if (y >= top) {
|
||||
int idx = (y - top) / J_ROW + g_jump_scroll;
|
||||
if (idx < n) return J_ITEM + idx;
|
||||
return J_NONE;
|
||||
}
|
||||
return J_NONE;
|
||||
}
|
||||
|
||||
static void reader_jump_to(int page)
|
||||
{
|
||||
if (!g_book) return;
|
||||
int np = rd_book_pages(g_book);
|
||||
if (np <= 0) return;
|
||||
int cur = rd_book_current(g_book);
|
||||
(void)cur;
|
||||
rd_book_set_page(g_book, page);
|
||||
g_prog_dirty = 1;
|
||||
reader_refill_page();
|
||||
reader_redraw();
|
||||
reader_reflow_persist();
|
||||
}
|
||||
|
||||
static void reader_jump_action(int a)
|
||||
{
|
||||
if (a == J_NONE) { g_jump_open = 0; reader_redraw(); return; }
|
||||
int s, e, inp, ofp;
|
||||
if (a >= J_GO_FIRST && a <= J_GO_LAST) {
|
||||
if (jump_region(g_jump_sel, &s, &e, &inp, &ofp) != 0) return;
|
||||
int target = rd_book_current(g_book);
|
||||
switch (a) {
|
||||
case J_GO_FIRST: target = s; break;
|
||||
case J_GO_M100: target -= 100; break;
|
||||
case J_GO_M10: target -= 10; break;
|
||||
case J_GO_P10: target += 10; break;
|
||||
case J_GO_P100: target += 100; break;
|
||||
case J_GO_LAST: target = e; break;
|
||||
}
|
||||
if (target < s) target = s;
|
||||
if (target > e) target = e;
|
||||
reader_jump_to(target);
|
||||
return;
|
||||
}
|
||||
if (a >= J_DIG_0 && a <= J_DIG_9) {
|
||||
size_t len = strlen(g_jump_buf);
|
||||
if (len + 1 < sizeof g_jump_buf) { g_jump_buf[len] = (char)('0' + (a - J_DIG_0)); g_jump_buf[len + 1] = 0; }
|
||||
reader_redraw();
|
||||
return;
|
||||
}
|
||||
if (a == J_BACK) {
|
||||
size_t len = strlen(g_jump_buf);
|
||||
if (len > 0) g_jump_buf[len - 1] = 0;
|
||||
reader_redraw();
|
||||
return;
|
||||
}
|
||||
if (a == J_JUMP) {
|
||||
if (g_jump_buf[0]) {
|
||||
int tpage = atoi(g_jump_buf);
|
||||
if (tpage < 1) tpage = 1;
|
||||
if (jump_region(g_jump_sel, &s, &e, &inp, &ofp) == 0) {
|
||||
int t = s + (tpage - 1);
|
||||
if (t < s) t = s;
|
||||
if (t > e) t = e;
|
||||
reader_jump_to(t);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (a >= J_ITEM) {
|
||||
int item = a - J_ITEM;
|
||||
if (g_jump_mode == 1) {
|
||||
if (jump_region(item, &s, &e, &inp, &ofp) == 0) {
|
||||
g_jump_sel = item;
|
||||
reader_jump_to(s);
|
||||
}
|
||||
} else {
|
||||
int sp = rd_book_seg_page(g_book, item);
|
||||
if (sp >= 0) { g_jump_sel = item; reader_jump_to(sp); }
|
||||
}
|
||||
return;
|
||||
}
|
||||
g_jump_open = 0; reader_redraw();
|
||||
}
|
||||
|
||||
/* 打开跳转弹层:解析章节(首次会扫描+缓存),按是否有章节选章节/文件·段模式。 */
|
||||
static void reader_jump_open(void)
|
||||
{
|
||||
if (!g_book) return;
|
||||
g_jump_buf[0] = 0;
|
||||
g_jump_scroll = 0;
|
||||
int nc = rd_book_chapter_n(g_book);
|
||||
if (nc > 0) {
|
||||
g_jump_mode = 1;
|
||||
int idx = -1;
|
||||
rd_book_chapter_for_page(g_book, rd_book_current(g_book), &idx);
|
||||
g_jump_sel = (idx >= 0) ? idx : 0;
|
||||
} else if (rd_book_seg_n(g_book) > 0) {
|
||||
g_jump_mode = 2;
|
||||
int seg = rd_book_seg_of_page(g_book, rd_book_current(g_book));
|
||||
g_jump_sel = (seg >= 0) ? seg : 0;
|
||||
} else {
|
||||
g_jump_mode = 2;
|
||||
g_jump_sel = 0;
|
||||
}
|
||||
g_jump_open = 1;
|
||||
g_menu_open = 0;
|
||||
reader_redraw();
|
||||
}
|
||||
|
||||
/* ----------------「界面」排版面板(B:字号/行距/背景/顶底栏/编码) ----------------
|
||||
* 自绘 + 自制命中(README §5.2 建议:W_CUSTOM 画布 + 自制 hit,零 SDK widget 协同)。
|
||||
* 几何 draw/hit 共用(常量)。 */
|
||||
@@ -416,6 +788,7 @@ static void reader_canvas_render(meow_widget *w, const meow_theme *th, void *ctx
|
||||
{
|
||||
(void)w; (void)ctx;
|
||||
meow_ui_clear(g_page_bg);
|
||||
chapter_name(); /* 每帧同步顶栏/页底章节名(就绪后取章名,否则文件名) */
|
||||
if (g_book) {
|
||||
int page = rd_book_current(g_book);
|
||||
if ((g_has_font || rd_book_is_image(g_book)) && g_page_buf[RD_PAGE_SLOT_CUR]) {
|
||||
@@ -427,8 +800,6 @@ static void reader_canvas_render(meow_widget *w, const meow_theme *th, void *ctx
|
||||
}
|
||||
/* 页头条(书名 + 章节名;可选,rd.header) */
|
||||
int sbh = meow_ui_statusbar_height();
|
||||
int pages = g_book ? rd_book_pages(g_book) : 0;
|
||||
int page = g_book ? rd_book_current(g_book) : 0;
|
||||
const char *bk = g_book ? rd_book_basename(g_book) : "阅读";
|
||||
if (g_show_header) {
|
||||
meow_ui_fill_rect(0, sbh, g_pw, RD_HEADER_H, g_page_bg);
|
||||
@@ -438,24 +809,23 @@ static void reader_canvas_render(meow_widget *w, const meow_theme *th, void *ctx
|
||||
}
|
||||
/* 页底条(章节 + 页码;可选,rd.footer) */
|
||||
if (g_show_footer) {
|
||||
char ft[48];
|
||||
if (pages > 0) snprintf(ft, sizeof ft, "%d/%d", page + 1, pages);
|
||||
else snprintf(ft, sizeof ft, "0/0");
|
||||
char ft[64];
|
||||
footer_progress(ft, sizeof ft);
|
||||
int fy = g_ph - RD_FOOT_H;
|
||||
meow_ui_fill_rect(0, fy, g_pw, RD_FOOT_H, g_page_bg);
|
||||
meow_ui_text(16, fy + 1, g_chapter[0] ? g_chapter : bk, 16, g_page_fg);
|
||||
meow_ui_text_right(g_pw - 16, fy + 1, ft, 16, g_page_fg);
|
||||
}
|
||||
|
||||
/* R3 刮擦指示条(非目录/面板覆盖时显示;菜单为底部条,与右缘条并存即可) */
|
||||
if (g_book && !g_dir_open && !g_ui_open) reader_draw_scrub(th);
|
||||
/* R3 刮擦指示条(非目录/面板/跳转覆盖时显示;菜单为底部条,与右缘条并存即可) */
|
||||
if (g_book && !g_dir_open && !g_ui_open && !g_jump_open) reader_draw_scrub(th);
|
||||
|
||||
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);
|
||||
/* 4 按钮(MVP 单条):返回 | 目录 | 界面 | 设置 */
|
||||
/* 5 按钮:返回 | 目录 | 跳转 | 界面 | 设置 */
|
||||
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);
|
||||
@@ -463,9 +833,13 @@ static void reader_canvas_render(meow_widget *w, const meow_theme *th, void *ctx
|
||||
}
|
||||
}
|
||||
if (g_ui_open && g_book) reader_draw_ui_panel(th);
|
||||
if (g_jump_open && g_book) reader_draw_jump(th);
|
||||
if (g_dir_open && g_book) {
|
||||
/* 目录覆盖层:半透明底 + 条目列表(当前行高亮) */
|
||||
int n = rd_book_toc_n(g_book);
|
||||
/* 目录覆盖层:TXT 有章节 → 章节列表(复用 rd_book 章节数据,与跳转共享);否则 rdsrc 目录;无则「无章节」。 */
|
||||
int nchap = 0;
|
||||
if (!rd_book_is_image(g_book) && rd_book_chapter_n(g_book) > 0) nchap = rd_book_chapter_n(g_book);
|
||||
int ntoc = (nchap == 0) ? rd_book_toc_n(g_book) : 0;
|
||||
int n = (nchap > 0) ? nchap : ntoc;
|
||||
if (n > 0) {
|
||||
int top = g_top + 8;
|
||||
int row = 34;
|
||||
@@ -474,19 +848,29 @@ static void reader_canvas_render(meow_widget *w, const meow_theme *th, void *ctx
|
||||
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;
|
||||
char t[160];
|
||||
int page = rd_book_current(g_book);
|
||||
int curchap = -1;
|
||||
if (nchap > 0) rd_book_chapter_for_page(g_book, page, &curchap);
|
||||
for (int i = 0; i < maxshow; i++) {
|
||||
if (rd_book_toc(g_book, i, t, sizeof t, &sg, &cch) != 0) continue;
|
||||
int hl = 0;
|
||||
if (nchap > 0) {
|
||||
if (rd_book_chapter(g_book, i, t, sizeof t, NULL, NULL, NULL, NULL) != 0) continue;
|
||||
hl = (i == curchap);
|
||||
} else {
|
||||
int sg = -1; int32_t cch = 0;
|
||||
if (rd_book_toc(g_book, i, t, sizeof t, &sg, &cch) != 0) continue;
|
||||
hl = (page == sg);
|
||||
}
|
||||
int y0 = top + i * row;
|
||||
if (page == sg) meow_ui_fill_rect(0, y0, g_pw, row - 4, th->surfaceContainer);
|
||||
if (hl) 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);
|
||||
meow_ui_text(20, g_top + 40, (rd_book_chapter_n(g_book) == 0) ? "本书无章节" : "本书无目录", 20, th->dim);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -629,12 +1013,13 @@ static void reader_scrub_preview(int page)
|
||||
static void reader_scrub_begin(int x, int y)
|
||||
{
|
||||
(void)x;
|
||||
if (!g_book || g_menu_open || g_dir_open) return;
|
||||
if (!g_book || g_menu_open || g_dir_open || g_jump_open) return;
|
||||
if (g_pw - x > RD_SCRUB_W) return;
|
||||
if (y < rect_y0() || y >= rect_y1()) return;
|
||||
int np = rd_book_pages(g_book);
|
||||
if (np <= 1) return;
|
||||
g_scrub = 1;
|
||||
g_scrub_moved = 0; /* 每次触控重置;拖动(touch_move)才置 1,区分点按/刮擦 */
|
||||
if (g_anim) { meow_anim_cancel(g_anim); g_anim = NULL; }
|
||||
reader_scrub_preview(scrub_page_for_y(y));
|
||||
}
|
||||
@@ -710,9 +1095,10 @@ int reader_open(const char *path)
|
||||
reader_alloc_page_bufs(); /* 重建 cur+next 两槽;g_anim_old 翻页时懒分配 */
|
||||
}
|
||||
|
||||
/* 复位动画/刮擦状态(跨书) */
|
||||
/* 复位动画/刮擦/覆盖层状态(跨书) */
|
||||
if (g_anim) { meow_anim_cancel(g_anim); g_anim = NULL; }
|
||||
g_scrub = 0; g_menu_open = 0; g_dir_open = 0; g_ui_open = 0;
|
||||
g_scrub = 0; g_menu_open = 0; g_dir_open = 0; g_ui_open = 0; g_jump_open = 0;
|
||||
g_chap_cache_idx = -1;
|
||||
|
||||
/* 编码:初始 = 打开探测结果;进度里的显式编码(Big5 用户选定)优先重开 */
|
||||
if (g_book && !rd_book_is_image(g_book)) {
|
||||
@@ -739,6 +1125,23 @@ int reader_open(const char *path)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ---------- R3.4 章节重解析(app/settings.c 调用) ---------- */
|
||||
int reader_has_chapter_book(void)
|
||||
{
|
||||
if (!g_book || rd_book_is_image(g_book)) return 0;
|
||||
/* TXT + 分段流式(seg 模式)即支持章节解析;用 rd_book_seg_n(不触发扫描)判定。 */
|
||||
return (rd_book_seg_n(g_book) > 0) ? 1 : 0;
|
||||
}
|
||||
|
||||
int reader_reparse_chapters(void)
|
||||
{
|
||||
if (!g_book) return -1;
|
||||
int rc = rd_book_reparse_chapters(g_book);
|
||||
if (rc >= 0) g_chap_cache_idx = -1; /* 章节列表变化 → 章范围缓存失效 */
|
||||
if (g_reader_active) reader_redraw();
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* ---------- 视图生命周期 ---------- */
|
||||
static void reader_on_create(meow_view *v, void *ctx) { (void)v; (void)ctx; }
|
||||
static void reader_on_enter(meow_view *v, void *ctx)
|
||||
@@ -766,7 +1169,8 @@ 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_dir_open = 0; g_ui_open = 0; g_scrub = 0; g_reader_active = 0;
|
||||
g_menu_open = 0; g_dir_open = 0; g_ui_open = 0; g_jump_open = 0; g_scrub = 0; g_reader_active = 0;
|
||||
g_chap_cache_idx = -1;
|
||||
}
|
||||
|
||||
meow_view *reader_view_create(void)
|
||||
@@ -785,28 +1189,52 @@ void reader_on_tap(int x, int y)
|
||||
{
|
||||
(void)x;
|
||||
if (!g_book) { return; }
|
||||
if (g_scrub) { /* 刮擦抬手 → 跳到目标页收尾 */
|
||||
if (g_scrub) { /* 刮擦抬手 → 跳到目标页收尾(仅真实拖动过才跳;点按右缘=翻下一页) */
|
||||
g_scrub = 0;
|
||||
reader_scrub_jump(g_scrub_page);
|
||||
if (g_scrub_moved) { reader_scrub_jump(g_scrub_page); return; }
|
||||
/* 未拖动:当作点按,落到下方三区翻页逻辑(右 1/3 下一页),避免右缘误触跳远页 */
|
||||
}
|
||||
if (g_jump_open) {
|
||||
/* 跳转弹层:快捷/键盘/条目 → 执行;点空白关闭 */
|
||||
int a = reader_jump_hit(x, y);
|
||||
reader_jump_action(a);
|
||||
return;
|
||||
}
|
||||
if (g_dir_open) {
|
||||
/* 目录覆盖层:点条目跳转,点空白关闭 */
|
||||
int n = rd_book_toc_n(g_book);
|
||||
/* 目录覆盖层:TXT 章节(复用 rd_book 章节数据)或 rdsrc 目录;点条目跳转,点空白关闭 */
|
||||
int nchap = 0;
|
||||
if (!rd_book_is_image(g_book) && rd_book_chapter_n(g_book) > 0) nchap = rd_book_chapter_n(g_book);
|
||||
int n = (nchap > 0) ? nchap : 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;
|
||||
if (nchap > 0) {
|
||||
int64_t co = 0;
|
||||
if (rd_book_chapter(g_book, idx, NULL, 0, &co, NULL, NULL, NULL) == 0) {
|
||||
int sp = rd_book_page_for_char(g_book, co);
|
||||
if (sp >= 0) {
|
||||
rd_book_set_page(g_book, sp);
|
||||
g_dir_open = 0; g_menu_open = 0;
|
||||
g_chap_cache_idx = -1;
|
||||
reader_refill_page();
|
||||
reader_redraw();
|
||||
reader_reflow_persist();
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -865,14 +1293,18 @@ void reader_on_tap(int x, int y)
|
||||
reader_reflow_persist();
|
||||
meow_view_page_pop();
|
||||
return;
|
||||
case 1: /* 目录 */
|
||||
if (rd_book_toc_n(g_book) > 0) { g_dir_open = 1; g_menu_open = 0; reader_redraw(); }
|
||||
case 1: /* 目录(TXT 章节 → 章节列表;无章节显示「本书无章节」) */
|
||||
g_dir_open = 1; g_menu_open = 0; reader_redraw();
|
||||
return;
|
||||
case 2: /* 跳转(章节/文件·段模式) */
|
||||
if (rd_book_chapter_n(g_book) > 0 || rd_book_seg_n(g_book) > 0) { reader_jump_open(); }
|
||||
else 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: /* 界面(阅读排版设置) */
|
||||
case 3: /* 界面(阅读排版设置) */
|
||||
g_ui_open = 1; g_menu_open = 0; reader_redraw();
|
||||
return;
|
||||
case 3: /* 设置 → App 设置页 */
|
||||
case 4: /* 设置 → App 设置页 */
|
||||
reader_reflow_persist();
|
||||
meow_view_page_push(ACT_SETTINGS);
|
||||
return;
|
||||
@@ -901,6 +1333,7 @@ void reader_on_swipe(int dir, int dx, int dy)
|
||||
* 刮擦(reader_scrub_begin,由 touch_down/move 驱动)两条既有路径。 */
|
||||
if (g_ui_open) { g_ui_open = 0; reader_redraw(); return; }
|
||||
if (g_dir_open) { g_dir_open = 0; reader_redraw(); return; }
|
||||
if (g_jump_open) { g_jump_open = 0; reader_redraw(); return; }
|
||||
if (g_menu_open) { g_menu_open = 0; reader_redraw(); return; }
|
||||
if (dir == MEOW_SWIPE_LEFT || dir == MEOW_SWIPE_RIGHT) {
|
||||
reader_reflow_persist();
|
||||
@@ -913,7 +1346,7 @@ void reader_on_touch_down(int x, int y) { reader_scrub_begin(x, y); }
|
||||
void reader_on_touch_move(int x, int y)
|
||||
{
|
||||
(void)x;
|
||||
if (g_scrub && g_book) reader_scrub_preview(scrub_page_for_y(y));
|
||||
if (g_scrub && g_book) { g_scrub_moved = 1; reader_scrub_preview(scrub_page_for_y(y)); }
|
||||
}
|
||||
|
||||
/* 系统翻页信号(E):reader_ipc 线程收到 MSG_KEY_PAGEUP/PAGEDOWN 后置 pending 计数;
|
||||
@@ -932,9 +1365,12 @@ void reader_on_tick(uint64_t now)
|
||||
if (g_volpage_pending) {
|
||||
int dir = g_volpage_pending > 0 ? 1 : -1;
|
||||
g_volpage_pending = 0;
|
||||
if (g_book && !g_menu_open && !g_dir_open && !g_ui_open)
|
||||
if (g_book && !g_menu_open && !g_dir_open && !g_ui_open && !g_jump_open)
|
||||
reader_turn_animated(dir);
|
||||
}
|
||||
/* Phase 2C:空闲预取下一段(距段界 ≤2% 时提前解码进窗口,翻段界零突发) */
|
||||
if (g_book && !g_menu_open && !g_dir_open && !g_ui_open && !g_jump_open)
|
||||
rd_book_prefetch(g_book);
|
||||
/* R3.2:进度实时落盘(翻页/刮擦/字号/编码变更后,节流 ~1s 定时写)——意外崩溃重开不丢进度 */
|
||||
if (g_prog_dirty && now - g_prog_last_save_ms >= 1000) {
|
||||
g_prog_last_save_ms = now;
|
||||
|
||||
@@ -20,10 +20,16 @@ static meow_widget *g_appbar;
|
||||
static meow_widget *g_list;
|
||||
static int g_active;
|
||||
|
||||
/* 行项枚举(顺序 = 列表顺序) */
|
||||
enum { ITEM_FS = 0, ITEM_LS, ITEM_BG, ITEM_THEME, ITEM_SHELF, ITEM_HDR, ITEM_FTR, ITEM_COUNT };
|
||||
/* 行项枚举(顺序 = 列表顺序)。ITEM_BASE = 固定的全局项数;重解析本书章节仅在
|
||||
* 已打开一本「支持章节解析」(TXT+seg 模式)的书时追加(见 settings_on_enter)。 */
|
||||
enum {
|
||||
ITEM_FS = 0, ITEM_LS, ITEM_BG, ITEM_THEME, ITEM_SHELF, ITEM_HDR, ITEM_FTR,
|
||||
ITEM_REPARSE, ITEM_COUNT
|
||||
};
|
||||
#define ITEM_BASE ITEM_REPARSE /* 7:不含重解析的固定项数 */
|
||||
static int g_nitems = ITEM_BASE;
|
||||
static const char *const g_labels[ITEM_COUNT] = {
|
||||
"默认字号", "默认行距", "默认背景", "主题", "书架布局", "页头条", "页底条"
|
||||
"默认字号", "默认行距", "默认背景", "主题", "书架布局", "页头条", "页底条", "重解析本书章节"
|
||||
};
|
||||
static void settings_redraw(void);
|
||||
|
||||
@@ -61,6 +67,7 @@ static void item_value(int item, char *out, int outsz)
|
||||
case ITEM_SHELF: snprintf(out, (size_t)outsz, "%s", shelf_label(c)); break;
|
||||
case ITEM_HDR: snprintf(out, (size_t)outsz, "%s", c->header ? "显示" : "隐藏"); break;
|
||||
case ITEM_FTR: snprintf(out, (size_t)outsz, "%s", c->footer ? "显示" : "隐藏"); break;
|
||||
case ITEM_REPARSE: snprintf(out, (size_t)outsz, "%s", "点按重新扫描章节"); break;
|
||||
default: snprintf(out, (size_t)outsz, "-"); break;
|
||||
}
|
||||
}
|
||||
@@ -69,15 +76,15 @@ static void item_value(int item, char *out, int outsz)
|
||||
static int settings_src(int idx, meow_list_item *out, void *ctx)
|
||||
{
|
||||
(void)ctx;
|
||||
if (idx < 0 || idx >= ITEM_COUNT) return 0;
|
||||
static char valbuf[16];
|
||||
if (idx < 0 || idx >= g_nitems || idx >= ITEM_COUNT) return 0;
|
||||
static char valbuf[48];
|
||||
item_value(idx, valbuf, sizeof valbuf);
|
||||
out->icon_kind = MEOW_LIST_ICON_NONE;
|
||||
out->title = g_labels[idx];
|
||||
out->subtitle = valbuf;
|
||||
out->right = MEOW_LIST_RIGHT_ARROW;
|
||||
out->on = 0;
|
||||
out->enabled = 1;
|
||||
out->enabled = (idx == ITEM_REPARSE) ? reader_has_chapter_book() : 1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -147,6 +154,7 @@ static void apply_choice(int item, int sel)
|
||||
/* 打开一个 SELECT 对话框并应用。 */
|
||||
static void settings_open_choice(int item)
|
||||
{
|
||||
if (item == ITEM_REPARSE) return; /* 重解析走 settings_reparse(非 SELECT) */
|
||||
const rdconf *c = rdconf_get();
|
||||
if (!c) return;
|
||||
const char *title = g_labels[item];
|
||||
@@ -191,6 +199,25 @@ static void settings_redraw(void)
|
||||
meow_ui_flush();
|
||||
}
|
||||
|
||||
/* R3.4 «重解析本书章节»:确认 → 清缓存+重扫 → 结果对话框(成功/失败/进行中=确认弹窗)。 */
|
||||
static void settings_reparse(void)
|
||||
{
|
||||
meow_dialog *d = meow_dialog_create(MEOW_DLG_CONFIRM, "重解析本书章节?\n将清除章节缓存并重新扫描全书。");
|
||||
if (!d) return;
|
||||
meow_dialog_show(d);
|
||||
int ok = meow_dialog_result(d);
|
||||
meow_dialog_free(d);
|
||||
if (ok != 1) { settings_redraw(); return; } /* 取消 */
|
||||
int rc = reader_reparse_chapters(); /* 阻塞:重扫全书段 */
|
||||
char msg[120];
|
||||
if (rc >= 0) snprintf(msg, sizeof msg, "重解析完成:共识别 %d 章(已保存缓存)", rc);
|
||||
else if (rc == -2) snprintf(msg, sizeof msg, "重解析失败:扫描出错,已回退为无章节(未保存)");
|
||||
else snprintf(msg, sizeof msg, "该书不支持章节解析");
|
||||
meow_dialog *r = meow_dialog_create(MEOW_DLG_INFO, msg);
|
||||
if (r) { meow_dialog_show(r); meow_dialog_result(r); meow_dialog_free(r); }
|
||||
settings_redraw();
|
||||
}
|
||||
|
||||
/* view 生命周期 */
|
||||
static void settings_on_create(meow_view *v, void *ctx) { (void)v; (void)ctx; }
|
||||
static void settings_on_enter(meow_view *v, void *ctx)
|
||||
@@ -203,7 +230,9 @@ static void settings_on_enter(meow_view *v, void *ctx)
|
||||
if (g_appbar) meow_topappbar_set_back(g_appbar, 1);
|
||||
if (g_list) { meow_widget_free(g_list); g_list = NULL; }
|
||||
int ly = sbh + SET_APPBAR_H + 8;
|
||||
g_list = meow_listview_create(SET_X, ly, SET_W, 720 - ly, ITEM_COUNT,
|
||||
/* 仅当打开的书「支持章节解析」(TXT+seg)才显示「重解析本书章节」项 */
|
||||
g_nitems = ITEM_BASE + (reader_has_chapter_book() ? 1 : 0);
|
||||
g_list = meow_listview_create(SET_X, ly, SET_W, 720 - ly, g_nitems,
|
||||
settings_src, NULL, MEOW_LIST_SEL_SINGLE);
|
||||
if (g_list) {
|
||||
meow_listview_set_container(g_list, 1);
|
||||
@@ -240,7 +269,8 @@ int settings_on_tap(int x, int y)
|
||||
}
|
||||
if (g_list) {
|
||||
int idx = meow_listview_tap_at(g_list, x, y);
|
||||
if (idx >= 0 && idx < ITEM_COUNT) {
|
||||
if (idx >= 0 && idx < g_nitems && idx < ITEM_COUNT) {
|
||||
if (idx == ITEM_REPARSE) { settings_reparse(); return 1; }
|
||||
settings_open_choice(idx);
|
||||
return 1;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user