PDF Oxide をはじめる (Zig)
PDF Oxide は、テキスト抽出機能を標準で備えた最速の PDF ライブラリです。平均 0.8ms、3,830 件の PDF で 100% のパス率を達成しています。Zig バインディングは @cImport を介して pdf_oxide の C ABI 上に構築されたイディオマティックな Zig 実装で、シムを挟まず C との相互運用をファーストクラスで扱います。ハンドルは deinit を持つ struct であり、返される C の文字列やバッファは呼び出し側が渡したアロケータにコピーされます。
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 は 1 ページ分 (0 始まり) のテキストを返します。結果は渡したアロケータが所有するため、使い終わったら解放してください。
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 への変換
1 ページまたはドキュメント全体を Markdown や HTML に変換できます。いずれもアロケータが所有するスライスを返します。
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 struct のスライスを返します。スライス全体は対応する 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 |
単語のテキスト (アロケータが所有) |
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 は 1 ページ内を検索し、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,
});
}
検索を 1 ページに限定するには、ページインデックスを指定して 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 を返します。ここで Error は error{ 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);
次のステップ
- Rust をはじめる — PDF Oxide の基盤となるネイティブコア
- Python をはじめる — Python から PDF Oxide を使う
- テキスト抽出 — 詳細な抽出オプションとレシピ
- PDF の作成 — メタデータや暗号化を伴う高度な作成