Skip to content

对比 Rust PDF 库

将 PDF Oxide 与最常用的 Rust PDF crate 进行对比:lopdf、printpdf、pdf-rs 和 pdf_extract。它们各自面向不同的抽象层次和不同的使用场景。

概览

PDF Oxide lopdf printpdf pdf-rs pdf_extract
API 层级 高层 底层 中层(创建) 底层(读取) 中层(读取)
读取 PDF
写入 PDF
文本提取 是(高层) 手动 手动 是(基础)
图像提取 是(高层) 手动 手动
表单字段 读取 + 写入 手动 仅读取
PDF 创建
Markdown/HTML 输入
编辑现有 PDF 是(底层)
注释 读取 + 写入 手动 仅读取
加密 读取 + 写入
PDF/A 校验
渲染 是(tiny-skia) 部分
Python 绑定
许可证 MIT MIT MIT MIT Apache-2.0

所有库都采用宽松许可证。差异在于功能范围和抽象层次。

性能对比

完整语料库基准测试(3,830 个 PDF)

在完整的 3,830 个 PDF 语料库上进行测试——由三个独立、公开可用的测试套件组成,涵盖 PDF 规范符合性(veraPDF,2,907 个文件)、真实世界的浏览器渲染边缘情况(Mozilla pdf.js,897 个文件),以及包含畸形结构和模糊测试生成损坏的安全性/健壮性压力测试(DARPA SafeDocs,26 个文件)。参见完整语料库详情

均值 p99 通过率 文本提取 备注
PDF Oxide 0.8ms 9ms 100% 内置,生产级 Unicode、CJK、阅读顺序
oxidize_pdf 13.5ms 11ms 99.1% 基础 最大 48 秒异常值
unpdf 2.8ms 10ms 95.1% 基础 完整语料库上 185 次失败
pdf_extract 4.08ms 37ms 91.5% 基础 遗漏复杂布局
lopdf 0.3ms 2ms 80.2% 无内置提取 在 20% 的 PDF 上失败

lopdf 在它能解析的 PDF 上更快——但它在 20% 的语料库上失败,并且不提供文本提取。你需要自己构建字体解码、CMap 解析和间距分析。

pdf_extract 提供基础的文本提取,但通过率为 91.5%,在处理复杂布局、CJK 文本和带标签的 PDF 时表现吃力。oxidize_pdf 具有不错的可靠性(99.1%),但在平均提取时间上比 pdf_oxide 慢 17 倍,最坏情况下有 48 秒的异常值。unpdf 能处理完整语料库,但在 185 个 PDF 上失败。

PDF Oxide 是唯一一个将 100% 可靠性与生产级文本提取结合在一起的 Rust crate。

API 设计对比

PDF Oxide:高层、面向任务

PDF Oxide 为常见任务提供专门设计的方法。你处理的是文本、图像和表单字段——而非 PDF 对象和字典。

use pdf_oxide::PdfDocument;

let mut doc = PdfDocument::open("report.pdf")?;

// Text extraction -- one call
let text = doc.extract_text(0)?;
println!("{}", text);

// Styled spans with font metadata
let spans = doc.extract_spans(0)?;
for span in &spans {
    println!("'{}' font={} size={:.1}pt", span.text, span.font_name, span.font_size);
}

// Image extraction
let images = doc.extract_images(0)?;
for img in &images {
    println!("{}x{} {:?}", img.width, img.height, img.format);
}

// Form fields
let fields = doc.extract_form_fields()?;
for field in &fields {
    println!("{}: {:?}", field.name, field.value);
}

创建 PDF 同样简单直接:

use pdf_oxide::api::Pdf;

// From Markdown
let pdf = Pdf::from_markdown("# Report\n\n| A | B |\n|---|---|\n| 1 | 2 |")?;
pdf.save("report.pdf")?;

// From HTML
let pdf = Pdf::from_html("<h1>Report</h1><p>Content here.</p>")?;
pdf.save("report.pdf")?;

lopdf:底层对象操作

lopdf 让你直接访问 PDF 对象、流和交叉引用表。要有效使用它,你必须理解 PDF 规范。它没有内置文本提取——你需要自己浏览字典并解码流。

use lopdf::Document;

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

// Get page dictionary
let page_id = doc.page_iter().next().unwrap();
let page = doc.get_dictionary(page_id)?;

// Get content stream -- manual work
let contents = page.get("Contents")?;
let stream = doc.get_object(contents.as_reference()?)?;

// To extract text you must:
// 1. Parse the content stream operators
// 2. Resolve font references from /Resources
// 3. Decode CMap/ToUnicode mappings
// 4. Apply text matrix transformations
// 5. Handle encoding differences
//
// lopdf does not provide any of this -- it is raw object access
println!("Page has {} objects", doc.objects.len());

当你需要直接操作 PDF 结构时,lopdf 是合适的工具:合并文档、重写对象流,或构建专门的 PDF 处理器。

printpdf:仅用于创建 PDF

printpdf 是一个仅用于创建的库。它无法读取或解析现有的 PDF。它提供了一个类型化的 API,用于从零开始构建包含文本、图像和矢量图形的 PDF 文档。

use printpdf::*;

let (doc, page1, layer1) = PdfDocument::new(
    "Report", Mm(210.0), Mm(297.0), "Layer 1"
);

let current_layer = doc.get_page(page1).get_layer(layer1);

