HTMLをPDFに変換するAPIサービス｜PDF生成の課題を解決


# TLDR

- HTML から PDF を生成する API サービスを開発しました。
- PDF 生成の際の環境構築不要、日本語フォント完備、アセットファイルのホスティング可能、開発中のプレビュー機能あり。PDF（HTML）の中身の開発に集中できます。
- 1 回の生成あたり約 0.3 円。固定費なしの完全従量課金。

https://pdfg.net/ja

# 背景

アプリケーションの開発をしていると、度々「PDF を生成する」という要件に遭遇します。例えば以下のようなケースです。

- **帳票出力**: 請求書、領収書、納品書、契約書、証明書など、公式な書類を PDF 形式で出力する必要があるケース
- **レポート作成**: 売上レポートやアクセスログ解析など、グラフや表を含むデータを綺麗にレイアウトして共有したいケース
- **チケット発行**: イベントチケットや搭乗券など、スマートフォンでの表示・保存や印刷が必要なケース
- **マニュアル配布**: 製品マニュアルや操作手順書など、デバイスに依存せず同じレイアウトでの表示が重要なケース

特に作成時点のもので後から変更ができず（しにくく）、静的なファイルとして残したいような場合は必須要件になり、自分自身何回か PDF 生成機能を含む開発を行った経験があります。  
しかし PDF の生成はなかなかやっかいで、いくつかの方法があるのですがいずれもそれなりに工数がかかり悩まされることが多かったです。

そのため「今後 PDF 生成の要件が来てもすぐに実現できるようにしよう」と考え、サービス化に至りました。
なお自分は Node.js、React、TypeScript といった技術スタックで開発をすることが多いので、その目線での内容が多めになります。

# PDF 生成の大変さ

PDF を生成する方法は現実的に以下の 2 つのアプローチがありますが、どちらの方法も課題があるなと考えています。

## 1. ライブラリで直接 PDF を生成する方法

