jsPDF
***** 確認環境
jsPDF: v2.1.1
***** 概要
このライブラリーでは、次のように PDF を一から組み立てることもできますし、
code:js
import { jsPDF } from "jspdf";
const doc = new jsPDF();
// 10, 10 は x, y 座標
doc.text("Hello world!", 10, 10);
doc.save();
次のように DOM ツリーから生成したり、HTML 文字列から PDF を生成できます。DOM ツリーから生成する場合、スクリーンショット画像を PDF に張り付けるわけではなく、ツリーを走査して対応するテキストや線を組み立てて生成します (Canvas API 互換の PDF 生成クラスを html2canvas に渡しています)。そのため、PDF ビューワー上でテキストを選択したりも可能です。
code:js
import { jsPDF } from 'jspdf';
const doc = new jsPDF();
doc.html(document.body, {
callback: function (doc) {
doc.save();
}
});
***** フォント埋め込み
デフォルトの設定では日本語を出力できませんが、公式に用意されているツールでフォントを JS ファイルに変換し、それを読み込むことで可能になります。これは jsPDF#text() で自力で組み立てるときも、jsPDF#html() で組み立てるときもどちらでもできます。
また、次のように .ttf ファイルを XMLHttpRequest で取得して設定することもできます。この場合でも、後述するように CSS での指定が必要です。基本的にこっちのやりかたのほうが変換する手間がかからないので良いと思います。
code:js
const fetchFont = async (uri) => {
return new Promise(function (resolve, reject) {
const xhr = new XMLHttpRequest()
xhr.open('GET', uri, true)
xhr.responseType = 'arraybuffer'
xhr.onload = () => {
const bytes = new Uint8Array(xhr.response);
const chars = [];
for (let i = 0; i < bytes.byteLength; i++) {
chars.push(String.fromCharCode(bytesi)) }
resolve(window.btoa(chars.join('')))
}
xhr.onerror = () => {
reject()
}
xhr.send()
})
}
const fontData = fetchFont('../ipag.ttf')
const doc = new jsPDF()
doc.addFileToVFS('ipag.ttf', fontData)
doc.addFont('ipag.ttf', 'ipag', 'normal')
// ...
以下の手順を行った結果はこのページの先頭でリンクされてるリポジトリーにあるので参考にしてください。
**** フォントを JS に変換
まず、公式リポジトリーにある fontconverter.html でフォントを JS に変換します。このツールでフォントファイルを入力すると、fontName と fontStyle が特定されます。これらはあとで jsPDF#setFont() を呼び出すときに指定する文字列になります。 ちなみに .ttf では出力できますが、いまのところ .otf ファイルには対応していません。他のフォーマットは未確認です。
**** 生成された JS ファイルをモジュール化
webpack および Babel で import したいので、生成された JS ファイルの先頭で適当に export します。例えば次のようなコードをファイルの先頭に追加してください。
code:js
export const name = 'ipag';
**** jsPDF でフォントを指定
jsPDF をインスタンス化したあとに、先ほど生成した際に特定された fontName および fontStyle を指定してください。
code:js
const doc = new jsPDF();
doc.setFont('ipag', 'normal');
**** CSS でも同じフォントを指定
jsPDF#html() で DOM ツリーから PDF を生成したいので、それらのノードのスタイルにも同一のフォントが指定されている必要があります。
.ttf ファイルも配置した後、次のようにフォントファミリーを指定してください。
code:html
<head>
<style>
@font-face {
font-family: "ipag";
src: url("./ipag.ttf");
}
html {
font-family: "ipag";
}
</style>
</head>
**** 生成結果
ここまでの手順で、生成した PDF ファイルにフォントが埋め込まれます。
https://gyazo.com/bf362eafedad7d677c2ad707fe84b61d
https://gyazo.com/3e2d89b9e96093d7665d83f375263c80
https://gyazo.com/4d61fdd848fa4c6037f2dfe2efff03b5
***** px レイアウトの HTML から PDF を生成
例えば A4 portrait の PDF を生成する場合、デフォルトの設定だと width: 210mm; height: 297mm と指定した要素が 1 ページにまったく収まらず、ものすごいはみ出ます。
これは、jsPDF が内部的に px ベースで計算していないのが原因らしく、jsPDF コンストラクターに hotfixes: [ 'px_scaling' ] を指定することで回避できます。
***** 改ページ
昔はサポートされてたっぽいですが、v2.1.1 では CSS の break-before などの改ページ系プロパティーはサポートされていません。
ページを表す要素の class 属性に .page を指定しておき、これに break-after: always を指定することで、PDF 上でも必ず改ページされるようにしたかったのですが諦めました。代わりに、CSS の height と jsPDF のコンストラクターに同じ px 数を指定することで、必ず改ページされるようにしました。ちなみに、CSS のほうも px で指定しないと単位計算で誤差が発生してしまうため、ページ数が増えるにつれてずれてしまいます。