// Add text -- requires manual font loading
let font = doc.add_builtin_font(BuiltinFont::Helvetica)?;
current_layer.use_text("Hello World", 24.0, Mm(10.0), Mm(280.0), &font);

// Save
doc.save(&mut std::io::BufWriter::new(
    std::fs::File::create("output.pdf")?,
))?;

// Cannot read existing PDFs
// Cannot extract text, images, or form fields

当你只需要生成新 PDF 并且想要一个简洁、专注的创建 API 时,printpdf 是合适的工具。

pdf-rs:底层 PDF 读取

pdf-rs 将 PDF 结构解析为 Rust 类型,但只提供极少的高层功能。你可以获得对 PDF 对象的类型化访问,但仍需自己处理文本解码、字体解析和内容流解析。

use pdf::file::FileOptions;

let file = FileOptions::cached().open("report.pdf")?;

// Access page objects
let page = file.get_page(0)?;
let media_box = page.media_box()?;
println!("Page size: {:?}", media_box);

// Content stream access -- low-level
if let Some(ref contents) = page.contents {
    // Returns raw operations -- you must interpret them
    // No built-in text assembly, font decoding, or layout analysis
}

// Cannot write or modify PDFs

当你需要一个类型安全的 PDF 解析器用于分析、校验或构建自定义渲染器时,pdf-rs 是合适的工具。

按任务划分的功能对比

文本提取

内置 质量 所需投入
PDF Oxide 生产级(Unicode、CJK、阅读顺序) 一次方法调用
pdf_extract 基础(遗漏复杂布局) 一次方法调用
lopdf 不适用 数百行自定义代码
printpdf 不适用 不可能(仅写入)
pdf-rs 不适用 需要大量自定义代码

PDF Oxide 处理 CMap/ToUnicode 解码、基于字体度量的间距、结构树阅读顺序以及连字重建。在 lopdf 或 pdf-rs 之上实现等效功能需要数千行代码和深厚的 PDF 规范知识。

PDF 创建

方式 Markdown/HTML 输入 表格 条形码
PDF Oxide 高层 + 底层
lopdf 原始对象构建
printpdf 类型化图层 API
pdf-rs 不适用(仅读取) 不适用 不适用 不适用

加密

读取加密 写入加密 算法
PDF Oxide RC4-40、RC4-128、AES-128、AES-256
lopdf
printpdf
pdf-rs 部分 仅 RC4

合规性

PDF/A PDF/X PDF/UA
PDF Oxide 校验 + 转换 校验 校验
lopdf
printpdf 部分(PDF/A-1b 输出)
pdf-rs

依赖占用

依赖项 编译时间 二进制大小
PDF Oxide ~40(核心) ~30s ~4 MB
lopdf ~15 ~10s ~1 MB
printpdf ~20 ~15s ~2 MB
pdf-rs ~25 ~20s ~2 MB

PDF Oxide 的依赖项更多,因为它包含了字体解析、图像解码、内容流解释和加密——这些功能是其他库留给用户处理或干脆省略的。启用所有可选功能(renderingbarcodesoffice)后,数量会上升到约 100。

组合使用多个库

由于它们都采用宽松许可证,你可以在同一个项目中组合使用:

[dependencies]
pdf_oxide = "0.3"
lopdf = "0.32"        # Optional: raw object access for edge cases

常见模式:

  • PDF Oxide + lopdf:用 PDF Oxide 做提取和创建,对于需要原始对象操作的边缘情况回退到 lopdf。
  • PDF Oxide + printpdf:用 PDF Oxide 做读取,用 printpdf 做专门的创建工作流。

使用场景矩阵

“我需要从 PDF 中提取文本”

Crate 适合吗? 备注
PDF Oxide 最佳提取质量、100% 通过率、阅读顺序、字体元数据
pdf_extract 部分 基础提取,91.5% 通过率
lopdf 无文本提取
printpdf 无法读取 PDF
pdf-rs 部分 基础解析,无高层文本提取

“我需要创建 PDF”

Crate 适合吗? 备注
PDF Oxide 高层(Markdown/HTML)和底层 API
lopdf 部分 底层对象构建
printpdf 简洁的创建 API,无读取功能
pdf-rs 仅读取

“我需要编辑现有的 PDF”

Crate 适合吗? 备注
PDF Oxide 类 DOM 编辑、注释、表单
lopdf 部分 底层对象操作
printpdf 无法读取 PDF
pdf-rs 仅读取

“我需要完整的生命周期(提取 + 创建 + 编辑)”

Crate 适合吗? 备注
PDF Oxide 唯一覆盖这三者的 crate
lopdf + printpdf 部分 两个 crate,无文本提取
pdf-rs + printpdf 部分 两个 crate,无编辑功能

各自的适用场景

选择 PDF Oxide,如果你需要不止一种 PDF 能力(提取 + 创建,或提取 + 编辑),并且想要一个经过充分测试、具备 100% 可靠性的单一依赖。

选择 lopdf,如果你需要底层 PDF 结构操作,并且乐于直接面对 PDF 规范。适合合并、拆分和批量 PDF 处理。

选择 printpdf,如果你只创建 PDF 而从不需要读取它们。是报表和文档生成的最简洁 API。

选择 pdf-rs,如果你需要一个符合规范的解析器用于 PDF 分析,或者正在构建自己的渲染管线。

选择 pdf_extract,如果你需要基础的文本提取,且不要求高可靠性或复杂布局支持。

相关页面