はじめに
今回はフロントエンド(TypeScript)でのPDF生成について紹介します。
画像などを変換するのではなく、アプリケーションの持っている情報を特定の座標に描画したPDFファイルです。
また、要件として罫線のある表を描画する想定です。
描画するコードそのものでは長くなってしまうため、ライブラリ選定理由・罫線のある表を描画するために使った関数・実装時のコード整理などについてのみとします。
利用したライブラリはPDF-LIBです。
※この記事の内容はテックブログでも公開しています。
環境
弊社ではNuxt.jsの3系を利用しているため、Vueも3系、TypeScriptとなります。
"nuxt": "3.11.2"
"pdf-lib": "1.17.1"
PDF生成
ライブラリ選定
PDF生成を出来るライブラリはいくつかありますが、下記の理由からPDF-LIB
を選択しました。
- フォントの読み込み方法が簡単
- PDFライブラリについて調べていると日本語フォントの導入がひとつの詰まりやすいポイントであることが分かりました
- 読み込み方を見比べていると、
PDF-LIB
はassets
フォルダなどに配置したフォントファイルをコンポーネントで読み込めば良さそうだったため理解しやすかったです
- 文字揃えなどの機能がなくライブラリ利用者側が計算をする方針
- 今回は罫線のある表を描画するという前提がありましたが、ライブラリがそれらの要件を満たせるかをそれぞれ調査するよりも、計算は自前でやってしまう前提の方が今後の要件追加などを考えても良いと考えました
描画
要件である表の描画を目指すために必要な機能を紹介します。
- 文字列
https://pdf-lib.js.org/docs/api/classes/pdfpage#drawtext
drawText
関数で文字列を描画出来ます。
- 罫線(四角形)
https://pdf-lib.js.org/docs/api/classes/pdfpage#drawrectangle
drawRectangle
関数で四角形を描画できます。
- 文字列の幅計算
https://pdf-lib.js.org/docs/api/classes/pdffont#widthoftextatsize
widthOfTextAtSize
関数で文字列の描画に必要な横幅を取得することが出来ます。
- 改行・文字揃え
今回は出力するPDFファイルの横幅には指定があったため、各四角形の横幅は固定値でした。
各四角形に入る文字列が四角形に入りきらない場合もあるため、改行をする必要があります。
widthOfTextAtSize
関数で文字列の横幅を指定して必要な箇所で改行を挟みます。
同様に文字揃えについても文字列の横幅を計算することで、文字列の描画開始位置(X)を計算することが出来ます。
- フォント指定
今回はNuxt.jsなので、assets
フォルダにフォントファイル(.otf
)を配置しました。
PDFを生成するコート上で、下記のようにフォントファイルを読み込み、PDFFont
型を取得します。
import FontRegular from './assets/fonts/NotoSerifJP-Regular.otf'
const fontBytes = await fetch(FontRegular).then((res) => res.arrayBuffer())
const font = await pdfDoc.embedFont(fontBytes)
このPDFFont
をdrawText
関数などに渡してあげることで、日本語フォントでの文字列描画が出来ます。
コード整理
ある程度自由にPDFを作れるようにしつつ、罫線を用いた表を描画出来るようにしました。
ただの文字列・改ページで分断されないようにブロック化した文字列・テーブルの行(計算で囲まれた表)の3種類の型を定義しました。
利用者はこれらの型のユニオン型であるPDFElement
型の配列をPDF生成クラスに渡し、PDF生成クラスでは配列の要素ごとに描画処理をしていきます。
描画時はX, Y軸の位置を指定する必要があるため、配列のループ処理の中で1行目はY軸0、2行目は1行目の高さをY軸に加算する…といった具合に描画を進めていきます。
参考までに、下記は定義した型のイメージです。
// テキスト用のPDF要素
type PDFTextElement = {
type: 'text'
text: string
size: number
align: 'center' | 'left' | 'right'
}
// テキストブロック用のPDF要素
// ブロックで定義することで改ページ時にブロックが分断されないようにする
type PDFTextBlockElement = {
type: 'textBlock'
elements: PDFTextElement[]
}
// テーブル行用のPDF要素
// 枠で囲まれて描画される
type PDFTableRowElement = {
type: 'tableRow'
rows: {
text: string
size: number
width: number
}[]
}
type PDFElement = PDFTextElement | PDFTextBlockElement | PDFTableRowElement
保存
https://pdf-lib.js.org/docs/api/classes/pdfdocument#save
PDFの保存にはsave
関数を使います。
save
関数の戻り値はPromise<UInt8Array>
型のため、あとは自由に保存処理を実行するだけです。
おわりに
PDFを生成するライブラリはいくつかありますが、今回はPDF-LIBを用いて表のあるPDFを生成しました。
計算をライブラリ利用者に任せる代わりに、関数ごとの動作やオプションも分かりやすく、なおかつ必要な関数は揃えてくれるため詰まることなく実現することが出来ました。
ここまで見ていただき、ありがとうございました!