Files
MeowOS-For-R3P2/patch_v2/reader_app/app/shelf.c
T
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

185 lines
6.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.
/* app/shelf.c — 书架:扫 TF 卡 `Books/`,识别 TXT/FB2/EPUB/MOBI/CBZ(R1 仅 TXT 可开,
* 其它显示「R2 支持」占位);卡片列表 + 最近阅读置顶(按 progress ts 降序)。
* 用 W_LISTVIEW(结构化列表)+ App 自绘标题。空态=清屏提示。
*/
#include "../reader_internal.h"
#include "progress.h"
#include <dirent.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#define SHELF_LIST_X 0
#define SHELF_LIST_Y 64
#define SHELF_LIST_W 480
#define SHELF_LIST_H (720 - 64)
typedef struct shelf_book {
char path[400];
char title[128];
char sub[160];
int fmt; /* 0 txt,1 fb2,2 epub,3 mobi,4 cbz */
long size;
uint64_t ts; /* 最近阅读 unix;无=0 */
} shelf_book;
static shelf_book g_books[256];
static int g_nbooks;
static int g_active;
static meow_widget *g_list;
static uint32_t g_bg, g_fg, g_dim;
static char g_notice[160]; /* 空态/提示 */
static int g_notice_ms; /* 剩余显示 ms(0=无) */
static const char *fmt_name(int f)
{
switch (f) { case 0: return "TXT"; case 1: return "FB2"; case 2: return "EPUB";
case 3: return "MOBI"; case 4: return "CBZ"; default: return "?"; }
}
static int fmt_from_ext(const char *name)
{
const char *d = strrchr(name, '.');
if (!d) return -1;
if (strcasecmp(d, ".txt") == 0) return 0;
if (strcasecmp(d, ".fb2") == 0) return 1;
if (strcasecmp(d, ".epub") == 0) return 2;
if (strcasecmp(d, ".mobi") == 0) return 3;
if (strcasecmp(d, ".cbz") == 0) return 4;
return -1;
}
static void fmt_size(char *out, int outsz, long s)
{
if (s >= 1 << 20) snprintf(out, outsz, "%.1fMB", (double)s / (1 << 20));
else if (s >= 1 << 10) snprintf(out, outsz, "%.0fKB", (double)s / (1 << 10));
else snprintf(out, outsz, "%ldB", s);
}
static int cmp_recent_then_name(const void *a, const void *b)
{
const shelf_book *x = (const shelf_book *)a, *y = (const shelf_book *)b;
if (x->ts != y->ts) return (x->ts < y->ts) ? 1 : -1; /* 最近在前 */
return strcasecmp(x->title, y->title);
}
static void scan_books(void)
{
const char *dir = READER_SD_ROOT "/Books";
g_nbooks = 0;
DIR *dp = opendir(dir);
if (!dp) {
snprintf(g_notice, sizeof g_notice, "未找到 %s 目录,请放入书籍(TXT)", dir);
g_notice_ms = 4000;
return;
}
struct dirent *de;
while ((de = readdir(dp)) && g_nbooks < 256) {
if (de->d_name[0] == '.') continue;
int f = fmt_from_ext(de->d_name);
if (f < 0) continue;
shelf_book *b = &g_books[g_nbooks];
if (snprintf(b->path, sizeof b->path, "%s/%s", dir, de->d_name) >= (int)sizeof b->path) continue;
strncpy(b->title, de->d_name, sizeof b->title - 1); b->title[sizeof b->title - 1] = 0;
char *dot = strrchr(b->title, '.');
if (dot) *dot = 0; /* 标题去扩展名 */
b->fmt = f;
struct stat st;
b->size = (stat(b->path, &st) == 0) ? (long)st.st_size : 0;
rdprog p;
b->ts = (rdprog_load(b->path, &p) == 1) ? p.ts : 0;
char sz[24]; fmt_size(sz, sizeof sz, b->size);
snprintf(b->sub, sizeof b->sub, "%s · %s%s", fmt_name(f), sz,
b->ts ? " · 读" : "");
g_nbooks++;
}
closedir(dp);
qsort(g_books, (size_t)g_nbooks, sizeof(shelf_book), cmp_recent_then_name);
}
/* listview 行源 */
static int shelf_list_src(int idx, meow_list_item *out, void *ctx)
{
(void)ctx;
if (idx < 0 || idx >= g_nbooks) return 0;
const shelf_book *b = &g_books[idx];
out->icon_kind = MEOW_LIST_ICON_FILE;
out->title = b->title;
out->subtitle = b->sub;
out->right = MEOW_LIST_RIGHT_ARROW;
out->enabled = 1;
out->on = 0;
return 1;
}
static void shelf_list_tapcb(int idx, int op, void *ctx)
{
(void)op; (void)ctx;
if (idx < 0 || idx >= g_nbooks) return;
const shelf_book *b = &g_books[idx];
if (b->fmt == 0) { reader_open(b->path); }
else { snprintf(g_notice, sizeof g_notice, "%s 格式 R2 支持(R1 先看 TXT)", fmt_name(b->fmt)); g_notice_ms = 2500; }
}
/* view 生命周期 */
static void shelf_on_create(meow_view *v, void *ctx) { (void)v; (void)ctx; }
static void shelf_on_enter(meow_view *v, void *ctx)
{
(void)v; (void)ctx;
g_active = 1;
scan_books(); /* 回到书架即重扫(新增/最近读置顶刷新) */
const meow_theme *th = meow_theme_get();
g_bg = th->bg; g_fg = th->fg; g_dim = th->dim;
/* 无控件时重建列表(首次/回到书架) */
if (g_list) { meow_widget_free(g_list); g_list = NULL; }
g_list = meow_listview_create(SHELF_LIST_X, SHELF_LIST_Y, SHELF_LIST_W, SHELF_LIST_H,
g_nbooks, shelf_list_src, NULL, MEOW_LIST_SEL_SINGLE);
meow_listview_set_on_tap(g_list, shelf_list_tapcb, NULL);
shelf_redraw();
}
static void shelf_on_leave(meow_view *v, void *ctx) { (void)v; (void)ctx; g_active = 0; }
static void shelf_on_destroy(meow_view *v, void *ctx)
{
(void)v; (void)ctx;
if (g_list) { meow_widget_free(g_list); g_list = NULL; }
}
meow_view *shelf_view_create(void)
{
static meow_view_hooks hooks = { shelf_on_create, shelf_on_enter, shelf_on_leave,
shelf_on_destroy, NULL, NULL, NULL };
return meow_view_create(ACT_SHELF, MEOW_VIEW_ACTIVITY, &hooks, NULL);
}
int shelf_is_active(void) { return g_active; }
void shelf_redraw(void)
{
const meow_theme *th = meow_theme_get();
g_bg = th->bg; g_fg = th->fg; g_dim = th->dim;
meow_ui_clear(g_bg);
meow_ui_text(20, 22, "书架", 28, g_fg);
/* 副标题:格式化说明 */
char sub[96]; snprintf(sub, sizeof sub, "Books/ · %d 本", g_nbooks);
meow_ui_text(20, 24 + 34, sub, 20, g_dim);
if (g_list) meow_widget_draw(g_list);
if (g_nbooks == 0) {
meow_ui_text(30, 320, "书架为空:请往 TF 卡 Books/ 放书籍", 20, g_dim);
meow_ui_text(30, 348, "支持 TXT(可读) / FB2 / EPUB / MOBI / CBZ(R2)", 20, g_dim);
}
if (g_notice_ms > 0) {
meow_ui_fill_rect(0, 620, 480, 44, g_bg);
meow_ui_text(20, 630, g_notice, 20, th->primary);
}
meow_ui_flush();
}
int shelf_on_tap(int x, int y)
{
if (g_list && g_nbooks > 0) {
int idx = meow_listview_tap_at(g_list, x, y);
if (idx >= 0) { shelf_redraw(); return 1; } /* 选中已由 SDK 标脏;重绘 */
}
return 0;
}