docparser-pdf
PDF 后端:PDFium 常驻 worker(一条线程独占 native 状态,通道收发)逐行抽取
text + bbox(text page rect 分段,含 Form XObject 内文本),支持 CJK/CID 字体;
pdf-extract(纯 Rust)作为兜底后端。
产出结构:页(width/height/rotation)→ 行归并(基线对齐)→ 表格检测(≥3 列 + ≥60% 行一致性)
→ 多栏阅读序 → Paragraph / Table 块 + Image(bbox / px / OCR 文本)。
能力
| 能力 | 说明 |
|---|---|
| 文本抽取 | 逐行取 text + bbox(pt、左上原点):FPDFText_CountRects + FPDFText_GetBoundedText,含 Form XObject 内文本(代码清单/图表标注/水印);rect 高 > 25% 页高的跨高条块跳过(否则会吞入叠压正文);CJK/CID 走 PDFium 内建 CMap,不依赖 ToUnicode |
| 版式 | LayoutMode::{Auto, Rebuild, Raw}:Auto/Rebuild 做行归并 + 表格 + 分栏;Raw 按内容流顺序 |
| 分栏阅读序 | 先算「相邻行同 y 且 x0 跨半页宽」的交错指纹,<0.25 不改序(内容流本已是栏序,盲改会把好顺序改坏);需重排时走沟槽法(窄项 x 占位直方图,≥20pt 零占位带即栏沟,与 Python _column_order 同口径),沟槽被跨栏元素填平/切分失败则回退视觉行行首 x0 聚类(先按基线聚行、再按 max(12, 2×字高) 横向间隙切开;阈值用行首起点自己的跨度,不能用整页内容跨度——表格页会把 126pt 内的起点全链成一栏) |
| 表格 | ≥3 列 + ≥60% 行一致,行按基线对齐聚行(与 Python _group_lines 同口径:底边差 ≤ 0.3×(行高+字高),用纵向重叠会在密排行/标签基线参差时并错行);另加自洽性判据:bbox 内散落文本多于表内、且 >40 字符长句单元格占比 ≥0.2 → 不认表(避免「正文行 + 图内标签」凑成假表格把正文掰断) |
| 页数上限 | PdfOptions::max_pages,超出时 meta.extra.pdf.truncated = true |
| 元数据 | pdf_version / title / author / producer / creator,页级 complexity(chars / garbled_ratio / needs_ocr)、page_errors、layout_modes,全部在 meta.extra.pdf(JSON 字符串) |
| 页眉页脚 | 顶/底 7% 边带 + 数字归一化,多页重复行剔除(meta.extra.pdf.headers_footers) |
| OCR | 稀疏/乱码页整页渲染 200dpi → docparser-ocr;嵌入图 OCR opt-in(DOCPARSER_OCR_IMAGES=1) |
| 兜底 | pdfium-text-v1(pdf-extract,纯 Rust,无 dylib;无 bbox → 自动退化为 raw 语义) |
安装
运行时依赖(重要)
PDFium 是运行时动态库,不由本 crate 打包。查找顺序:
DOCPARSER_PDFIUM_LIB显式指定(推荐,发布环境务必显式设置);<exe>/../lib/{libpdfium.so|libpdfium.dylib|pdfium.dll}(自包含发布布局);- home 下常见 conda 布局(
~/miniconda3/lib/python3.x/site-packages/{pypdfium2_raw,liteparse}); - 扫描常见 Python site-packages(
python*/Lib/site-packages、AppData\Local\Programs\Python\Python3xx、/usr/lib/python3*等)。HOME在 Windows 上常常为空,这里同时看USERPROFILE与HOMEDRIVE+HOMEPATH;docparser-cli doctor可打印实际绑定到的库路径。
use ;
use ;
let mut reg = new;
register_defaults;
// 默认选项(LayoutMode::Auto、不限页数)
register_defaults;
// 或指定选项:不重排版式 + 只解析前 20 页
let options = new
.with_layout_mode
.with_max_pages;
register_with;
let doc = new.parse?;
println!;
if let Some = doc.meta.extra.get
find_library() / find_ocr_binary() / find_ocr_models() 可直接用于自检:
println!;
示例
CLI 侧等价开关:docparser-cli parse doc.pdf --layout-mode raw --max-pages 20。
扫描件与嵌入图 OCR
- 纯文本层可用即不触发 OCR;稀疏/乱码页才路由到 OCR(
BackendPolicy.enable_ocr)。 - OCR 走外部可执行文件
docparser-ocr(DOCPARSER_OCR_BIN)+ 模型目录(DOCPARSER_OCR_MODELS), 缺失时降级为纯文本,不报错。 - 嵌入图文字默认关闭,
DOCPARSER_OCR_IMAGES=1开启。
后端与错误
| 后端 id | 形态 | 说明 |
|---|---|---|
pdfium-worker |
bundled | 逐文本对象 + CJK CMap |
pdfium-text-v1 |
bundled | pdf-extract 兜底 |
encrypted / unsupported 为终止码,不回退;其余错误按策略链继续尝试。
已知差异(相对 Python 参考实现)
| 项 | 现状 |
|---|---|
| pypdf 轻量路由(T1/T2/T3/T5 双维度 + 页数交叉点) | 不做:固定走 PDFium(小文件多 ~17ms 启动开销,结论不变) |
speed 极速模式(整页 get_text_range) |
不做:LayoutMode::Raw 是最近似替代(仍逐对象取文本) |
| 嵌入图 md5 去重键 | 已做:有字节 → md5 十六进制(image_dedup_key),无字节 → "{w}x{h}@{ext}" 兜底(与 Python 同形);跨载体的键值不保证相等——原始流路径一致时才逐字节相同,位图回退两侧编码器不同(Rust image vs Pillow),真实 JPX 图实测两侧各得各的 md5 |
| 单页失败 | 渲染失败记入 meta.extra.pdf.page_errors,不影响其它页 |
逐页文本 sim 残余 |
11 件 / 162 页真实语料上 cov 全部 1.0(最低 0.9994)、 3 篇整篇逐字一致;sim 均值最低 0.9991、单页最低 0.990,只剩"一个 token 落点"级差异(tracemonkey p7 项目符号 •/\x02 等)——见 python/bench/REAL_CHECKLIST.md |
测试注意:仓库不含真实 PDF 语料(python/bench/corpus_pdf/real/ 与 corpus_real/ 均已 gitignore,
本地用 cd python && python bench/fetch_arxiv.py --max 8 现取),
改动行归并/表格/分栏逻辑后请自备语料复测(cargo test -p docparser-pdf 只覆盖纯函数)。
许可
MIT OR Apache-2.0