docparser-pdf 0.2.0

docparser PDF 后端:PDFium 常驻 worker 逐文本对象抽取(CJK/CID 可用)+ pdf-extract 纯 Rust 兜底
Documentation

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 语义)

安装

cargo add docparser-pdf   # 或 cargo add docparser-sdk(含 core + office)

运行时依赖(重要)

PDFium 是运行时动态库,不由本 crate 打包。查找顺序:

  1. DOCPARSER_PDFIUM_LIB 显式指定(推荐,发布环境务必显式设置);
  2. <exe>/../lib/{libpdfium.so|libpdfium.dylib|pdfium.dll}(自包含发布布局);
  3. home 下常见 conda 布局(~/miniconda3/lib/python3.x/site-packages/{pypdfium2_raw,liteparse});
  4. 扫描常见 Python site-packages(python*/Lib/site-packages、AppData\Local\Programs\Python\Python3xx、 /usr/lib/python3* 等)。HOME 在 Windows 上常常为空,这里同时看 USERPROFILE 与 HOMEDRIVE+HOMEPATH;docparser-cli doctor 可打印实际绑定到的库路径。
use docparser_core::{DocumentEngine, Registry};
use docparser_pdf::{LayoutMode, PdfOptions};

let mut reg = Registry::new();
docparser_core::plugins::text::register_defaults(&mut reg);

// 默认选项(LayoutMode::Auto、不限页数)
docparser_pdf::register_defaults(&mut reg);

// 或指定选项:不重排版式 + 只解析前 20 页
let options = PdfOptions::new()
    .with_layout_mode(LayoutMode::Raw)
    .with_max_pages(20);
docparser_pdf::register_with(&mut reg, &options);

let doc = DocumentEngine::new(reg).parse("report.pdf")?;
println!("{} 页 / {} 字符", doc.pages.len(), doc.meta.text_length);
if let Some(pdf) = doc.meta.extra.get("pdf") {
    println!("pdf 元数据: {pdf}"); // pdf_version / complexity / truncated / layout_modes …
}

find_library() / find_ocr_binary() / find_ocr_models() 可直接用于自检:

println!("{:?}", docparser_pdf::find_library());

示例

cargo run -p docparser-pdf --example layout_modes -- doc.pdf   # auto vs raw 对照

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