例えば JavaScript には[PDFKit](https://pdfkit.org/)というライブラリがあり、これを使うと Node.js 環境で PDF を生成することができます。  
自分はこの[PDFKit](https://pdfkit.org/)を内包した[React-PDF](https://react-pdf.org/)というライブラリを使ったことがありますが、以下のような難点がありました。

- **表現力の制限**
  - HTML/CSS と比べて利用可能なタグが大幅に制限される
  - 独自タグの使用が必要で、冗長なコードになりがち
  - 複雑なレイアウトやデザインの実現が困難
- **日本語対応の手間**
  - フォントファイルの追加が必要

シンプルな PDF であれば使用可能ですが、レポートや帳票のような複雑なデザインが必要な場合は、表現力や保守性の面で制約となりそうです。

## 2. HTML から PDF を生成する方法

なんらかのレンダリングエンジンを使って HTML を解釈し、PDF を生成する方法です。
自分は ヘッドレスブラウザ（Puppeteer）を使用したことがあり、HTML/CSS でコーディングしてそのままの見た目で PDF を生成できる反面、環境構築がネックになりがちでした。

- **リソース管理**
  - ブラウザを動かすためのマシンリソースの考慮が必要
- **環境構築の複雑さ**
  - 自分は実際 AWS Lambda を使った以下の 2 つのアプローチをとったことがあります。
  - AWS Lambda コンテナイメージ：
    - Amazon Linux ベースのランタイムイメージに Chromium の実行に必要な追加ライブラリのインストールが必要
    - 日本語フォントパッケージの個別インストールが必要
  - 通常の AWS Lambda 関数（zip アーカイブ）：
    - AWS Lambda 向けにビルドされた Chromium バイナリが必要
    - Lambda レイヤーを使用した日本語フォントの追加が必要

いずれの方法でも、インストールするフォントによっては異体字や旧字体が含まれていないケースがあったり、環境構築に手戻りが発生 .. といったことがありました。  
また、調べると色々な方法が見つかるのでそれらのトライアンドエラーを繰り返したり、環境構築部分も当然コード化（IaC）したかったりと、結果的に環境構築で数日経過することもザラでした。

上記は Node.js 環境での例ですが他の言語だと例えば Python には WeasyPrint というライブラリがあり、GTK ベースのレンダリングエンジンで HTML/CSS を PDF に変換できるようです。  
WeasyPrint も調べるとやはり環境構築に関する情報が色々とヒットするので、同様の大変さがあるのかなという印象を持っています。

## PDF に起因する煩雑さ

開発する機能が PDF をどのように扱うかにもよりますが、以下のような点も地味に辛いと考えています。

- 生成した PDF ファイルをいちいち開いて確認しないといけない
- Web サイトであればブラウザの DevTools を用いてレイアウトや見た目がなぜそうなっているかを判断できるが、PDF ではそのようなことができない

# 既存サービスの物足りなさ

HTML を PDF に変換する API サービスは既存のものがいくつかあるのですが、自分は以下のような物足りなさを感じていました。

### コスト

- 月額定額課金のものが多い
- 例えば 20 ドル弱で 4,000 回の PDF 生成 といった金額形態があるが、個人的に高いと感じる

### 日本語対応

- 海外製のものしかなく、日本語フォントが不十分
- 日本語フォントがあってもスタイルがいまいち

### アセット管理

- 画像などの静的ファイルを自前でホスティングする必要がある
- 例えばロゴを請求書に挿入したいような場合、画像は別途インターネットアクセスできるところに置いてそれを参照 .. といったことになる
- スタイルは一式揃った CSS ファイルを参照したかったりするが、CDN 経由で配信されているものに依存したくない

### ドキュメントの不足

- HTML を PDF に変換するとなるとどうやって HTML を組むかが肝になるが、あまりその具体的な方法について触れているドキュメントがない
- 例えば PDF のページサイズや改ページの制御方法などを知りたかったりする

### 機能が多くて迷う

- 自分は HTML を送ったら PDF のバイナリファイルが返ってくるだけで満足なのだが、色々な機能があって迷ってしまうことがある
- 例えば URL やオフィスファイルなど HTML 以外から PDF を生成する機能や、生成した PDF をストレージに格納してくれる機能など、自分には不要だった

# 作ったサービス: pdfg

以上から、これだったら自分でも使いたい！と思えるものを作りました。

https://pdfg.net/ja

以下が特徴です。

### ヘッドレスブラウザ

内部的には Puppeteer を利用しています。HTML/CSS で記述したものがそのまま PDF として出力されるため、Web 開発の延長で作業できます。

### コスト

PDF 生成 1 回あたり 0.3 円としました。

### 日本語対応

Noto Sans JP と Noto Serif JP のすべてのスタイル（Thin 100 から Black 900 まで）をサポートしており、ゴシック体と明朝体の両方を任意の font-weight で使用できます。

### アセット管理

画像や CSS ファイルをダッシュボードから簡単にアップロードでき、HTML から直接参照できます。外部 CDN などへの依存をなくし、安定した PDF 生成を実現します。

### ドキュメントの充実

各種テンプレートエンジン（React、Vue、Blade、ERB、Thymeleaf、Jinja2 など）を使った HTML の生成方法、フォント、画像、スタイル（CSS）の扱い方まで、詳細なドキュメントを日本語で提供しています。

#### 各種利用方法

[はじめに](https://pdfg.net/ja/docs)  
[API 仕様](https://pdfg.net/ja/docs/api)  
[フォントと言語](https://pdfg.net/ja/docs/font)  
[画像](https://pdfg.net/ja/docs/image)  
[スタイル](https://pdfg.net/ja/docs/style)  
[ページ](https://pdfg.net/ja/docs/page)  
[開発向けプレビューモード](https://pdfg.net/ja/docs/preview)

#### 各フレームワーク毎の HTML 生成方法のサンプル

[React(JSX/TSX)](https://pdfg.net/ja/docs/react)  
[Vue.js](https://pdfg.net/ja/docs/vue)  
[Jinja2 (Python)](https://pdfg.net/ja/docs/jinja2)  
[ERB (Ruby)](https://pdfg.net/ja/docs/erb)  
[Blade (PHP)](https://pdfg.net/ja/docs/blade)  
[Thymeleaf (Java)](https://pdfg.net/ja/docs/thymeleaf)  
[Go](https://pdfg.net/ja/docs/go)

### シンプルで迷わせない

API エンドポイントは 1 つのみで、PDF のサイズや向き、マージンなどの基本的なオプションを提供しつつ、複雑な設定を避けシンプルに使えるようにしています。

### プレビュー機能

開発時のためのプレビューモードという機能をつけています。生成された PDF をリアルタイムでプレビューできるもので、以下が実際の機能の操作動画です。

![preview.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3760386/a65637ea-d871-60a9-f231-42825ccf2380.gif)

cURL で HTML を API に送っていて、説明のために body の背景色を変えています。このようにプレビュー画面は開いているだけで API コールをトリガーに WebSocket 経由で変更がリアルタイムで反映されるようになっています。  
個人的にこの機能はかなり嬉しく、QOL が爆上がりしました。

動画には映していないのですが、複数ページにまたがる時にデフォルトで表示するページ番号やズームの倍率なども設定で変更できるようにしていて、自分自身いつも面倒に感じていた、生成された PDF ファイルの確認作業をなるべくスムーズになるようにしています。

なお、プレビューモードでの API コールは利用料の発生対象外となります。

# まとめ

早速自分でも開発案件で利用していて、帳票を PDF で生成するという要件を従来比で爆速にかつストレスなく進められています。

もし同様の要件で辛さを感じている方がいらっしゃれば、是非お試しください。

[pdfg - HTML を PDF に変換する API サービス](https://pdfg.net/ja)