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
This commit is contained in:
MeowOS committed 2026-09-01 23:26:42 +08:00
1 parent 33d3059038
commit 553a7d5d9e
26 files changed
+6155

No files matched your search

+215
View File
@@ -0,0 +1,215 @@
/* engine/rdft.c — FreeType 封装实现(见 engine/rdft.h)。
* 依赖 FreeType 2(静态篇 mipsel musl);无 SDK 依赖。
*/
#include "rdft.h"
#include <ft2build.h>
#include FT_FREETYPE_H
#include <stdlib.h>
#include <string.h>
typedef struct {
uint64_t key; /* (px<<32)|cp */
uint64_t stamp; /* LRU 时间戳 */
int adv; /* 水平 advance(px) */
uint8_t *bmp; /* 灰阶位图拷贝(malloc;bw==0 时 NULL) */
int bw, bh, pitch, left, top;
} glyph_slot;
struct rdft {
FT_Library lib;
FT_Face face;
int px;
glyph_slot *slots;
int nslots; /* 容量 */
int used; /* 在线条目数 */
uint64_t clock; /* 单调戳 */
int has_font;
};
int rdft_init(rdft **r)
{
if (!r) return -1;
rdft *x = (rdft *)calloc(1, sizeof *x);
if (!x) return -6;
if (FT_Init_FreeType(&x->lib) != 0) { free(x); return -3; }
x->px = 20;
x->nslots = 512;
x->slots = (glyph_slot *)calloc((size_t)x->nslots, sizeof(glyph_slot));
if (!x->slots) { FT_Done_FreeType(x->lib); free(x); return -6; }
x->used = 0;
x->clock = 1;
*r = x;
return 0;
}
int rdft_load_font(rdft *r, const char *path)
{
if (!r || !path) return -1;
if (r->face) { FT_Done_Face(r->face); r->face = NULL; }
if (r->slots) { for (int i = 0; i < r->nslots; i++) free(r->slots[i].bmp); }
if (r->slots) memset(r->slots, 0, (size_t)r->nslots * sizeof(glyph_slot));
r->used = 0;
if (FT_New_Face(r->lib, path, 0, &r->face) != 0) { r->has_font = 0; return -1; }
r->has_font = 1;
if (r->px > 0) FT_Set_Pixel_Sizes(r->face, 0, r->px);
return 0;
}
void rdft_set_px(rdft *r, int px)
{
if (!r) return;
if (px <= 0) px = r->px;
r->px = px;
if (r->face) FT_Set_Pixel_Sizes(r->face, 0, (FT_UInt)px);
/* key 含字号,清缓存防旧字号位图残留 */
if (r->slots) { for (int i = 0; i < r->nslots; i++) free(r->slots[i].bmp); memset(r->slots, 0, (size_t)r->nslots * sizeof(glyph_slot)); }
r->used = 0;
}
int rdft_ascender(rdft *r)
{
if (!r || !r->face) return 0;
return (int)(r->face->size->metrics.ascender >> 6);
}
int rdft_descender(rdft *r)
{
if (!r || !r->face) return 0;
return (int)(r->face->size->metrics.descender >> 6);
}
const char *rdft_family(rdft *r)
{
if (!r || !r->face || !r->face->family_name) return "";
return r->face->family_name;
}
static glyph_slot *slot_find(rdft *r, uint64_t key)
{
for (int i = 0; i < r->nslots; i++)
if (r->slots[i].key == key && r->slots[i].stamp) return &r->slots[i];
return NULL;
}
static glyph_slot *slot_lru_evict(rdft *r)
{
/* 找未用槽(stamp==0)或最旧使用 */
glyph_slot *oldest = NULL;
uint64_t mins = UINT64_MAX;
for (int i = 0; i < r->nslots; i++) {
if (!r->slots[i].stamp) { r->used++; return &r->slots[i]; } /* 空槽 */
if (r->slots[i].stamp < mins) { mins = r->slots[i].stamp; oldest = &r->slots[i]; }
}
free(oldest->bmp);
oldest->bmp = NULL; oldest->key = 0; oldest->stamp = 0;
return oldest;
}
int rdft_glyph(rdft *r, uint32_t cp, int *adv_x,
const uint8_t **bitmap, int *bw, int *bh, int *left, int *top)
{
if (!r || !r->face || !adv_x) return -1;
if (r->clock == UINT64_MAX) { r->clock = 1; } /* 时钟回绕(罕见) */
uint64_t key = ((uint64_t)(uint32_t)r->px << 32) | cp;
glyph_slot *s = slot_find(r, key);
if (s) {
s->stamp = r->clock++;
*adv_x = s->adv;
*bitmap = s->bmp; *bw = s->bw; *bh = s->bh; *left = s->left; *top = s->top;
return 0;
}
/* miss:光栅化 */
FT_UInt gi = FT_Get_Char_Index(r->face, cp);
if (r->px <= 0) FT_Set_Pixel_Sizes(r->face, 0, (FT_UInt)r->px);
if (FT_Load_Glyph(r->face, gi, FT_LOAD_RENDER | FT_LOAD_NO_HINTING | FT_LOAD_TARGET_LIGHT) != 0) {
/* 空字形(缺字)→ 仅 advance */
*adv_x = r->face->glyph->advance.x >> 6;
*bitmap = NULL; *bw = *bh = *left = *top = 0;
return 0;
}
FT_GlyphSlot g = r->face->glyph;
int a = (int)((g->advance.x >= 0 ? g->advance.x + 0x20 : g->advance.x - 0x20) >> 6);
if (a < 0) a = 0;
s = slot_lru_evict(r);
s->key = key; s->stamp = r->clock++; s->adv = a;
s->bmp = NULL; s->bw = 0; s->bh = 0; s->pitch = 0; s->left = 0; s->top = 0;
if (g->bitmap.buffer && g->bitmap.width > 0 && g->bitmap.rows > 0) {
int w = (int)g->bitmap.width, rows = (int)g->bitmap.rows, pitch = (int)g->bitmap.pitch;
uint8_t *b = (uint8_t *)malloc((size_t)w * (size_t)rows);
if (b) {
/* 紧凑拷贝(每行 = w 字节),blend 用 bw 作行步长 */
for (int r_ = 0; r_ < rows; r_++)
memcpy(b + (size_t)r_ * (size_t)w, g->bitmap.buffer + (size_t)r_ * (size_t)pitch, (size_t)w);
s->bmp = b; s->bw = w; s->bh = rows; s->pitch = w;
s->left = g->bitmap_left; s->top = g->bitmap_top;
}
}
*adv_x = s->adv;
*bitmap = s->bmp; *bw = s->bw; *bh = s->bh; *left = s->left; *top = s->top;
return 0;
}
int rdft_advance(rdft *r, uint32_t cp, int *adv_x)
{
if (!r || !r->face || !adv_x) return -1;
FT_UInt gi = FT_Get_Char_Index(r->face, cp);
if (r->px <= 0) FT_Set_Pixel_Sizes(r->face, 0, (FT_UInt)r->px);
if (FT_Load_Glyph(r->face, gi, FT_LOAD_NO_HINTING | FT_LOAD_TARGET_LIGHT) != 0) {
*adv_x = r->face->glyph->advance.x >> 6;
return 0;
}
int a = (int)((r->face->glyph->advance.x >= 0 ? r->face->glyph->advance.x + 0x20
: r->face->glyph->advance.x - 0x20) >> 6);
*adv_x = a < 0 ? 0 : a;
return 0;
}
void rdft_blend565(uint16_t *fb, int pitch_bytes, int tw, int th,
int x, int y, const uint8_t *bmp, int bw, int bh, int left, int top,
uint16_t fg565, uint16_t bg565)
{
if (!fb || !bmp || bw <= 0 || bh <= 0) return;
int fg_r = (fg565 >> 11) & 0x1F, fg_g = (fg565 >> 5) & 0x3F, fg_b = fg565 & 0x1F;
int bg_r = (bg565 >> 11) & 0x1F, bg_g = (bg565 >> 5) & 0x3F, bg_b = bg565 & 0x1F;
int start_x = x + left, base_y = y; /* y 为基线 */
int start_y = base_y - top;
int pitch_px = pitch_bytes / 2; /* 目标每行像素数 */
for (int row = 0; row < bh; row++) {
int ty = start_y + row;
if (ty < 0) continue;
if (ty >= th) break;
const uint8_t *src = bmp + (size_t)row * (size_t)bw; /* 紧凑:每行 bw */
for (int col = 0; col < bw; col++) {
int tx = start_x + col;
if (tx < 0) continue;
if (tx >= tw) break;
unsigned a = src[col];
if (a == 0) continue;
uint16_t *px = fb + (size_t)ty * (size_t)pitch_px + tx;
int r = (fg_r * a + bg_r * (255 - a)) / 255;
int g = (fg_g * a + bg_g * (255 - a)) / 255;
int b = (fg_b * a + bg_b * (255 - a)) / 255;
*px = (uint16_t)((r << 11) | (g << 5) | b);
}
}
}
void rdft_cache_set_slots(rdft *r, int slots)
{
if (!r || slots < 0 || slots == r->nslots) return;
glyph_slot *na = (glyph_slot *)calloc((size_t)(slots ? slots : 1), sizeof(glyph_slot));
if (!na) return;
for (int i = 0; i < r->nslots; i++) free(r->slots[i].bmp);
free(r->slots);
r->slots = na; r->nslots = (slots ? slots : 1); r->used = 0;
}
int rdft_cache_slots(rdft *r) { return r ? r->nslots : 0; }
int rdft_cache_used(rdft *r) { return r ? r->used : 0; }
void rdft_free(rdft *r)
{
if (!r) return;
if (r->slots) { for (int i = 0; i < r->nslots; i++) free(r->slots[i].bmp); }
free(r->slots);
if (r->face) FT_Done_Face(r->face);
if (r->lib) FT_Done_FreeType(r->lib);
free(r);
}