【JS】PDFKitの日本語フォント使用に詰まった話（ブラウザ上）

#はじめに
javascriptのPDFKitで、日本語フォントを使うのが色々とめんどくさかったので書きます。

###参考
>[PDFKit公式サイト](https://pdfkit.org/)
>[XMLHttpRequest - MDN](https://developer.mozilla.org/ja/docs/Web/API/XMLHttpRequest)

##開発環境
- Node.js 14.17.0
- **PDFKit**
- **blobStream** (pdfのデータをblob変換して使用するため)
- **browserify** (require()を使えるようにするため)
- google chrome (ブラウザ)
- atom (テキストエディタ)-->自分の使っているやつで結構

※プログラムはlocalhostにサーバーを立てて実行。
[ここで詳しく解説されています](https://qiita.com/massie_g/items/2913066e596dae197539)

※**「PDFKit」「blobStream」「browserify」**はあらかじめインストールしといてください。

##目次
>1. PDFKitで日本語を出力してみる --> 文字化け
>2. 公式サイトに従ってフォントを指定。 --> エラー
>3. 解決？
>4. 本当の解決！！
>5. おわりに

#1.PDFKitで日本語を出力してみる
まず、試しにPDFを出力したいと思います。

```html:kijiyouhtml
<!DOCTYPE html>
<html lang="ja" dir="ltr">
  <head>
    <meta charset="utf-8">
    <title>PDFKIT日本語</title>
    <style>
      html, body{
        width: 100%;
        height: 100%;
      }
    </style>
  </head>
  <body>
    <iframe src="" width="100%" height="100%" id='pdf_result'></iframe>
  </body>
  <!-- jsファイル読み込み -->
  <script src="BundledKijiyou.js" charset="utf-8"></script>
</html>
```
```javascript:kijiyou.js
// pdfkit,blobStreamの読み込み
const PDFDocument = require('pdfkit');
const blobStream = require('blob-stream');

// iframeを取得
const iframe = document.getElementById('pdf_result');

// pdfドキュメントを作成
const doc = new PDFDocument({
  size: 'A4'
});
const stream = doc.pipe(blobStream());

// 適当にテキストを入力
doc.fontSize(25).text('This is TEXT!!!!', 10, 10);

// ドキュメントの編集を終了
doc.end();

// ドキュメントをオブジェクトURLに変換
stream.on('finish', function() {
  // オブジェクトURLを取得
  const url = stream.toBlobURL('application/pdf');
  iframe.src = url;
})
```
**※「kijiyou.js」はbrowserifyでrequire()を使えるようにする。--> 「BundledKijiyou.js」**

####結果
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1453146/45c4f2a2-400c-d50c-cd0a-cab637333021.png)

という風に、英語ならフォントが対応しているので、正常に描画されます。

###日本語を出力すると...

テキストに日本語を追加してみます。

```javascript:kijiyou.js
// pdfkit,blobStreamの読み込み
const PDFDocument = require('pdfkit');
const blobStream = require('blob-stream');

// iframeを取得
const iframe = document.getElementById('pdf_result');

// pdfドキュメントを作成
const doc = new PDFDocument({
  size: 'A4'
});
const stream = doc.pipe(blobStream());

// 適当にテキストを入力
doc.fontSize(25).text('This is TEXT!!!!', 10, 10);
doc.fontSize(25).text('これはテキストです!!!!', 10, 40);

// ドキュメントの編集を終了
doc.end();

// ドキュメントをオブジェクトURLに変換
stream.on('finish', function() {
  // オブジェクトURLを取得
  const url = stream.toBlobURL('application/pdf');
  iframe.src = url;
})
```

####結果
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1453146/7d2a146a-b1c4-3c2a-9f9c-dd882627acf2.png)


このように文字化けしてしまいます。
まあ、もともとのフォントが英語用なので、日本語に対応しないのは当然ですね。

#2.フォントを指定する
[PDFKit公式サイト](https://pdfkit.org/)に従ってフォントを指定してみます。
公式サイトによると、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1453146/47effdce-c655-19e7-17e8-36e6cbd3798f.png)
このように、「.font(ファイルパス)」でフォントを指定できそうです。

ちなみに、ファイルのルートは、

```:ルート
ファイル
├── node_modules
├── kijiyou.html
├── kijiyou.js
├── font.ttf(フォントファイル)
└── BundledKijiyou.js
```

となっているので、「font.ttf」とパスを指定すればいいはず...

じゃあ、指定してみます。



```javascript:kijiyou.js
// pdfkit,blobStreamの読み込み
const PDFDocument = require('pdfkit');
const blobStream = require('blob-stream');

// iframeを取得
const iframe = document.getElementById('pdf_result');

// pdfドキュメントを作成
const doc = new PDFDocument({
  size: 'A4'
});
const stream = doc.pipe(blobStream());

// 適当にテキストを入力
doc.fontSize(25).text('This is TEXT!!!!', 10, 10);
doc.font('font.ttf'); // <-- フォント指定
doc.fontSize(25).text('これはテキストです!!!!', 10, 40);

// ドキュメントの編集を終了
doc.end();

// ドキュメントをオブジェクトURLに変換
stream.on('finish', function() {
  // オブジェクトURLを取得
  const url = stream.toBlobURL('application/pdf');
  iframe.src = url;
})
```

####結果
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1453146/a5de97f0-1a61-2fc8-b7f8-7c4ffaeaa647.png)

fs.read...エラーが出やがった！！
んで、僕はこのエラーに3日を溶かしてしまいました...(ﾊｽﾞｶｼｲ...)

