ハクソク

世界を動かす技術を、日本語で。

Zpdf: ZigによるPDFテキスト抽出

概要

zpdfはZigで書かれたPDFテキスト抽出ライブラリ。
大容量ファイル並列処理に強い設計。
PDFiumMuPDFと比較し、速度・精度で優位性。
CLIやPythonバインディングも提供。
用途や特徴に応じた使い分けが可能。

zpdf: Zig製PDFテキスト抽出ライブラリ

  • Zig言語で開発されたPDFテキスト抽出ライブラリ
  • メモリマップドファイルによる大容量PDFの効率的処理
  • アリーナアロケーションを用いたストリーミング抽出
  • 各種デコードフィルタ対応(FlateDecode, ASCII85, ASCIIHex, LZW, RunLength)
  • フォントエンコーディング(WinAnsi, MacRoman, ToUnicode CMap)サポート
  • XRefテーブル/ストリーム解析(PDF 1.5以降対応)
  • 厳格/寛容なエラーハンドリング切替可能
  • マルチスレッド並列ページ抽出対応
  • ベンチマーク機能搭載

ベンチマーク結果(Apple M4 Pro, 並列・ストリーム順)

| ドキュメント | ページ数 | zpdf | pdfium | MuPDF | |----------------------|----------|--------|--------|--------| | Intel SDM | 5,252 | 227ms | 3,632ms| 2,331ms| | Pandas Docs | 3,743 | 762ms | 2,379ms| 1,237ms| | C++ Standard | 2,134 | 671ms | 1,964ms| 1,079ms| | Acrobat Reference | 651 | 120ms | - | - | | US Constitution | 85 | 24ms | 63ms | 58ms |

  • 最大スループット:23,137ページ/秒(Intel SDM)
  • 精度:いずれもMuPDF基準で99%以上の文字認識率

ビルド・要件

  • Zig 0.15.2以降必須
  • ビルド:zig build -Doptimize=ReleaseFast
  • テスト:zig build test

ライブラリ利用例(Zig)

const zpdf = @import("zpdf");
pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();
    const doc = try zpdf.Document.open(allocator, "file.pdf");
    defer doc.close();
    var buf: [4096]u8 = undefined;
    var writer = std.fs.File.stdout().writer(&buf);
    defer writer.interface.flush() catch {};
    for (0..doc.pages.items.len) |page_num| {
        try doc.extractText(page_num, &writer.interface);
    }
}

CLIコマンド例

  • 全ページ抽出zpdf extract document.pdf
  • 特定ページ抽出zpdf extract -p 1-10 document.pdf
  • 出力ファイル指定zpdf extract -o out.txt document.pdf
  • 視覚的読順抽出(実験的)zpdf extract --reading-order doc.pdf
  • ドキュメント情報表示zpdf info document.pdf
  • ベンチマーク実行zpdf bench document.pdf

Pythonバインディング利用例

import zpdf
with zpdf.Document("file.pdf") as doc:
    print(doc.page_count)
    text = doc.extract_page(0)
    all_text = doc.extract_all()
    ordered_text = doc.extract_all(reading_order=True)
    info = doc.get_page_info(0)
    print(f"{info.width}x{info.height}")
  • 事前にzig build -Doptimize=ReleaseFastで共有ライブラリを構築

プロジェクト構成

  • src/
    • root.zig:ドキュメントAPI・コア型定義
    • capi.zig:C ABIエクスポート(FFI用)
    • parser.zig:PDFオブジェクトパーサ
    • xref.zig:XRefテーブル/ストリーム解析
    • pagetree.zig:ページツリー解決
    • decompress.zig:ストリームデコード
    • encoding.zig:フォントエンコーディング・CMap解析
    • interpreter.zig:コンテントストリーム解釈
    • simd.zig:SIMD文字列処理
    • main.zig:CLI実装
  • python/zpdf/:Pythonバインディング(cffi)
  • examples/:使用例

他ライブラリ比較・使い分け指針

