MAGAZINE
ルーターマガジン
PDF生成ライブラリのベンチマーク比較
1. はじめに
システム開発において、帳票出力やレポート作成などでPDF生成機能が求められることは少なくありません。私たちもこれまで、PDF生成を伴う機能を複数開発してきました。弊社では主にRubyを用いてシステムを構築することが多いため、これまではRubyのWickedPDFを利用したPDF生成処理を実装してきました。
既存の構成では、数ページ程度のPDFであれば問題なく機能していましたが、数十ページから数百ページに及ぶ大量の画像を含むPDFを生成する際に、処理時間が数秒〜十数秒に及ぶようになりました。このようにデータ量の増加に伴って生成速度が著しく低下する課題に直面していました。
そのような中、インフラコスト削減の一環として、PDF生成処理をサーバーレスアーキテクチャへ移行することになりました。これにより、技術スタックの制約がなくなり、言語の壁を超えることが可能になったため、「言語を問わず、現状で実用的な最速の選択肢は何か」を客観的なデータに基づいて再選定することにしました。
結論としては、今回の検証を経て、Go言語のgpdfを採用しました。本記事では、その採用経緯をベンチマーク結果を交えてお話しします。
2. 比較対象の9ライブラリ
今回の検証では、代表的な言語からPDF生成機能を持つ9つのライブラリをピックアップしました。PDFの生成アプローチは大きく分けて、座標を指定して直接描画する「direct-drawing系」と、HTMLやCSSを解釈してレンダリングする「HTML/CSSレイアウト系」に大別されます。
- Go
- gofpdf: direct-drawing系。古くからある有名なライブラリ。
- gopdf: direct-drawing系。日本語対応が比較的容易。
- gpdf: direct-drawing系。12列グリッド+宣言的なテンプレートAPI。
- Ruby
- WickedPDF: HTML/CSSレイアウト系。wkhtmltopdfのラッパー。
- Typst: 厳密には独自の組版システムですが、Rubyから呼び出して検証。
- Python
- PyMuPDF: direct-drawing/PDF操作系。
- WeasyPrint: HTML/CSSレイアウト系。
- Node.js
- Puppeteer: HTML/CSSレイアウト系。ヘッドレスブラウザでPDF化。
- .NET
- QuestPDF: direct-drawingとレイアウトエンジンの中間的なアプローチ。
3. ベンチマーク条件
純粋な「PDF生成速度」を比較するため、以下の条件でベンチマークを実施しました。 ダミー画像100枚を使用し、1ページにつき1画像を配置した計100ページのPDFを生成しました。 すべて同一マシンのDockerコンテナ上で実行し、各ライブラリ3回実行したPDF生成処理時間の中央値を採用しています。
4. 結果
全体的な傾向として、直接座標を計算して描画する「direct-drawing系(gofpdf, gpdf, gopdf)」が、ブラウザエンジンなどを介する「HTML/CSSレイアウト系(puppeteer, weasyprint, wickedpdf)」を速度面で明確に上回る結果となりました。WickedPDFと比較すると、上位のGo言語ライブラリ群は約2〜6倍ほど高速です。なお、direct-drawing系であるPyMuPDFはPDF生成速度が速いと思っていましたが、想定よりも時間を要する結果となりました。
各ライブラリの実行結果は以下の通りです。(処理時間の短い順)
| 順位 | エンジン | 言語 | 処理時間 (ms) |
|---|---|---|---|
| 1 | gofpdf | Go | 171.01 |
| 2 | gpdf | Go | 367.07 |
| 3 | gopdf | Go | 399.29 |
| 4 | questpdf | .NET | 636.84 |
| 5 | wickedpdf | Ruby | 1000.33 |
| 6 | typst | Ruby | 1836.64 |
| 7 | pymupdf | Python | 1888.19 |
| 8 | puppeteer | Node.js | 3866.35 |
| 9 | weasyprint | Python | 10454.51 |
5. フォントに関する制約
Go言語の gofpdf が171msと最速を記録していますが、運用上の課題がありました。
gofpdfには文字コードの制限があり、BMP(Basic Multilingual Plane:U+0000〜U+FFFF)の範囲の文字しか対応しておらず、絵文字などU+FFFFを超える文字が入力されるとすべて「?」に変換されてしまいます。この制約はライブラリ側の根幹に関わる部分であり、仮に外部からフォントを追加したとしても解消されないため、PDF上の表現力に明確な限界が生じます。後継とされるgo-pdf/fpdfも同じ制約を引き継いだままメンテナンスが停止しており、この課題が解決される見込みがありません。
一方、次点のgpdf(367ms)は文字コードの制限はありません。ただし、対応するグリフ(形)を持たない文字はPDFの標準仕様通り.notdefグリフ(□)として描画されてしまう問題を抱えていました。
この課題には、日本語フォント(IPAゴシック)と絵文字専用フォント(Symbola)を用意し、文字ごとにどちらかに実グリフがあれば描画し、両方に無い文字は描画自体をスキップする、という対応を実装することで解決しています。Symbolaは色付きではなく黒一色の輪郭フォントのため、対応する絵文字は色を失った状態(モノクロの輪郭グリフ)で表示されます。またSymbolaの収録はUnicode 9.0(2016年)までのため、それより新しい絵文字は表示されず消えます。
これらを踏まえ、文字コードの制約で絵文字が問答無用で「?」になり、開発も止まっているgofpdfよりも、日本語表示が正常に機能し、一定範囲の絵文字もモノクロで表示できるgpdfを採用しました。
6. おわりに
以上の検証から、サーバーレスアーキテクチャ上のPDF生成エンジンとしてgpdfを採用しました。
扱うテキストが決められた文字コード(BMP範囲内)に収まる前提であればgofpdfが最速の選択肢となりますが、絵文字対応を含めたフォントの自由度やメンテナンス性を重視する場合はgpdfの使用をおすすめします。
CONTACT
お問い合わせ・ご依頼はこちらから