Node.js向けPDFライブラリをリリースしました
約半年前にpdf_oxide (RustエンジンのPDFライブラリ) をMIT / Apache-2.0で公開しました。最初はRustとPythonだけでしたが、ここ数ヶ月はほぼ毎日のようにフィードバック (バグ報告、パースできないPDF、日本語CIDフォントのエッジケース、ICCカラー、マルチカラムレイアウト検出など) をいただいて、地道に直してきました。
v0.3.5 → v0.3.37の間でかなり安定してきたので、コアが落ち着いたこの2ヶ月でGo、C# / .NET、Node.js / TypeScriptの各言語バインディングを書きました。今回のNode.js / TypeScriptバインディングについて、実際に使ってみたご意見を伺いたく投稿しています。
平均0.8ms、MIT / Apache-2.0ライセンス、node-gyp不要でインストールできます。
インストールと使い方
npm install pdf-oxide
const { PdfDocument } = require("pdf-oxide");
const doc = new PdfDocument("paper.pdf");
const text = doc.extractText(0);
const markdown = doc.toMarkdown(0);
doc.close();
TypeScript:
import { PdfDocument } from "pdf-oxide";
const doc = new PdfDocument("paper.pdf");
const text: string = doc.extractText(0);
型定義 (.d.ts) はパッケージに同梱しています。Node.js 18 / 20 / 22対応、Linux / macOS / Windowsのx64 + ARM64でプリビルドバイナリを配布。インストール時にnode-gypやRustツールチェーンは一切必要ありません。
なぜ作ったか
元々はRustとPythonで使っていたものを、Node.jsからも呼びたくて作りました。Node.jsのPDFライブラリは選択肢が限られていて、以下の不満がありました。
-
pdf-parse— メンテナンスが実質止まっている -
pdf.js— 本来はブラウザ用なので依存が重い (~10MB) -
pdf-lib— PDF作成はできるがテキスト抽出はできない
MITライセンスで、AWS Lambdaのコールドスタートで気にならない速度で、.nodeファイルをプリビルドで配布していて、日本語PDFの文字化けが出ない、という条件を全部満たすものが欲しかった、という経緯です。
Linux glibcの話
Node.jsバインディングで一番苦労したのは、プリビルドの.nodeファイルのglibc互換性でした。
GitHub Actionsのubuntu-latestでビルドしたバイナリは、Alpine Kubernetes上のコンテナやAWS Lambda provided.al2023 ランタイムに持っていくと、GLIBC_2.34 not found で起動すら失敗します。実行時にldd --versionが合わないケースで落ちるので、CIは緑なのに本番で落ちるというパターンになりがちです。
対応はシンプルで、centos7時代のglibcをベースラインにしてビルドし直すだけなのですが、CI環境の再構築に1週間ほどかかりました。
v0.3.37時点では、LambdaとAlpine K8sの両方で動作確認済みです。
日本語PDFの対応
Node.jsバインディングもRust / Pythonと同じコアを使っているので、日本語CIDフォント対応もそのまま引き継いでいます。
- Adobe-Japan1 (15,000+エントリ) のCID-to-Unicodeマッピング内蔵
- 4言語合計で80,000+マッピング (Japan1 / GB1 / CNS1 / Korea1)
- Shift-JIS / RKSJデコーディング対応
- CFFフォントのエンコーディングパーサー実装済み
日本語PDFでの文字化け撲滅についてはQiitaで前に別記事を書いたので、詳しくはそちら (【PDF】日本語PDFの文字化けに怒ってRustでPDFパーサーを書いた) を参照してください。
ベンチマーク
3,830の実PDFファイル (veraPDF 2,907、Mozilla pdf.js 897、DARPA SafeDocs 26) でテスト。全バインディングで同じRustコアを使っているので数字は共通です。
| ライブラリ | 平均 | p99 | パス率 | ライセンス |
|---|---|---|---|---|
| pdf_oxide | 0.8ms | 9ms | 100% | MIT / Apache-2.0 |
| PyMuPDF | 4.6ms | 28ms | 99.3% | AGPL-3.0 |
| pypdfium2 | 4.1ms | 42ms | 99.2% | Apache-2.0 |
| pdftext | 7.3ms | 82ms | 99.0% | GPL-3.0 |
| pdfminer | 16.8ms | 124ms | 98.8% | MIT |
| pdfplumber | 23.2ms | 189ms | 98.8% | MIT |
Node.jsバインディングのオーバーヘッドは、Rust直接呼び出しに対して実PDFで~25%以内に収まっています。
その他のバインディング
同じ期間にGoとC# / .NETも同時にリリースしました。
# Go
go get github.com/yfedoseev/pdf_oxide/go
go run github.com/yfedoseev/pdf_oxide/go/cmd/install@latest
# C# / .NET
dotnet add package PdfOxide
全バインディング共通で、Rustコア側のメンテナンス (v0.3.34 — v0.3.37) も続けています。ICCカラー管理、マルチカラムレイアウト検出、XY-cut列検出の精度改善など。
残っている課題
- AES-256暗号化PDFでエッジケースがいくつか残っています
- テーブル抽出は
pdfplumberほど細かくはないです、改善中です
リンク
- GitHub: github.com/yfedoseev/pdf_oxide
- Docs (日本語): oxide.fyi/ja
- PDF処理ドキュメント: pdf.oxide.fyi/ja
- npm: npmjs.com/package/pdf-oxide
Node.jsからのAPIは自然でしょうか、型定義に違和感はないでしょうか、あるいは「このユースケースが足りない」など、ご意見をコメントかGitHub issueで教えていただけると助かります。日本語PDFで文字化けするケースもそのまま送っていただければ直します。