| 機能 | zpdf | pdfium | MuPDF | |---------------|-------------|-------------|------------| | ストリーム順抽出 | Yes | Yes | Yes | | 読順抽出 | 実験的 | No | Yes | | ワード境界 | Yes | Yes | Yes | | フォントサポート | WinAnsi等 | Yes | Yes | | ToUnicode CMap | 部分対応* | Yes | Yes | | CIDフォント | 部分対応* | Yes | Yes | | 圧縮 | Flate, LZW等| Yes | Yes | | JBIG2/JPEG2000 | No | Yes | Yes | | 暗号化PDF | No | Yes | Yes | | レンダリング | No | Yes | Yes | | マルチスレッド | Yes | No** | No |

  • *ToUnicode/CID:CMapが埋め込まれていれば動作
  • **pdfiumはスレッド非対応、プロセス分割で並列化

選択基準

  • zpdf:バッチ処理、シンプルなテキスト抽出、Zig統合
  • pdfium:ブラウザ連携、完全なPDFサポート、安定性重視
  • MuPDF:読順抽出、複雑レイアウト、レンダリング必要時

ライセンス

  • WTFPL(完全自由ライセンス)

Hackerたちの意見

ZigでPDFテキスト抽出ライブラリを作ったんだけど、MuPDFよりもかなり速いよ。ピークスループットは約41Kページ/秒。主な選択肢は、メモリマップI/O、SIMD文字列検索、並列ページ抽出、ストリーミング出力。CIDフォントやインクリメンタルアップデート、一般的な圧縮フィルターも扱える。コードは約5,000行で、依存関係なし、コンパイルも簡単。 - メモリマップファイルI/O(read syscallなし) - ゼロコピー解析が可能なところはそうしてる - PDF構造を見つけるためのSIMD加速文字列検索 - Zigのスレッドプールを使ったページ間の並列抽出 - ストリーミング出力(抽出したテキストの中間的なメモリ確保なし) 対応しているもの: - XRefテーブルとストリーム(PDF 1.5+) - インクリメンタルPDFアップデート(/Prevチェーン) - FlateDecode、ASCII85、LZW、RunLengthのデコンプレッション - フォントエンコーディング: WinAnsi、MacRoman、ToUnicode CMap - CIDフォント(Type0、Identity-H/V、サロゲートペアを使ったUTF-16BE)
SIMDを有効にした場合とそうでない場合で、どんなパフォーマンスが出てる? https://github.com/Lulzx/zpdf/blob/main/src/main.zig のヘルプテキストには、複数スレッドを有効にするための未実装の「-j」オプションが書いてあるみたい。 "--parallel"オプションもあるけど、これは「bench」コマンドにしか実装されてないね。
最近、いくつかのプロジェクトをリリースしてるね、すごいよ!コーディングの一部にLLMを使ってるの?新しいプロジェクトに取り組むときのワークフローはどうなってる?
mmapの何が速いの?
tikaと比べて精度はどう?
参考までに言うと、mupdfは単純に速くない。PDFインデックスアプリをたくさん作ったけど、mupdfはテキスト抽出に関しては最も遅くて、正しいPDFを開く能力も最低だった。メモリもめちゃくちゃ食うし。より良い速度比較は、マルチプロセスのpdfium(pdfiumはfoxitからフォークされたから、マルチスレッドサポート前はスレッド化できない)、マルチスレッドのfoxit、またはsyncfusionみたいなもの(これはかなり速くてマルチスレッドをサポートしてる)になるだろうね。あるいはシングルスレッドのpdfiumとシングルスレッドの自分のコードの比較でもいい。これらが常に最速で最高の選択肢だった。これらのオプションで41kページ/秒以上を達成できるし、実際にやってるよ。もう一つ言及してないようだけど、単語を読み順に配置することを扱ってるかどうか(つまり、ページ上での見え方)なのか、ストリーム順序だけなのか(見え方との関係が変わる)も気になるね。もしストリーム順序だけなら、それは確かに速いけど、他のテキスト抽出エンジンがやってる読み順ほど役に立たないよ。コードを見る限り、読み順を処理するコードは存在するみたいだけど、ベンチマークやデフォルトで使われてるわけじゃないのかな?そうだとしたら、これは本当にリンゴとオレンジを比べてることになるね。
> 私が作った。君じゃない。Claudeが作ったんだ。これがこのコメントを書いたんだよ。しかも、提出する前にテストすらしなかったなんて、みんなに対して失礼だよ。
すごいね、Zigがこんなに速い理由は何?
遅くないよ。バイトコードに直接コンパイルされるから、解釈されるわけじゃないし、デフォルトで攻撃的で意見のある最適化が組み込まれてるから、デフォルト条件下ではコンパイルされたCよりも速いんだ。対照的に、Pythonは解釈されるし、ランタイムがもっさりしてるし、最適化も最小限で、遅くて冗長なパフォーマンスを引き起こす選択肢がいろいろある。パフォーマンスの代償は、安全チェックや冗長性、物事がどれだけ悪くなるか、みたいなことだね。いい妥協点はluajitで、同じような攻撃的な最適化を得られるけど、解釈型言語の便利さもあって、Cよりも良いパフォーマンスを持ちながら、zigやCと同じくらい爆発的な低レベルのものにもアクセスできる、しかも美しい言語なんだ。
開発ワークフローがスムーズになるから、https://news.ycombinator.com/item?id=46437289 にあるようなことをする時間とエネルギーができるんだよ。
すごくいいね!MuPDFを使ったときに、速度だけじゃなくて、いろんなマイナーなPDF機能のサポートレベルや、二段組のページや段落の識別などの精度も重要だから、機能比較が見たいな。ライセンスの問題で、非OSSツールでMuPDFを使うのは大きな障壁だから、これがMITライセンスなのは嬉しい。Pythonのバインディングもあったらいいな。
比較を追加した、さらに改善する予定。 https://github.com/Lulzx/zpdf?tab=readme-ov-file#comparison-... それと、Pythonのバインディングも追加したよ。
あとはPythonのバインディングがあれば、好きな言語で使えるんだけど。
Pythonのバインディングを追加したよ!
74910,74912c187768,187779 > myconv; > [例1: codecvt_utf8を使って、coutにUTF-8のマルチバイトシーケンスを出力したいけど、coutのロケールを変更したくない場合は、こんな感じで書ける: > > § D.27.2 > 1954 > > © ISO/IEC > N4950 > > wstring_convert> myconv; > std::string mbstring = myconv.to_bytes(L"Hello\n"); 確かに速いけど、出力がごちゃごちゃしてるし、Unicodeも扱えないから、mutoolとは対照的だね。(これが大きな速度向上の理由かもしれない。)
修正した。
PDFを解析する経験から言うと、速度は問題じゃなくて、常に品質の問題だったよ。
すごいパフォーマンス向上だね!MuPDFの5倍速はかなり大きい。特に大量のPDFを処理するアプリには重要だよね。Zigのガベージコレクションなしのメモリ安全性は、こういうパフォーマンスが求められる作業にぴったりだと思う。コメントで言われてたUnicodeの取り扱いについてのトレードオフが気になるな。ドキュメント分析パイプライン(技術文書や研究論文からテキストを抽出するような)では、しっかりしたUnicodeサポートがしばしば重要だからね。異なるPDFタイプのベンチマークも見てみたいな。数式がある学術論文、OCRレイヤー付きのスキャン文書、テーブルがある複雑なレイアウトとか。ドキュメントの構造によってパフォーマンスが大きく変わるからね。
どのメモリ安全性?
このバイブコーディングテスト、ひどいな: https://github.com/Lulzx/zpdf/blob/main/python/tests/test_zp...
これはPythonバインディングのための簡単なテストみたいなもんだね。Zigのファイルには、いろんなことに対するテストが含まれてるよ。
主要なPDFコーパスでテストしてみてほしいな。 [1] https://github.com/pdf-association/pdf-corpora
画像にフラット化されたテキストブロックにOCRをフックする可能性はある?コールバックとかで。これがPDFを扱うときの一番の不満なんだよね。
スペーシングの問題がまだうまくいってないみたい。zpdf extract texbook.pdf | grep -m1 Stanford DONALD E. KNUTHStanford UniversityIllustrations by