跳转至

教材页面渲染与拼接

本页最后更新于 ,内容可能过时

把 PDF 的页渲染成一张读图用的大图, 供模型读图, 也供后面裁插图
默认一次一页, 拼多页时整图变高, 读图端会等比缩小
产物放仓库外的临时目录

环境准备

python -m pip install pymupdf pillow

临时目录取 $env:TEMP\mindcity-pdf\<教材名>

查看 PDF 的基本情况

python .claude/skills/pdf-page-image/scripts/render.py info "<pdf>"

打印页数, 文字层字符总数(不含空白), 其中可读字符数, 其中中文字符数, 平均每页可读字符, 书签目录, 首页文本片段

加 --json 换一行 JSON 输出, 内容与给人看的那份一致, 给上层程序读

python .claude/skills/pdf-page-image/scripts/render.py info "<pdf>" --json

verdict 是判定那一栏的机器可读版, 取值 scanned / garbled / thin / ok

书签目录默认全部打印, 篇幅大时用 --toc-limit N 截断
已有书签的书, 章节页码范围就从这份目录里取, 不必另找途径

判定看两个数: 可读字符占比, 与平均每页可读字符, 分四种情形

判定 含义 对策
扫描版, 无文字层 字符总数为 0 只能渲染候选页人工看图定范围
抽出来是乱码 可读字符占比不足 0.8, 或中文书里汉字占比不足 0.05 同上
文字层很薄 平均每页可读字符不足 50, 只够放页眉页码 同上
正文可用 平均每页可读字符足够多, 首页片段读得通 可以按关键词定位页码

可读字符的口径是: 非空白字符里, 控制字符与 U+0080 到 U+00FF 之外, 都算
排除那一段是因为缺 ToUnicode 映射时, 中文字节会被当成 Latin-1 解释
整篇正文恰好落进那一段, 一眼就能认出来
正经的欧洲语言里带音标的字母只占几个百分点, 由阈值吸收
换行, 制表, 全角空格不计入分母, 语文教材一个汉字一行加一行拼音, 换行能占抽取结果的三成

这个口径不认中文字符数, 所以英文, 俄文, 希腊文教材与中文教材走同一套阈值

另一条判据防的是 OCR 写回文字层的扫描件, 那种乱码落在 ASCII 段, 可读占比能到 98%, 上面那个阈值看不见
做法是先判这本书是不是中文书, 证据只取文件名, 书签标题与首页正文, 不取文字层本身
中文书里汉字占可读字符不足 0.05 就判乱码
实测一本 502 页的无机化学扫描件, 可读占比 98%, 汉字占比 0, 判 garbled

扫描版教材多数落在不能用之列, 遇到时直接渲染页面查看
这些数只是证据, 最终自己看
拿不准时渲染选中的第一页, 与抽出来的样本对照
页面是扫描图像, 或样本与页面对不上, 就走读图
字符数只用来判断能不能按关键词定位页码, 正文一律以读图为准
[只有由 epub 或 txt 等纯文本转换生成的 PDF, 文字层才是可信的原生文本, 才可以不读图]

按关键词定位页码, 匹配前会去掉所有空白, 中文 PDF 的字间间隙不影响命中

python .claude/skills/pdf-page-image/scripts/render.py search "<pdf>" "热力学第一定律"

命中页数多时只列前 40 页, 换个更长的片段可以缩小范围

python .claude/skills/pdf-page-image/scripts/render.py search "<pdf>" "第5章 脂肪族烃"

渲染并拼接

python .claude/skills/pdf-page-image/scripts/render.py render "<pdf>" --pages 37 \
    --dpi 160 --out "$tmp\p0037-d160.png"

--pages 支持单页 37, 范围 37-39, 列表 37,45
单页的产物命名 p<页号>-d<dpi>.png, 多页的产物命名 p<起>-<止>-d<dpi>.png

多页纵向堆叠, 每块左上角画一行 PDF 页号, 页与页之间留空白

  • --dpi 默认 160
  • --gap 页间空白像素, 默认 16
  • --no-label 不画页号标签条
  • --manifest 坐标清单路径, 默认与页面图同名 .json

默认一次一页
单页在 160 dpi 下约 1030×1450, 读图时按原分辨率显示, 正文清楚
拼多页时整图变高, 读图端等比缩小, 页数越多正文越糊
4 页在 160 dpi 下约 1030×5940, 被压缩到约 350×2000, 中文无法辨认

判据是渲染后先读一遍, 不是固定的页数与 dpi
正文发虚, 或者整图被压缩得看不清, 就减页数或者降 dpi
单页仍看不清细节时, 走 pdf-figure-crop 的 --from-page 按更高 dpi 重裁那一页

产物格式

页面图是各页纵向堆叠的 png, 单页时就是那一页
坐标清单是 json, 记录每页在页面图里的像素区间

{"pdf":"...", "dpi":160, "gap":16, "label_h":22,
 "pages":[{"pdf_page":37, "y0":0, "y1":1474, "w":1037, "h":1452}]}

y0 是该页块(含页号标签条)在页面图中的起始行, y1 是结束行
h 是页面内容本身的高度
pdf 记的是绝对路径, 给后面高清重裁用

注意事项

  • 读图用的 dpi 取 130 到 200, 取值更高只会增加读图成本与图床占用
  • 单页需要高分辨率时, 不要重渲整张页面图, 走 pdf-figure-crop 的 --from-page
  • 同一本书的页面宽度可能差几个像素, 页面图按最宽的一页对齐
  • 仓库路径可能含 [ ] ( ) 和中文, 使用时加引号