はじめに
請求書や証明書、契約書といったPDF帳票を自動生成したい。そんなとき、多くの人がまず思い浮かべるのは「サーバー側でPDF生成ライブラリを動かす」という構成ではないでしょうか。ですが、外部APIに機密情報を含んだデータを送るのは避けたい、サーバーレス環境で重い依存関係を抱えたくない、といった悩みもつきものです。
PDF-Libは、そうした悩みをブラウザ・Node.js・Deno・React Nativeなど、あらゆるJavaScript環境の中だけで解決してくれるライブラリです。外部サービスに依存せず、PDFの作成から既存ドキュメントの編集まで完結できます。
実際にブラウザだけでPDFが生成できるところを、先に手を動かして確かめてみましょう。
PDF-Libとは
PDF-Libは「Create and modify PDF documents in any JavaScript environment」を掲げるオープンソースのPDF操作ライブラリです。新規PDFの作成はもちろん、既存PDFの読み込みと編集、フォームの作成・入力・フラット化まで、幅広い操作をJavaScript/TypeScriptだけで実現できます。
主な特徴
- どこでも動く - ブラウザ、Node.js、Deno、React Nativeなど、JavaScript環境を問わず同じAPIで動作します
- 既存PDFの編集が可能 - ゼロから作るだけでなく、既にあるPDFを読み込んでテキストや画像を追記・修正できます
- フォーム操作に対応 - テキストフィールドやチェックボックス、ラジオボタンなどのAcroFormを作成・入力・フラット化できます
- 依存が軽い - サーバーサイドのPDFレンダリングエンジンを別途用意する必要がなく、単体のパッケージとして完結します
インストール
npmでインストールします。
npm install pdf-lib
yarnやpnpmを使っている場合は、以下のコマンドでも導入できます。
yarn add pdf-lib
# または
pnpm add pdf-lib
CDN経由で読み込む場合は、UMDビルドをそのまま利用できます。
<script src="https://unpkg.com/pdf-lib/dist/pdf-lib.min.js"></script>
PDF-Libのサンプルを動かす
下のテキスト入力を書き換えて「PDFを生成」を押すと、その内容を反映したPDFがブラウザ内だけで作られ、その場でプレビューされます。サーバーにもファイルにも触れず、PDFDocument.create()とpage.drawText()だけでPDFが1枚できあがる様子を確かめてみてください。
まずは中心となる書き方だけを抜き出すと、次のようになります。
import { PDFDocument, StandardFonts, rgb } from 'pdf-lib'
const pdfDoc = await PDFDocument.create()
const page = pdfDoc.addPage([595.28, 841.89]) // A4サイズ
const font = await pdfDoc.embedFont(StandardFonts.Helvetica)
page.drawText('Hello, PDF-Lib!', {
x: 50,
y: 780,
size: 24,
font,
color: rgb(0.1, 0.1, 0.4),
})
const pdfBytes = await pdfDoc.save() // Uint8Array
これを、入力フォームとプレビュー付きにした実行可能なサンプルが以下です。テキストを空にしてみたり、sizeやx・yの値をコードエディタ側で書き換えたりすると、生成されるPDFがどう変わるかをその場で確認できます。
PDFDocument.create()で作ったドキュメントにaddPage()でA4ページを追加し、embedFont()で埋め込んだフォントを使ってdrawText()で文字を描画しています。最後にsave()で得られるpdfBytesはUint8Arrayなので、Blobに変換してURL.createObjectURL()でBlob URLを作れば、そのままiframeやリンクとしてブラウザ上に表示できます。テキストを空文字にすると何も描かれない1ページのPDFになり、sizeを大きくすると文字が拡大され、x・yの値を変えると描画位置が動く点も試してみてください。
基本的な使い方
まずは、新規PDFを作成してテキストを描画するシンプルな例です。
import { PDFDocument, StandardFonts, rgb } from 'pdf-lib';
async function createPdf() {
// 新しいPDFドキュメントを作成
const pdfDoc = await PDFDocument.create();
// A4サイズのページを追加
const page = pdfDoc.addPage([595.28, 841.89]);
// 標準フォントを埋め込み
const font = await pdfDoc.embedFont(StandardFonts.Helvetica);
// テキストを描画
page.drawText('Hello, PDF-Lib!', {
x: 50,
y: 780,
size: 24,
font,
color: rgb(0.1, 0.1, 0.4),
});
// バイト列として保存
const pdfBytes = await pdfDoc.save();
return pdfBytes;
}
pdfBytesはUint8Arrayなので、Node.jsではファイルに書き出し、ブラウザではBlobに変換してダウンロードリンクを作成できます。
既存のPDFを読み込んで編集する場合も、手順はほとんど変わりません。
import { PDFDocument, rgb } from 'pdf-lib';
import fs from 'fs/promises';
async function editExistingPdf(inputPath: string) {
const existingBytes = await fs.readFile(inputPath);
const pdfDoc = await PDFDocument.load(existingBytes);
// 1ページ目を取得
const [firstPage] = pdfDoc.getPages();
const { height } = firstPage.getSize();
// 「確認済み」の印を追記
firstPage.drawText('確認済み', {
x: 50,
y: height - 50,
size: 18,
color: rgb(0.8, 0, 0),
});
return pdfDoc.save();
}
実践的なユースケース
実務でよくあるのが、テンプレートPDFにフォーム入力値を差し込んで帳票を自動生成するケースです。PDF-LibはAcroForm(PDFに埋め込まれたフォーム機能)の操作にも対応しているため、既存のフォーム付きPDFテンプレートに値を流し込んで、そのまま提出用PDFを作ることができます。
import { PDFDocument } from 'pdf-lib';
import fs from 'fs/promises';
async function fillInvoiceTemplate(templatePath: string, data: {
customerName: string;
invoiceNumber: string;
amount: string;
}) {
const templateBytes = await fs.readFile(templatePath);
const pdfDoc = await PDFDocument.load(templateBytes);
const form = pdfDoc.getForm();
// テンプレート側で定義済みのフィールド名に値をセット
form.getTextField('customerName').setText(data.customerName);
form.getTextField('invoiceNumber').setText(data.invoiceNumber);
form.getTextField('amount').setText(data.amount);
// 入力後は編集不可のフラット状態に変換
form.flatten();
const outputBytes = await pdfDoc.save();
await fs.writeFile('invoice-filled.pdf', outputBytes);
}
このように、あらかじめデザイナーが作ったフォーム付きテンプレートを用意しておけば、アプリケーション側は値を差し込んでフラット化するだけで完成します。帳票のレイアウトをコードで一から組む必要がないため、デザインと実装の分業がしやすくなるのもメリットです。
また、複数のPDFからページを抜き出して1つに結合する、既存の複数ページPDFから一部だけを取り出す、といった操作にも対応しています。契約書と添付資料をまとめて1つのPDFにする、といった業務でも活躍します。
PDF-Libでブラウザから読み込んだPDFに追記する
ここまでは新規作成のパターンでしたが、既にあるPDFに追記したい場面も多いはずです。PDF-LibはPDFDocument.load()にPDFのバイト列を渡すだけで既存ドキュメントを読み込めるため、Node.jsのfsを使わなくても、ブラウザの<input type="file">で選んだPDFをそのまま編集できます。
要点だけを抜き出すと、次のようになります。
import { PDFDocument, rgb } from 'pdf-lib'
async function stampPdf(file: File, stampText: string) {
const existingBytes = await file.arrayBuffer()
const pdfDoc = await PDFDocument.load(existingBytes)
const [firstPage] = pdfDoc.getPages()
const { height } = firstPage.getSize()
firstPage.drawText(stampText, {
x: 50,
y: height - 50,
size: 18,
color: rgb(0.8, 0, 0),
})
return pdfDoc.save()
}
実際に手元のPDFファイルを選んでスタンプ文字を追記できるサンプルが以下です。手頃なPDFがなければ、先ほどの「PDF-Libのサンプルを動かす」で生成したPDFをダウンロードして読み込ませてみてください。
PDFDocument.load()で読み込んだドキュメントからgetPages()で1ページ目を取得し、getSize()で得たheightを基準に座標を計算してからdrawText()で追記しています。スタンプ文字の位置を変えたいときはx・yの値を、色を変えたいときはrgb()の引数を書き換えてみてください。y: height - 50のように高さ基準で計算しているため、ページサイズが違うPDFを読み込ませても文字が上部からずれない点もポイントです。
まとめ
PDF-Libは、サーバーサイドの重量級PDFエンジンに頼らずとも、JavaScript環境だけでPDFの生成・編集・フォーム操作までを完結させられるライブラリです。ブラウザ完結のツールを作りたいときも、Node.jsのバッチ処理に組み込みたいときも、同じAPIで扱えるのは大きな強みです。
まずは今回紹介した新規作成とテキスト描画のサンプルを試し、慣れてきたら既存テンプレートへのフォーム入力やページ結合など、より実践的な機能にも触れてみてください。