動機
jsPDFの公式が出しているttf変換ツールを使いたくない。
jsファイルにbase64エンコードされたフォントファイルが入ると、バンドルサイズが大きくなってページの読み込み速度が遅くなるためである。
コード
現在のプロジェクトはReactを使用しているため、カスタムフックを使って実現する。
実際のコードは以下をそのままコピペしてもらえれば良い。
import jsPDF from "jspdf"
import { useCallback, useMemo } from "react"
export interface Font {
name: string,
install(pdf: jsPDF): Promise<void>
}
async function loadFont(path: string): Promise<ArrayBuffer | undefined> {
try {
if (typeof window === "undefined") return undefined
const response = await fetch(path)
if (!response.ok) {
console.error(`Failed to fetch font: ${response.statusText}`)
return undefined
}
return await response.arrayBuffer()
} catch (e) {
console.error(e)
return undefined
}
}
export function useFont(name: string, path: string, style?: string): Font {
const promise = useMemo(() => loadFont(path), [ path ])
const install = useCallback(async (pdf: jsPDF) => {
const data = await promise
if (!data) return
const fileName = name + ".ttf"
pdf.addFileToVFS(fileName, Buffer.from(data).toString("base64"))
pdf.addFont(fileName, name, style ?? "normal")
}, [ name, promise, style ])
return useMemo(() => ({
name,
install,
}), [ name, install ])
}
使用方法
使用方法としては、useFont(ファイル名, ファイルのパス)
を呼び出してフォントのダウンロードを開始したあと、await Font.install(jsPDF)
を実行してPDFにフォントの導入を行う。
Font.installが実行された後は、pdf.setFont(font.name)
を実行するだけでテキストにフォントが設定される。
なお注意点としては、Buffer.fromはNodeJSだけでブラウザ環境には存在しないらしいので、適当なpolyfillを入れるかNext.jsといったフレームワークを使うこと。
export function usePdf() {
const poppins = useFont("Poppins-Regular", "/fonts/Poppins-Regular.ttf")
const download = useCallback(async () => {
const pdf = new jsPDF()
// add fonts
await poppins.install(pdf),
pdf.setFont(poppins.name)
// contents
// ...
pdf.save("data.pdf")
}, [poppins, product.name])
return useMemo(() => ({
download,
}), [ download ])
}
解説
PDFをダウンロードする上で、以下のような挙動で動作する。
- ページを読み込んだ段階で、非同期でフォントファイルをダウンロードする。
- もしPDFのダウンロード時に依然としてフォントがダウンロード出来ていない場合は、フォントのダウンロードが終わるまでPDFのダウンロードを待機する。
- フォントのダウンロードでエラーが発生した場合は、フォントを読み込まずにPDFをダウンロードする。
1と2に関しては、Promiseは何回でもawaitでき、かつPromiseが既にresolveされている場合は即座にawaitが返る特性を利用してこのカスタムフックを実現している。また3に関しては、単純にPromiseでエラーが発生した場合はフォントの導入をキャンセルするような仕組みである。