reader(R2): EPUB/FB2/MOBI/CBZ 加载器落地

- rdsrc 统一文档源抽象:TXT/FB2/EPUB/MOBI/CBZ 都产出「UTF-8 段序列+目录+元数据+图片」,
  与 R1 rdtxt 段契约一致;book.c 改喂 rd_src(文本)或图页(CBZ)。
- EPUB:miniz 解 ZIP → 自写 rdxml 回调解析 OPF(spine/manifest)+NCX/nav → 正文 XHTML 用
  rdhtml 逐行粗解析(坏标签/未闭合/实体容错,不建 DOM),章节按 RDSRC_SEG_MAX 分片。
- FB2:rdxml 严格解析抽 section 段落 + base64 内嵌图;段题进正文+目录。
- MOBI/AZW3:vendor libmobi 最小调用面(mobi_init→load→parse_kf7/8→parse_rawml_opt→
  flow/resources),HTML 走 rdhtml 同款粗解析;LGPL-3.0 合规见 NOTICE(版权+全文+锁定 commit)。
- CBZ:miniz 解 ZIP → 自然序图片序列,rdimg(stb_image) 解码+降采样,阅读页图页模式。
- 目录显示与跳转(EPUB NCX/FB2 section/MOBI 标题);书架开放四格式;进度按
  (段,字符偏移)(TXT/FB2/EPUB/MOBI)/页号(CBZ)。
- 宿主单测 loaders_smoke(四格式抽取/目录/图片/base64/页序)+ qemu-mipsel
  formats_smoke/book_smoke;R1 test_reader_core 58/0 不回归。
- build_reader.sh 集成 miniz/libmobi/stb/zlib 头;NOTICE 入仓;交叉 -Wall -Wextra 0 warning。

新 reader md5: e29036599300eb277b1c960a75b65dbf
This commit is contained in:
AskaEth committed 2026-09-02 03:11:55 +08:00
1 parent 10690bc348
commit 0294553096
27 files changed
+3440 -193

No files matched your search

