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

87 lines
3.3 KiB
C

/* 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, "\nChapter two: the quick brown fox jumps over the lazy dog.\n");
fclose(f);
}
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;
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");
free(buf);
rd_book_close(b);
printf("book_smoke: %s\n", fails ? "FAIL" : "PASS");
return fails ? 1 : 0;
}