|||||||||||||||||||||

なんぶ電子

- 更新: 

JavaScriptでPDFを作成

PDF-LIB

過去に、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のバックグラウンドが透過状態なら背後が透けます。この辺りの詳細はPDFをLibre Officeで編集した際の記事をみていただければと思います。

指定位置から上に伸びます

 

別ページでは、言語はJavaとなってしまいますがPDFからテキストや画像を抽出する方法も紹介していますのでよろしかったら合わせて読んでみてください。


参考にさせていただきましたサイトの皆様、ありがとうございました。

筆者紹介


自分の写真
がーふぁ、とか、ふぃんてっく、とか世の中すっかりハイテクになってしまいました。プログラムのコーディングに触れることもある筆者ですが、自分の作業は硯と筆で文字をかいているみたいな古臭いものだと思っています。 今やこんな風にブログを書くことすらAIにとって代わられそうなほど技術は進んでいます。 生活やビジネスでPCを活用しようとするとき、そんな第一線の技術と比べてしまうとやる気が失せてしまいがちですが、おいしいお惣菜をネットで注文できる時代でも、手作りの味はすたれていません。 提示されたもの(アプリ)に自分を合わせるのでなく、自分の活動にあったアプリを作る。それがPC活用の基本なんじゃなかと思います。 そんな意見に同調していただける方向けにLinuxのDebianOSをはじめとした基本無料のアプリの使い方を紹介できたらなと考えています。

広告