Skip to content

PDF Oxide 与 lopdf 对比

lopdf 是一个用于直接操作 PDF 对象的底层 Rust crate。PDF Oxide 则是一个内置文本提取、创建和编辑功能的高层库。两者面向的使用场景从根本上就不同。

主要区别

抽象层级。 lopdf 给你的是原始 PDF 对象——字典、流和交叉引用表。它没有文本提取,没有字体解码,也没有图像导出。PDF Oxide 提供专门设计的方法:extract_text()extract_images()to_markdown()

可靠性。 lopdf 在 3,830 个 PDF 的测试语料库中有 20% 无法解析。在它能解析的 PDF 中,57% 产生空输出,因为 lopdf 没有文本提取功能——你拿到的是对象,却得不到文本。PDF Oxide 通过率达到 100%。

可解析 PDF 上的速度。 在原始对象解析方面 lopdf 更快:平均 0.3ms 对 PDF Oxide 的 0.8ms。但 lopdf 不做任何文本提取工作——你需要自己实现字体解码、CMap 解析、间距分析和阅读顺序。

快速对比

PDF Oxide lopdf
API 层级 高层 底层
文本提取 内置(生产级)
通过率(3,830 个 PDF) 100% 80.2%
平均解析时间 0.8ms 0.3ms
图像提取 内置 手动(原始流)
表单字段 读 + 写 手动(原始字典)
PDF 创建 支持(Markdown/HTML) 支持(原始对象)
Markdown/HTML 输出 支持 不支持
加密 读 + 写 不支持
渲染 支持 不支持
PDF/A 校验 支持 不支持
许可证 MIT MIT

lopdf 做不到的事

lopdf 提供对 PDF 对象的访问,但文本提取需要按照 PDF 规范来解释这些对象。以下是你需要自己实现的部分:

  1. 内容流解析——解析类 PostScript 的操作符(Tj、TJ、Tm、Tf 等)
  2. 字体解析——查找 /Font 资源,解析间接引用
  3. CMap/ToUnicode 解码——将字形 ID 转换为 Unicode 字符
  4. 字体度量间距——根据字体描述符计算字符宽度
  5. 文本矩阵变换——应用 Tm、Td、T* 操作符来定位文本
  6. 阅读顺序——为多栏排版确定正确的顺序
  7. 连字重建——处理 fi、fl、ffi 连字
  8. CJK 编码——解码中文、日文、韩文的文本编码

这意味着数千行代码以及对 ISO 32000 的深入理解。PDF Oxide 在内部处理了全部这些工作。

代码并排对比

文本提取

PDF Oxide:

use pdf_oxide::PdfDocument;

let mut doc = PdfDocument::open("report.pdf")?;
let text = doc.extract_text(0)?;
println!("{}", text);

lopdf:

use lopdf::Document;

let doc = Document::load("report.pdf")?;

// lopdf does not provide text extraction.
// You get access to PDF objects only:
let page_id = doc.page_iter().next().unwrap();
let page = doc.get_dictionary(page_id)?;
let contents = page.get("Contents")?;
let stream = doc.get_object(contents.as_reference()?)?;

// To get actual text, you must:
// 1. Parse content stream operators
// 2. Resolve font references from /Resources
// 3. Decode CMap/ToUnicode mappings
// 4. Apply text matrix transformations
// 5. Handle encoding differences
// ... (hundreds to thousands of lines of code)

PDF 创建

PDF Oxide:

use pdf_oxide::api::Pdf;

let pdf = Pdf::from_markdown("# Report\n\n| Q1 | Q2 |\n|---|---|\n| $1M | $2M |")?;
pdf.save("report.pdf")?;

lopdf:

use lopdf::{Document, Object, Stream, dictionary};

let mut doc = Document::with_version("1.5");

// Create font dictionary
let font_id = doc.add_object(dictionary! {
    "Type" => "Font",
    "Subtype" => "Type1",
    "BaseFont" => "Helvetica",
});

// Create resources
let resources_id = doc.add_object(dictionary! {
    "Font" => dictionary! { "F1" => font_id },
});

// Create content stream (raw PostScript operators)
let content = Stream::new(
    dictionary! {},
    b"BT /F1 12 Tf 72 720 Td (Hello World) Tj ET".to_vec(),
);
let content_id = doc.add_object(content);

// Create page
let page_id = doc.add_object(dictionary! {
    "Type" => "Page",
    "MediaBox" => vec![0.into(), 0.into(), 612.into(), 792.into()],
    "Contents" => content_id,
    "Resources" => resources_id,
});

// Wire up page tree
let pages_id = doc.add_object(dictionary! {
    "Type" => "Pages",
    "Kids" => vec![page_id.into()],
    "Count" => 1,
});
doc.add_object(dictionary! {
    "Type" => "Catalog",
    "Pages" => pages_id,
});

doc.save("report.pdf")?;

加密 PDF

PDF Oxide:

use pdf_oxide::PdfDocument;

let doc = PdfDocument::open_with_password("encrypted.pdf", "secret")?;
let text = doc.extract_text(0)?;
println!("{}", text);

lopdf:

// lopdf does not support encrypted PDFs.
// Loading an encrypted PDF will fail or produce undecrypted streams.

可靠性对比

指标 PDF Oxide lopdf
成功解析的 PDF 3,823 / 3,823(100%) 3,071 / 3,823(80.2%)
有文本输出的 PDF 3,823 / 3,823 约 1,320 / 3,823(估算)
加密 PDF 支持 支持 不支持
畸形 PDF 恢复 支持 不支持

lopdf 的 80.2% 通过率意味着大约每 5 个 PDF 就有 1 个失败。失败发生在加密文档、带有非标准 xref 表的 PDF,以及使用交叉引用流的文档上。PDF Oxide 通过宽容解析和回退策略处理了所有这些情况。

何时选用哪一个

在以下情况选择 PDF Oxide:

  • 你需要文本提取、图像提取或任何内容级别的操作
  • 你想用单个 crate 完成读 + 写 + 创建
  • 你需要可靠地处理所有 PDF(加密、畸形、复杂的)
  • 你需要 Markdown/HTML 输出、渲染或 OCR
  • 你需要合规校验(PDF/A、PDF/X、PDF/UA)

在以下情况选择 lopdf:

  • 你需要直接访问 PDF 对象以进行自定义处理
  • 你正在构建一个工作在对象层级的专用 PDF 工具
  • 你需要通过直接操作对象树来合并文档
  • 你的 PDF 简单且格式规范(未加密、标准 xref 表)

两者结合:

用 PDF Oxide 处理高层操作,用 lopdf 应对需要原始对象访问的边缘情况:

[dependencies]
pdf_oxide = "0.3"
lopdf = "0.32"

相关页面