Skip to content

PDF Oxide 快速上手(Zig)

PDF Oxide 是内置文本提取功能、速度最快的 PDF 库 — 在 3,830 个 PDF 上达成 0.8ms 平均耗时、100% 通过率。Zig 绑定通过 @cImport 在 pdf_oxide C ABI 之上提供了地道的 Zig 接口 — 无需任何中间层,享有一等的 C 互操作能力。句柄是带 deinit 的结构体,返回的 C 字符串/缓冲区会被复制到调用方提供的 allocator 中。

固定使用 Zig 0.15.1 — 1.0 之前的版本及其 C 导入 API 在各发行版之间会发生变动,因此 CI 也使用同一版本。

安装

该绑定链接的是默认特性的 cdylib(而非 Python wheel)。先构建原生库,再让 Zig 指向头文件和 cdylib:

# 1. build the native library (shipped binding feature set)
cargo build --release --lib \
  --features ocr,rendering,signatures,barcodes,tsa-client,system-fonts

# 2. test + run the example
cd zig
LD_LIBRARY_PATH="$PWD/../target/release" \
  zig build test \
    -DPDF_OXIDE_INCLUDE_DIR="$PWD/../include" \
    -DPDF_OXIDE_LIB_DIR="$PWD/../target/release"

LD_LIBRARY_PATH="$PWD/../target/release" \
  zig build example \
    -DPDF_OXIDE_INCLUDE_DIR="$PWD/../include" \
    -DPDF_OXIDE_LIB_DIR="$PWD/../target/release"

在你自己的代码中,导入该模块即可开始使用:

const pdf_oxide = @import("pdf_oxide");

打开 PDF

Document.open 打开文件(内存中的数据则用 Document.openFromBytes),并查看其元数据。每个句柄都持有 C 资源,因此要搭配 defer doc.deinit() 一起使用。

const std = @import("std");
const pdf_oxide = @import("pdf_oxide");

pub fn main() !void {
    const a = std.heap.page_allocator;

    var doc = try pdf_oxide.Document.open("research-paper.pdf");
    defer doc.deinit();

    std.debug.print("pages:   {d}\n", .{try doc.pageCount()});
    const v = doc.version();
    std.debug.print("version: {d}.{d}\n", .{ v.major, v.minor });
    std.debug.print("encrypted: {}\n", .{doc.isEncrypted()});
}

文本提取

extractText 返回单页(从 0 开始计数)的文本。结果归你传入的 allocator 所有,因此用完后请释放它。

const a = std.heap.page_allocator;

var doc = try pdf_oxide.Document.open("report.pdf");
defer doc.deinit();

const text = try doc.extractText(a, 0);
defer a.free(text);
std.debug.print("{s}\n", .{text});

整文档版本会一次性提取所有页面:

const all_text = try doc.toPlainTextAll(a);
defer a.free(all_text);
std.debug.print("{s}\n", .{all_text});

Markdown 与 HTML 转换

可将单页或整个文档转换为 Markdown 或 HTML。每个方法都返回一个归 allocator 所有的切片。

const md = try doc.toMarkdown(a, 0);
defer a.free(md);
std.debug.print("{s}\n", .{md});

const md_all = try doc.toMarkdownAll(a);
defer a.free(md_all);

const html = try doc.toHtml(a, 0);
defer a.free(html);

单词级提取

extractWords 返回一个 Word 结构体切片,每个结构体包含文本、边界框、字体和加粗标志。用配套的 freeWords 辅助函数释放整个切片 — 它会一并释放每个单词的字符串以及底层切片。

const words = try doc.extractWords(a, 0);
defer pdf_oxide.Document.freeWords(a, words);

for (words) |w| {
    std.debug.print("'{s}' at ({d:.1}, {d:.1}) font={s} size={d:.1} bold={}\n", .{
        w.text, w.bbox.x, w.bbox.y, w.fontName, w.fontSize, w.bold,
    });
}

Word 字段:

字段 类型 说明
text []u8 单词文本(归 allocator 所有)
bbox Bbox { x, y, width, height },单位为点
fontName []u8 PostScript 字体名
fontSize f32 字号,单位为点
bold bool 该文本段是否为加粗

同样的模式也能提取字符和文本行:

const chars = try doc.extractChars(a, 0);
defer pdf_oxide.Document.freeChars(a, chars);

const lines = try doc.extractTextLines(a, 0);
defer pdf_oxide.Document.freeTextLines(a, lines);

搜索

search 在单页内查找;searchAll 则扫描每一页。两者都接受一个以 NUL 结尾的关键词和一个 case_sensitive 标志,并返回一个 SearchResult 切片。

const hits = try doc.searchAll(a, "configuration", false);
defer pdf_oxide.Document.freeSearchResults(a, hits);

for (hits) |hit| {
    std.debug.print("page {d}: '{s}' at ({d:.0}, {d:.0})\n", .{
        hit.page, hit.text, hit.bbox.x, hit.bbox.y,
    });
}

若要将搜索限定在单页内,请使用 search 并传入页索引:

const page_hits = try doc.search(a, 0, "Alpha", false);
defer pdf_oxide.Document.freeSearchResults(a, page_hits);

创建 PDF

Pdf 类型可从 Markdown、HTML 或纯文本构建文档。toBytes 序列化到内存;save 写入磁盘。

const a = std.heap.page_allocator;

var pdf = try pdf_oxide.Pdf.fromMarkdown("# Hello\n\nThis is a **Zig** PDF.\n");
defer pdf.deinit();

// Serialize to memory...
const bytes = try pdf.toBytes(a);
defer a.free(bytes);

// ...or write straight to disk.
try pdf.save("output.pdf");

你可以把刚构建好的 PDF 直接送回提取器走一趟完整的往返流程:

var pdf = try pdf_oxide.Pdf.fromHtml("<h1>Invoice</h1><p>Amount: $42</p>");
defer pdf.deinit();

const bytes = try pdf.toBytes(a);
defer a.free(bytes);

var doc = try pdf_oxide.Document.openFromBytes(bytes);
defer doc.deinit();

const text = try doc.extractText(a, 0);
defer a.free(text);
std.debug.print("{s}\n", .{text});

错误处理

可能失败的调用返回 Error!T,其中 Errorerror{ PdfOxide, OutOfMemory }。由于 Zig 的错误值无法携带额外数据,底层的 C-ABI 错误码通过 lastErrorCode() 暴露出来 — 请在捕获 error.PdfOxide 后立即读取它。

const text = doc.extractText(a, 99) catch |err| switch (err) {
    error.PdfOxide => {
        std.debug.print("pdf_oxide error code: {d}\n", .{pdf_oxide.lastErrorCode()});
        return;
    },
    error.OutOfMemory => return err,
};
defer a.free(text);

后续步骤