Files
MeowOS-For-R3P2/patch_v2/reader_app/test/book_smoke.c
T
AskaEth 7c7d891570 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 与容器一致)。
2026-11-04 20:02:28 +08:00

194 lines
8.4 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* test/book_smoke.c — os.meow.reader 书桥全链路 qemu-mipsel 冒烟。
* 开一本 TXT(UTF-8,含章节)→ 探测编码 → 建段+页索引 → FreeType 渲染首页(校验非底像素)→
* 翻页/锚定 → 改字号重排回锚 → 再渲染;全程不碰 SDK(book.c 只依赖 core+engine)。
* 跑于 qemu-mipsel(静态)。用法:qemu-mipsel ./test/book_smoke_mipsel <font.otf>
*/
#include "book.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static int fails = 0;
#define CHECK(c, m) do { if (c) { printf(" ok %s\n", m); } else { printf("FAIL %s\n", m); fails++; } } while (0)
static void write_txt(const char *path)
{
FILE *f = fopen(path, "wb");
fprintf(f, "第一章 测试编码与排版\n");
for (int i = 0; i < 60; i++) {
fprintf(f, "第%d段:这是一段用于验证流式排版与 FreeType 渲染的中文正文内容。"
"它包含汉字、标点,以及一些 English words 混合排版。"
"断行、避头尾与分页行为都要正确。\n", i + 1);
}
fprintf(f, "\n第二章 尾声\n这是一段章尾正文,用于验证章节跳转与章内翻页。\n");
fclose(f);
}
/* ---- Phase 2A/2C:跨段拼接页连续性(硬用例)。
* 构造恰好 512KB 的段 0(5242×100B + 88B,末行为短段落),段 1 开头放唯一标记 SEG1MARKER;
* 建段 1 触发滚动拼接后,段界页(段 0 末页)应包含段 1 开头的文本(拼接页含两段文本),
* 且跨段往返翻页后锚 (seg,ch) 稳定。 ---- */
typedef struct { char *buf; size_t len, cap; } linecol;
static void linecol_add(int x, int y, const char *utf8, int len, void *ctx)
{
(void)x; (void)y;
linecol *lc = (linecol *)ctx;
if (lc->len + (size_t)len + 2 > lc->cap) {
size_t nc = lc->cap ? lc->cap * 2 : 4096;
while (nc < lc->len + (size_t)len + 2) nc *= 2;
char *nb = (char *)realloc(lc->buf, nc);
if (!nb) return;
lc->buf = nb; lc->cap = nc;
}
memcpy(lc->buf + lc->len, utf8, (size_t)len);
lc->len += (size_t)len;
lc->buf[lc->len++] = '\n';
lc->buf[lc->len] = 0;
}
static void phase2a_stitch(const char *font)
{
const char *txt = "/tmp/rdbook_stitch.txt";
FILE *f = fopen(txt, "wb");
char line[100];
memset(line, 'a', 99); line[99] = '\n';
for (int i = 0; i < 5242; i++) fwrite(line, 1, 100, f); /* 524200 B */
for (int i = 0; i < 88; i++) fputc('a', f); /* 共 524288 B = 512KB 码位边界 */
fprintf(f, "SEG1MARKER 段一开头唯一标记,证明拼接页跨段包含两段文本。\n");
for (int i = 0; i < 2000; i++)
fprintf(f, "段一正文第%d行:滚动拼接后段界无短页,页表与进度锚稳定。\n", i + 1);
fclose(f);
rd_book_set_seg_base("/tmp/meow_seg_stitch");
rd_book_set_index_dir("/tmp/meow_seg_stitch_index");
rd_book *b = NULL;
int rc = rd_book_open(&b, txt, font, 20, 130, 480, 720, 16, 20, 62);
CHECK(rc == 0, "stitch open");
if (rc != 0) { remove(txt); return; }
int nseg = rd_book_seg_n(b);
CHECK(nseg >= 2, "stitch multi-seg");
if (nseg >= 2) {
int p1 = rd_book_seg_page(b, 1); /* 建段 1 → 触发段 0 末页拼接 */
CHECK(p1 >= 0, "stitch seg1 built");
int ps = rd_book_seg_page(b, 0);
int pc = rd_book_seg_page_count(b, 0);
CHECK(ps == 0 && pc > 0, "stitch seg0 pages");
int bnd = ps + pc - 1;
linecol lc = { NULL, 0, 0 };
int rr = rd_book_render_text(b, bnd, linecol_add, &lc);
CHECK(rr == 0, "stitch boundary render_text ok");
CHECK(lc.buf && strstr(lc.buf, "SEG1MARKER") != NULL, "stitch boundary contains seg1 text");
free(lc.buf);
/* 跨段往返:段界页翻前/后,锚 (seg,ch) 不变 */
int as = -1; int32_t ach = -1;
rd_book_set_page(b, bnd);
rd_book_anchor(b, &as, &ach);
CHECK(as == 0 && ach >= 0, "stitch boundary anchor seg0");
int cur = rd_book_current(b);
int fwd = rd_book_turn(b, 1);
int back = rd_book_turn(b, -1);
CHECK(fwd == cur + 1 && back == cur, "stitch boundary turn roundtrip");
int as2 = -1; int32_t ach2 = -1;
rd_book_anchor(b, &as2, &ach2);
CHECK(as2 == as && ach2 == ach, "stitch boundary anchor stable");
}
rd_book_close(b);
remove(txt);
}
int main(int argc, char **argv)
{
const char *font = (argc > 1) ? argv[1] : "/home/Aska/Project/R3ProII/font/src/MiSans-Regular.otf";
const char *txt = "/tmp/rdbook_smoke.txt";
write_txt(txt);
rd_book *b = NULL;
/* R3.2:启用分段流式缓存(/tmp),走「切 512KB 段 + 按需布局」路径,而非全量直排 */
rd_book_set_seg_base("/tmp/meow_seg_smoke");
rd_book_set_index_dir("/tmp/meow_seg_smoke_index");
int rc = rd_book_open(&b, txt, font, 20, 130, 480, 720, 16, 20, 62);
CHECK(rc == 0, "rd_book_open");
if (rc != 0) { printf("open rc=%d\n", rc); return 1; }
int np = rd_book_pages(b);
printf(" pages=%d basename=%s enc=%s\n", np, rd_book_basename(b), rd_book_encname(b));
CHECK(np > 0, "pages>0");
/* 渲染首页:校验有非背景像素 */
int W = 480, H = 720;
uint16_t *buf = (uint16_t *)malloc((size_t)W * H * 2);
uint16_t bg = 0xFFFF, fg = 0x0000;
rc = rd_book_render(b, 0, buf, W * 2, bg, fg);
CHECK(rc == 0, "render page0");
int inked = 0;
for (int i = 0; i < W * H; i++) if (buf[i] != bg) inked++;
printf(" page0 inked=%d px\n", inked);
CHECK(inked > 100, "page0 has ink");
/* 锚定当前页 */
int seg = -1; int32_t ch = -1;
rd_book_anchor(b, &seg, &ch);
CHECK(seg >= 0 && ch >= 0, "anchor valid");
/* 翻页 */
int cur = rd_book_current(b);
int np_total = rd_book_pages(b);
int nxt = rd_book_turn(b, 1);
CHECK(np_total > 1 || nxt == 0, "pages>1 or single-page clamps");
if (np_total > 1) CHECK(nxt == cur + 1, "turn fwd +1");
/* 回退 */
rd_book_turn(b, -1);
CHECK(rd_book_current(b) == cur, "turn back to cur");
/* 改字号(20→24) 重排回锚到含 (seg,ch) 的页 */
int np2 = rd_book_jump_to_anchor(b, seg, ch, 24, 130);
CHECK(np2 >= 0 && np2 < rd_book_pages(b), "reflow lands in range");
int seg2 = -1; int32_t ch2 = -1;
rd_book_anchor(b, &seg2, &ch2);
CHECK(seg2 == seg, "reflow keeps segment");
/* 重排后渲染 */
rc = rd_book_render(b, rd_book_current(b), buf, W * 2, bg, fg);
CHECK(rc == 0, "render after reflow");
/* 行距调整再排 */
int np3 = rd_book_set_size(b, 20, 150);
CHECK(np3 >= 0 && np3 < rd_book_pages(b), "set_size reflow ok");
/* R3.3 章节:解析 + 章节→页映射 + 章内翻页单调(页号递增不跳变) */
int nch = rd_book_chapter_n(b);
printf(" chapters=%d\n", nch);
CHECK(nch == 2, "book has 2 chapters");
if (nch == 2) {
char t0[160], t1[160]; int n0 = -1, n1 = -1; int64_t c0 = -1, c1 = -1;
CHECK(rd_book_chapter(b, 0, t0, sizeof t0, &c0, NULL, NULL, &n0) == 0, "chapter0 get");
CHECK(n0 == 1, "chapter0 number 1");
CHECK(c0 == 0, "chapter0 char_off 0");
int p0 = rd_book_page_for_char(b, c0);
CHECK(p0 == 0 && p0 < rd_book_pages(b), "chapter0 start page 0");
CHECK(rd_book_chapter(b, 1, t1, sizeof t1, &c1, NULL, NULL, &n1) == 0, "chapter1 get");
CHECK(n1 == 2, "chapter1 number 2");
int p1 = rd_book_page_for_char(b, c1);
CHECK(p1 >= p0 && p1 < rd_book_pages(b), "chapter1 start page in range");
printf(" chapter1('%s') at page %d (of %d)\n", t1, p1, rd_book_pages(b));
/* 跳到章二首页,验证章内翻页 +1 单调(不越过章界仍递增) */
int landed = rd_book_set_page(b, p1);
CHECK(landed == p1, "jump lands on chapter1 start");
int cur1 = rd_book_current(b);
int nxt1 = rd_book_turn(b, 1);
CHECK((nxt1 == cur1 + 1) || (nxt1 == cur1), "chapter-internal turn +1 (clamp-safe)");
/* R3.4 手动重解析(清 .chap 缓存 + 重扫)应幂等返回同章数,且章节仍可用 */
int nrep = rd_book_reparse_chapters(b);
CHECK(nrep == nch, "reparse returns same chapter count");
CHECK(rd_book_chapter_n(b) == nch, "reparse keeps chapters");
}
free(buf);
rd_book_close(b);
/* Phase 2A/2C:跨段拼接页连续性 + 段界锚稳定(另开一本 >512KB 的多段书) */
phase2a_stitch(font);
printf("book_smoke: %s\n", fails ? "FAIL" : "PASS");
return fails ? 1 : 0;
}