JavaScriptでPDFを作成
過去に、MSワードのサムネイルプレビューをPDF化したファイルから作成したり、GhostScriptを使ってPDFを画像に変換したりしました。このブログではあまり触れていませんが、スクリプトからPDFを作成したこともあり、その際はiTextというJavaライブラリを使いました。
iTextはたいへん優れたライブラリですが、ブラウザにおけるJavaの環境がお世辞にもいいとは言えない状態なので、Webで活用しようとするとなかなか難しい存在です。ちなみにiTextがどのような感じが知りたい方はItext8で文字列をシェイプ化してPDFに変換した際の記事を参考にしてください。
そこでその代替を探していたところ、JavaScriptでPDFを生成・加工できるPDF-LIBというライブラリを見つけたので、使い方の覚書を残しておきます。
ライブラリの使い方
ブラウザと、Nodeどちらからでも利用が可能です。
ブラウザで利用する場合は、CDNを利用します。自サイトでホストしたい場合もここからダウンロードすればいいと思います。
ページを読み込んだ際に、事前に用意しておいたiframeの表示域に作成したPDFを表示するサンプルは次のようになります。
sample.html
...
<!-- PDF表示枠 -->
<iframe id="display-target" style="width: 100%; height: 100%;"></iframe>
<!-- ライブラリ読み込み -->
<script src="https://unpkg.com/pdf-lib"></script>
<script>
async function createPdf(numWidth, numHeight) {
// PDFドキュメントの作成
const pdfDoc = await PDFLib.PDFDocument.create();
// ページの作成
const page = pdfDoc.addPage([numWidth, numHeight]);
// カーソル位置を110,200へ
page.moveTo(50, 250);
// テキストを線画
page.drawText('Hello World.');
// PDFをブラウザで表示できるようにデータ化
const pdfDataUri = await pdfDoc.saveAsBase64({ dataUri: true });
// iFrameのsrcにセット
document.getElementById('display-target').src = pdfDataUri;
}
</script>
createPdf(400,500);
...
addPageに渡す引数は全体のサイズです。単位はポイント(pt)のようです。ミリに換算する場合は、値に0.3528をかけます。サンプル中では、moveToでptを指定してカーソルを動かしています。X軸は左辺が0で右に向かって伸びていきますが、Y軸は下辺が0で上に向かって伸びていきます。
作成したPDFをそのままダウンロードしたい場合は次のように変更します。
sample2.html
...
<!-- ライブラリ読み込み -->
<script src="https://unpkg.com/pdf-lib"></script>
<script>
async function createPdf(numWidth, numHeight) {
// PDFドキュメントの作成
const pdfDoc = await PDFLib.PDFDocument.create();
// ページの作成
const page = pdfDoc.addPage([numWidth, numHeight]);
// カーソル位置を110,200へ
page.moveTo(50, 250);
// テキストを線画
page.drawText('Hello World.');
const pdfDoc = await PDFLib.PDFDocument.create();
const page = pdfDoc.addPage([350, 400]);
page.moveTo(50, 250);
page.drawText('Hello World!');
const pdfBytes = await pdfDoc.save();
const blob = new Blob([pdfBytes],{type:'application/octet-stream'});
const elAD = document.createElement('a');
elAD.download='sample.pdf';
elAD.innerHTML='ダウンロード';
elAD.href=window.URL.createObjectURL(blob);
elAD.click();
}
</script>
createPdf(400,500);
...
日本語フォント
日本語フォントの設定をしてみます。先ほどはブラウザでしたので、今度はNode環境で試してみたいと思います。
まずNode環境を作成してアプリをインストールします。
@pdf-lib/fontkitがフォントを取り込むためのライブラリですが、fontkitのフォークだそうです。
PS> mkdir d:¥jspdf
PS> cd d:¥jspdf
PS > node init
...
PS > npm install pdf-lib
...
npm install @pdf-lib/fontkit
...
次にコードです。ESモジュールとして記述するので、package.jsonに "type": "module"と追加するか、ファイルの拡張子をmjsにします。
sample.html
...
import { PDFDocument, rgb } from 'pdf-lib';
import fontkit from '@pdf-lib/fontkit';
import fs from 'fs';
const makePdfSample = async (strFontName) => {
// フォント読み込み
// 形式は Uint8Array or ArrayBuffer
const fontBytes = fs.readFileSync(strFontName);
// PDFドキュメントを作成します。
const pdfDoc = await PDFDocument.create();
// フォントキットのインスタンスを登録
pdfDoc.registerFontkit(fontkit);
// ドキュメントにフォントを埋め込み
const customFont = await pdfDoc.embedFont(fontBytes);
//利用するフォントの表示
//console.log(customFont);
// 新規ページを作成
const page = pdfDoc.addPage();
// テキストとサイズの設定
const text = '日本語表示のサンプル';
const textSize = 35
//テキスト全体のサイズを取得(後で枠線を引くため)
const textWidth = customFont.widthOfTextAtSize(text, textSize)
const textHeight = customFont.heightAtSize(textSize)
// テキストを線画
page.drawText(text, {
x: 40,
y: 450,
size: textSize,
font: customFont,
color: rgb(0, 0.53, 0.71),
})
// ボックスを線画
page.drawRectangle({
x: 40,
y: 450,
width: textWidth,
height: textHeight,
borderColor: rgb(1, 0, 0),
borderWidth: 1.5,
})
// Uint8Arrayのバイナリとして保存
const pdfBytes = await pdfDoc.save()
// ファイル書き出し
fs.writeFileSync('sample.pdf', pdfBytes);
}
//エントリーポイント
makePdfSample('meiryob001.ttf');
オブジェクト形式で位置情報やサイズ、色などの情報を渡せます。色の指定は0から1の間で行います。また、「rotate: degrees(90)」といったように角度を渡すこともできます。さらに、メソッドでdrawSvgPath(パス情報,{})とすることで任意のSVG図形も線画することができます。
上記のコードで作成したファイルが次のようになりました。
ttfファイルのコレクションであるttcファイルを利用できるとありがたかったのですが、残念ながらこのあたりは本家のfontkitに依存している部分でうまくいかないそうです。上記のサンプルではUniteTTCを利用させていただき、メイリオのttcを分割したうちの一つを使っています。
JavaScriptでは同様のことができるライブラリにttc2ttfがあるので、このあたりのコードを改変させてもらえばttcを指定して使えそうですが...。
Webでフォントを埋め込む
通常Webフォントや.ttfフォントを使ってページを装飾するにはCSSを使いますが、LIB-PDFで利用するためにはスクリプトで読み込みをしないといけません。
先にもあったようにfontkitを使って読み込みをします。幸いこちらもCDNで配布されているようですので、本体と共に次のようにして利用します。
<script src="https://unpkg.com/pdf-lib"></script>
<script src="https://unpkg.com/@pdf-lib/fontkit@1.1.1/dist/fontkit.umd.min.js"></script>
Webで利用する場合はフォントファイルをホストすることになりますのでライセンスの問題が発生します。再配布を認めているフォントでないといけないでしょう。
ここでは再配布可能なフォントであるUme-fontを利用させていただきました。
他にはOpen Font Licenseで公開されている、Google FontsのNoto Sans Japaneseが利用できると思いますが、 こちらはフォントの形式がOTFなので、TTFへの変換が必要です。
Webでフォントを埋め込む際は、fetchを使ってホストしたフォントファイルを読み込んでArrayBufferにします。ArrayBufferの方が簡単なのでそちらにしていますが、データとしてはかUnit8Arrayでもいいようです。(ArrayBufferやUint8Arrayは何なのかという件はリンク先の記事で紹介しています。)日本語環境ではpdfDoc.saveAsBase64がうまく動かないのか、iframeに表示させようとしてもうまく動きませんでした。なのでblobからの変換でセットしました。ダウンロードする場合は前述の通りで動きます。
async function createPdf() {
const pdfDoc = await PDFLib.PDFDocument.create();
let fontBytes=await fetch('./ume-hgo4.ttf').then(function(response){ return response.arrayBuffer();});
await pdfDoc.registerFontkit(fontkit);
const customFont = await pdfDoc.embedFont(fontBytes);
const page = await pdfDoc.addPage([350, 400]);
await page.moveTo(110, 200);
await page.drawText('Hello 日本語!',{font:customFont});
//この方法だと表示されませんでした
//const pdfDataUri = await pdfDoc.saveAsBase64({ dataUri: true });
//document.getElementById('pdf').src=pdfDataUri;
const pdfBytes = await pdfDoc.save();
const blob = new Blob([pdfBytes],{type:'application/pdf'});
document.getElementById('pdf').src = URL.createObjectURL(blob);
//ダウンロードの場合は前述と同様です
//const pdfBytes = await pdfDoc.save();
//const blob = new Blob([pdfBytes],{type:'application/octet-stream'});
//const elAD = document.createElement('a');
//elAD.download='sample.pdf';
//elAD.innerHTML='ダウンロード';
//elAD.href=window.URL.createObjectURL(blob);
//console.log(blob);
//elAD.click();
}
アプリ
PDF-LIBを使ってPDFを「並べるツール」と「合成するツール」を作ってみました。どちらもサーバーにPDFを送信することはありませんのでご安心ください。ご利用の機器の環境やPDFファイルのサイズによってはうまく動かないことがあります。不具合があったら報告いただければ幸いですが、当ブログはこのツールを使って生じた一切の責任を負いません。
以下のツールは、MITライセンスのPDF-LIBを利用して提供しています。
PDFを並べるツール選択したPDFを縦横中央寄せで、仕上がりサイズに入るだけ並べます。
選択した下地のPDFの上に、下からの位置を指定してもうひとつのPDFを重ねます。重ねるPDFのバックグラウンドが透過状態なら背後が透けます。この辺りの詳細はPDFをLibre Officeで編集した際の記事をみていただければと思います。
指定位置から上に伸びます
別ページでは、言語はJavaとなってしまいますがPDFからテキストや画像を抽出する方法も紹介していますのでよろしかったら合わせて読んでみてください。
参考にさせていただきましたサイトの皆様、ありがとうございました。