#3.エラー解決！！
このエラーを解決するには、なぜフォントを読み込めないかを理解する必要があります。
フォントを取得するには、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1453146/92a597b3-e58f-5593-b064-4801462ce104.png)
こんな感じに、サーバーにリクエストを送って、サーバーからフォントをもらわなければなりません。
でも、今の状態だと、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1453146/f8b25fe1-1c96-08a4-d257-f47fe82c6e00.png)
サーバーにリクエストを送っていないので、あるはずのないフォントファイルにアクセスしようとしていることになります。

なので、サーバーにリクエストを送って、フォントファイルを貰いましょう。
じゃあ、これでいいはず...

```javascript:kijiyou.js
// pdfkit,blobStreamの読み込み
const PDFDocument = require('pdfkit');
const blobStream = require('blob-stream');

// フォントを取得する関数
const getFont = function(fontPath) {
  // フォントリクエストを設定
  const fontRequest = new XMLHttpRequest();
  // フォントリクエスト初期化
  fontRequest.open('GET', fontPath);
  fontRequest.responseType = 'arraybuffer';
  // フォントを取得したら実行
  fontRequest.onload = function() {
    fontArrayBuffer = fontRequest.response;
    // fontArrayBufferをエンコード
    font = new Uint8Array(fontArrayBuffer);
    return font;
  }
  fontRequest.send();
}

// iframeを取得
const iframe = document.getElementById('pdf_result');

// pdfドキュメントを作成
const doc = new PDFDocument({
  size: 'A4'
});
const stream = doc.pipe(blobStream());

// 適当にテキストを入力
doc.fontSize(25).text('This is TEXT!!!!', 10, 10);
doc.font(getFont('font.ttf'));
doc.fontSize(25).text('これはテキストです!!!!', 10, 40);

// ドキュメントの編集を終了
doc.end();

// ドキュメントをオブジェクトURLに変換
stream.on('finish', function() {
  // オブジェクトURLを取得
  const url = stream.toBlobURL('application/pdf');
  iframe.src = url;
})
```

[XMLHttpRequest](https://developer.mozilla.org/ja/docs/Web/API/XMLHttpRequest)で、サーバーにリクエストを送って、ファイルをもらう関数を作った。
すると...

####結果
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1453146/6a6090d7-089d-720d-3364-c23c85ef856b.png)
はあ、、またエラーかよ、、、。
そう。ここにまた落とし穴があるんです。

この「XMLHttpRequest」の実行タイミングが、分かりづらいんです！！

僕は最初、こうだと思ってました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1453146/5158a17b-2157-61ee-a3a5-d5af72f5f5ab.png)


でも仮に、取得するデータが大きいと、データを取得し終わるまでサイトを表示できず、閲覧者を待たせてしまいます。なので、実行タイミングはこの様になっています。（多分）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1453146/d38ac3eb-76df-1540-88f6-bab78b03c4ba.png)

イメージとしては、プログラムが1本から2本に分岐して、同時に処理をしている感じです。

**フォントを取得する前にフォントを設定してしまうので、あるはずもないデータを読み込むことになりますね。**

#4.本当の解決
なので、フォントのデータを取得したあとにpdfを編集してあげればいいですね。
イメージとしてはこんな感じ。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1453146/99c39998-9f4a-188e-0a65-784e54bc97bb.png)


プログラムは...

```javascript:kijiyou.js
// pdfkit,blobStreamの読み込み
const PDFDocument = require('pdfkit');
const blobStream = require('blob-stream');

// iframeを取得
const iframe = document.getElementById('pdf_result');

// pdfを作成する関数を設定
const PDFProgram = function(font) {
  // pdfドキュメントを作成
  const doc = new PDFDocument({
    size: 'A4'
  });
  const stream = doc.pipe(blobStream());

  // 適当にテキストを入力
  doc.fontSize(25).text('This is TEXT!!!!', 10, 10);
  doc.font(font);
  doc.fontSize(25).text('これはテキストです!!!!', 10, 40);

  // ドキュメントの編集を終了
  doc.end();

  // ドキュメントをオブジェクトURLに変換
  stream.on('finish', function() {
    // オブジェクトURLを取得
    const url = stream.toBlobURL('application/pdf');
    iframe.src = url;
  })
}

// フォントを取得する関数
const getFont = function(fontPath) {
  // フォントリクエストを設定
  const fontRequest = new XMLHttpRequest();
  // フォントリクエスト初期化
  fontRequest.open('GET', fontPath);
  fontRequest.responseType = 'arraybuffer';
  // フォントを取得したら実行
  fontRequest.onload = function() {
    fontArrayBuffer = fontRequest.response;
    // fontArrayBufferをエンコード
    font = new Uint8Array(fontArrayBuffer);

    // pdfを作成 ------ 大事！！！
    PDFProgram(font);


  }
  fontRequest.send();
}

// 実行！！
getFont('font.ttf');
```

####結果
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1453146/7db16701-352b-1736-c7fe-40d87dbc716e.png)

このように、日本語出力ができました！！
fontRequest.onloadの式の中でpdfを作成することで、
フォントを取得したあと、無事に日本語出力することができます。

#5.おわりに
今回は、わかりやすいように、関数を作ったりしましたが、書き方を変えても、**フォントを取得してからpdfを作成する**ことを守っていれば、日本語出力ができるので、ぜひ、好きなようにプログラミングしてください！！

###それでは、良きPDFKitライフを！