+103 -59
View File
@@ -1,93 +1,137 @@
#!/bin/bash
# build_reader.sh — os.meow.reader R1 可复现构建:vendor FreeType(mipsel musl 静态) → 链 reader 二进制
# + 宿主 core 单测 + qemu-mipsel FreeType 冒烟。
# build_reader.sh — os.meow.reader R2 可复现构建:
# vendor: freetype(静态)/miniz/libmobi/stb/zlib 头 → 交叉 mipsel 编 reader 二进制
# + 宿主 core 单测(R1+loaders) + qemu-mipsel 冒烟(rdft/book/formats)。
#
# 产物:
# reader mipsel 静态 ELF(链 meow_ui.a + FreeType + core + engine)
# test/test_reader_core_host 宿主 core 单测(enc/layout/kinsoku/page/reflow/txt)
# test/rdft_smoke_mipsel qemu-mipsel FreeType 封装冒烟
# reader mipsel 静态 ELF(链 meow_ui.a + freetype + miniz + libmobi + zlib)
# test/test_reader_core_host 宿主 core 单测(enc/layout/txt)
# test/loaders_smoke_host 宿主四格式 loader 单测
# test/rdft_smoke_mipsel qemu-mipsel FreeType 冒烟
# test/book_smoke_mipsel qemu-mipsel TXT 书桥冒烟
# test/formats_smoke_mipsel qemu-mipsel 四格式书桥冒烟
#
# 约束:只写本目录(patch_v2/reader_app/);不改 sdk/;-Wall -Wextra 0 warning。
# vendor/(FreeType 源码+构建产物)gitignore,不入仓;build 脚本可复现。
# 约束:只写本目录(patch_v2/reader_app/);不改 sdk/;-Wall -Wextra 0 warning(自有代码)。
# vendor/(第三方源码+构建产物)gitignore,不入仓;build 脚本可复现。
set -e
cd "$(dirname "$0")"
ROOT="$HOME/Project/R3ProII"
CC="$ROOT/.tools/zig-mipsel-cc"
SYSROOT="$ROOT/.tools/mipsel-linux-muslsf-cross/mipsel-linux-muslsf"
HOST_CC="${HOST_CC:-gcc}"
FT_VER="2.14.3"
VENDOR="$PWD/vendor"
FT_SRC="$VENDOR/freetype-src"
FT_BUILD="$VENDOR/freetype-build"
FT_INC_SRC="$FT_SRC/include"
FT_INC_BUILD="$FT_BUILD/include"
OBJ="$VENDOR/build"
mkdir -p "$OBJ"
# ---------- 1) FreeType vendor 拉取 ----------
# ---------- 1) vendor 拉取/对齐 ----------
FT_VER="2.14.3"; MINIZ_URL="https://codeload.github.com/richgel999/miniz/tar.gz/refs/heads/master"
LM_URL="https://codeload.github.com/bfabiszewski/libmobi/tar.gz/refs/heads/master"
mkdir -p "$VENDOR"
fetch_freetype() {
# 优先级:env FREETYPE_SRC → 本目录 vendor 已存在 → /tmp/freetype → 下载
if [ -n "$FREETYPE_SRC" ] && [ -f "$FREETYPE_SRC/CMakeLists.txt" ]; then
cp -r "$FREETYPE_SRC" "$FT_SRC"
elif [ ! -d "$FT_SRC" ] && [ -f /tmp/freetype/CMakeLists.txt ]; then
cp -r /tmp/freetype "$FT_SRC"
elif [ ! -d "$FT_SRC" ]; then
echo "==> downloading freetype $FT_VER"
rm -rf "$FT_SRC"
( cd "$VENDOR" && curl -fsSL -o ft.tar.xz \
"https://download.savannah.gnu.org/releases/freetype/freetype-$FT_VER.tar.xz" \
&& mkdir -p "$FT_SRC" && tar xf ft.tar.xz -C "$FT_SRC" --strip-components=1 && rm ft.tar.xz )
if [ -n "$FREETYPE_SRC" ] && [ -f "$FREETYPE_SRC/CMakeLists.txt" ]; then cp -r "$FREETYPE_SRC" vendor/freetype-src;
elif [ ! -d "$VENDOR/freetype-src" ] && [ -f /tmp/freetype/CMakeLists.txt ]; then cp -r /tmp/freetype "$VENDOR/freetype-src";
elif [ ! -d "$VENDOR/freetype-src" ]; then
( cd "$VENDOR" && curl -fsSL -o ft.tar.xz "https://download.savannah.gnu.org/releases/freetype/freetype-$FT_VER.tar.xz" \
&& mkdir -p freetype-src && tar xf ft.tar.xz -C freetype-src --strip-components=1 && rm ft.tar.xz )
fi
[ -f "$FT_SRC/CMakeLists.txt" ] || { echo "FREETYPE_SRC 不可用/下载失败"; exit 1; }
[ -f "$VENDOR/freetype-src/CMakeLists.txt" ] || { echo "freetype 不可用"; exit 1; }
}
fetch_freetype
fetch_miniz() {
if [ ! -f "$VENDOR/miniz/miniz.c" ]; then
( cd /tmp && curl -fsSL -o miniz.tgz "$MINIZ_URL" && mkdir -p miniz_m && tar xzf miniz.tgz -C miniz_m --strip-components=1 \
&& cp miniz_m/miniz.c miniz_m/miniz.h miniz_m/miniz_common.h miniz_m/miniz_tdef.c miniz_m/miniz_tdef.h \
miniz_m/miniz_tinfl.c miniz_m/miniz_tinfl.h miniz_m/miniz_zip.c miniz_m/miniz_zip.h "$VENDOR/miniz/" )
fi
[ -f "$VENDOR/miniz/miniz.c" ] || { echo "miniz 不可用"; exit 1; }
}
fetch_libmobi() {
if [ ! -f "$VENDOR/libmobi/src/mobi.h" ]; then
( cd /tmp && curl -fsSL -o lm.tgz "$LM_URL" && mkdir -p lm_m && tar xzf lm.tgz -C lm_m --strip-components=1 \
&& mkdir -p "$VENDOR/libmobi" && cp -r lm_m/src "$VENDOR/libmobi/src" && cp lm_m/COPYING "$VENDOR/libmobi/COPYING" )
fi
[ -f "$VENDOR/libmobi/src/mobi.h" ] || { echo "libmobi 不可用"; exit 1; }
}
fetch_stb() { [ -f "$VENDOR/stb/stb_image.h" ] || { mkdir -p "$VENDOR/stb"; cp "$ROOT/patch_v2/sdk/stb_image.h" "$VENDOR/stb/stb_image.h"; }; }
fetch_freetype; fetch_miniz; fetch_libmobi; fetch_stb
if [ ! -f "$VENDOR/zlib/zlib.h" ]; then mkdir -p "$VENDOR/zlib"; cp /usr/include/zlib.h /usr/include/zconf.h "$VENDOR/zlib/" 2>/dev/null || true; fi
# ---------- 2) FreeType mipsel 交叉静态构建(禁用可选项,只要最简) ----------
# ---------- 2) FreeType mipsel 静态构建 ----------
FT_SRC="$VENDOR/freetype-src"; FT_BUILD="$VENDOR/freetype-build"
if [ ! -f "$FT_BUILD/libfreetype.a" ] || [ -n "$FORCE_FT" ]; then
rm -rf "$FT_BUILD"
cmake -S "$FT_SRC" -B "$FT_BUILD" \
-DCMAKE_SYSTEM_NAME=Linux \
-DCMAKE_C_COMPILER="$CC" \
-DCMAKE_TRY_COMPILE_TARGET_TYPE=STATIC_LIBRARY \
-DCMAKE_BUILD_TYPE=Release \
-DBUILD_SHARED_LIBS=OFF \
-DFT_DISABLE_ZLIB=TRUE -DFT_DISABLE_BZIP2=TRUE -DFT_DISABLE_PNG=TRUE \
-DFT_DISABLE_HARFBUZZ=TRUE -DFT_DISABLE_BROTLI=TRUE \
>/dev/null
cmake -S "$FT_SRC" -B "$FT_BUILD" -DCMAKE_SYSTEM_NAME=Linux -DCMAKE_C_COMPILER="$CC" \
-DCMAKE_TRY_COMPILE_TARGET_TYPE=STATIC_LIBRARY -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF \
-DFT_DISABLE_ZLIB=TRUE -DFT_DISABLE_BZIP2=TRUE -DFT_DISABLE_PNG=TRUE -DFT_DISABLE_HARFBUZZ=TRUE -DFT_DISABLE_BROTLI=TRUE >/dev/null
cmake --build "$FT_BUILD" -j"$(nproc)" >/dev/null 2>&1
fi
[ -f "$FT_BUILD/libfreetype.a" ] || { echo "FreeType build FAILED"; exit 1; }
FT_INCS="-I$FT_BUILD -I$FT_INC_BUILD -I$FT_INC_SRC"
echo "==> freetype.a: $(du -h "$FT_BUILD/libfreetype.a" | cut -f1)"
FT_INCS="-I$FT_BUILD -I$FT_BUILD/include -I$FT_SRC/include"
# ---------- 3) 宿主 core 单测 ----------
echo "==> host core test"
$HOST_CC -O2 -Wall -Wextra -Icore -I. test/test_reader_core.c \
core/rdenc.c core/rdlayout.c core/rdtxt.c -o test/test_reader_core_host -lm
COREINC="-Icore -Ivendor/miniz -Ivendor/stb -Ivendor/libmobi/src -Ivendor/zlib"
MZFLAGS="-DMINIZ_NO_ZLIB_COMPATIBLE_NAMES -DMINIZ_NO_TIME"
# ---------- 3) miniz + libmobi 对象(mipsel) ----------
build_mipsel_libs() {
for f in vendor/miniz/miniz_tinfl.c vendor/miniz/miniz_tdef.c vendor/miniz/miniz_zip.c vendor/miniz/miniz.c; do
b="$(basename "$f" .c)"; [ -f "$OBJ/mz_$b.o" ] || $CC -Os -static -Wall -Wextra -Ivendor/miniz $MZFLAGS -c "$f" -o "$OBJ/mz_$b.o"
done
for f in vendor/libmobi/src/*.c; do b="$(basename "$f" .c)"; [ "$b" = "miniz" ] && continue
[ -f "$OBJ/lm_$b.o" ] || $CC -Os -static -w -Ivendor/libmobi/src -Ivendor/zlib -c "$f" -o "$OBJ/lm_$b.o"
done
[ -f "$OBJ/rdimg.o" ] || $CC -Os -static -Wall -Wextra -Ivendor/stb -DSTB_IMAGE_IMPLEMENTATION -c engine/rdimg.c -o "$OBJ/rdimg.o"
}
build_mipsel_libs
# ---------- 4) 宿主 loader 单测(miniz/libmobi 用宿主编一遍) ----------
echo "==> host core test (R1)"
$HOST_CC -O2 -Wall -Wextra -Icore -I. test/test_reader_core.c core/rdenc.c core/rdlayout.c core/rdtxt.c -o test/test_reader_core_host -lm
./test/test_reader_core_host
# ---------- 4) qemu-mipsel FreeType 冒烟 ----------
echo "==> host loaders test"
HOBJ="$OBJ/host"; mkdir -p "$HOBJ"
for f in vendor/miniz/miniz_tinfl.c vendor/miniz/miniz_tdef.c vendor/miniz/miniz_zip.c vendor/miniz/miniz.c; do
b="$(basename "$f" .c)"; [ -f "$HOBJ/mz_$b.o" ] || $HOST_CC -O2 -Ivendor/miniz $MZFLAGS -c "$f" -o "$HOBJ/mz_$b.o"
done
for f in vendor/libmobi/src/*.c; do b="$(basename "$f" .c)"; [ "$b" = "miniz" ] && continue
[ -f "$HOBJ/lm_$b.o" ] || $HOST_CC -O2 -w -Ivendor/libmobi/src -Ivendor/zlib -c "$f" -o "$HOBJ/lm_$b.o"
done
HCORE=""
for f in rdxml rdhtml rdsb rdzip rdsrc rdepub rdfb2 rdmobi rdcbz rdenc rdtxt; do
$HOST_CC -O2 -Wall -Wextra $COREINC -c core/$f.c -o "$HOBJ/core_$f.o"
done
python3 tools/gen_samples.py >/dev/null
$HOST_CC -O2 -Wall -Wextra $COREINC test/loaders_smoke.c "$HOBJ"/*.o -lz -lm -o test/loaders_smoke_host
./test/loaders_smoke_host /tmp/rd_samples
# ---------- 5) qemu-mipsel FreeType 冒烟 ----------
echo "==> mipsel ft smoke"
$CC -Os -static -Wall -Wextra -Iengine $FT_INCS test/rdft_smoke.c engine/rdft.c \
"$FT_BUILD/libfreetype.a" -o test/rdft_smoke_mipsel
$CC -Os -static -Wall -Wextra -Iengine $FT_INCS test/rdft_smoke.c engine/rdft.c "$FT_BUILD/libfreetype.a" -o test/rdft_smoke_mipsel
timeout 120 qemu-mipsel ./test/rdft_smoke_mipsel "${FONT:-$ROOT/font/src/MiSans-Regular.otf}"
# ---------- 4b) qemu-mipsel 书桥全链路冒烟 ----------
echo "==> mipsel book smoke"
$CC -Os -static -Wall -Wextra -Iapp -Icore -Iengine $FT_INCS test/book_smoke.c \
app/book.c core/rdenc.c core/rdlayout.c core/rdtxt.c engine/rdft.c \
"$FT_BUILD/libfreetype.a" -o test/book_smoke_mipsel
# ---------- 6) qemu-mipsel 书桥冒烟(TXT + 四格式) ----------
echo "==> mipsel book smoke (TXT)"
$CC -Os -static -Wall -Wextra -Iapp -Icore -Iengine $FT_INCS $COREINC test/book_smoke.c \
app/book.c core/rdenc.c core/rdlayout.c core/rdtxt.c core/rdxml.c core/rdhtml.c core/rdsb.c core/rdzip.c \
core/rdsrc.c core/rdepub.c core/rdfb2.c core/rdmobi.c core/rdcbz.c engine/rdft.c \
"$OBJ"/mz_*.o "$OBJ"/lm_*.o "$OBJ"/rdimg.o "$SYSROOT/lib/libz.a" "$FT_BUILD/libfreetype.a" -o test/book_smoke_mipsel
timeout 120 qemu-mipsel ./test/book_smoke_mipsel "${FONT:-$ROOT/font/src/MiSans-Regular.otf}"
# ---------- 5) reader 二进制(mipsel 静态) ----------
echo "==> mipsel formats smoke"
$CC -Os -static -Wall -Wextra -Iapp -Icore -Iengine $FT_INCS $COREINC test/formats_smoke.c \
app/book.c core/rdenc.c core/rdlayout.c core/rdtxt.c core/rdxml.c core/rdhtml.c core/rdsb.c core/rdzip.c \
core/rdsrc.c core/rdepub.c core/rdfb2.c core/rdmobi.c core/rdcbz.c engine/rdft.c \
"$OBJ"/mz_*.o "$OBJ"/lm_*.o "$OBJ"/rdimg.o "$SYSROOT/lib/libz.a" "$FT_BUILD/libfreetype.a" -o test/formats_smoke_mipsel
timeout 150 qemu-mipsel ./test/formats_smoke_mipsel "${FONT:-$ROOT/font/src/MiSans-Regular.otf}" /tmp/rd_samples
# ---------- 7) reader 二进制(mipsel 静态) ----------
echo "==> reader binary"
CORE="core/rdenc.c core/rdlayout.c core/rdtxt.c"
ENGINE="engine/rdft.c"
APP="app.c app/shelf.c app/reader.c app/book.c app/progress.c"
$CC -Os -static -Wall -Wextra -I../sdk -I. -Icore -Iapp -Iengine $FT_INCS \
$CORE $APP $ENGINE \
../sdk/meow_ui.a "$SYSROOT/lib/libz.a" "$FT_BUILD/libfreetype.a" -lpthread -o reader
CORE_L="core/rdenc.c core/rdlayout.c core/rdtxt.c core/rdxml.c core/rdhtml.c core/rdsb.c core/rdzip.c core/rdsrc.c core/rdepub.c core/rdfb2.c core/rdmobi.c core/rdcbz.c"
ENGINE_L="engine/rdft.c"
APP_L="app.c app/shelf.c app/reader.c app/book.c app/progress.c"
$CC -Os -static -Wall -Wextra -I../sdk -I. -Icore -Iapp -Iengine $FT_INCS $COREINC \
$CORE_L $APP_L $ENGINE_L "$OBJ"/mz_*.o "$OBJ"/lm_*.o "$OBJ"/rdimg.o \
../sdk/meow_ui.a "$SYSROOT/lib/libz.a" "$FT_BUILD/libfreetype.a" -lpthread -o reader
ls -la reader
file reader
md5sum reader
echo " 运行(qemu): qemu-mipsel ./reader"
echo "build_reader OK"