IE11でPDFを表示する

# はじめに
IE11ではPDFビューアー機能を提供していない為、[pdf.js](https://github.com/mozilla/pdf.js)を使ってPDFの表示をしようと思います。
pdf.jsはPDFを表示する機能を提供できるライブラリでダウンロードして設置するだけでPDFビューアーの機能を導入できます。
ただし、今回はnpmを使って管理をしたかった為、ビューアーページは自作して実装を行います。

# pdf.jsのインストール
npmを使ってインストールします。

```
npm i pdfjs-dist 
```

# PDFビューアーの作成
以下の３つ作成します。
- レンダリング用のjs
- PDF変換用のjs
- ビューアー用のテンプレート

### レンダリング用のjs
IE11でも動作させる為にrequireをes5にしています。

```js:viewer.js
// pdf.jsのes5用を指定
const pdfJs = require("pdfjs-dist/es5/build/pdf");

// PDF変換用のjsを指定
// なぜ、このような書き方をしているかはPDF変換用のjsの箇所で説明しています
pdfJs.GlobalWorkerOptions.workerSrc = '/js/worker.js';

let currentPage = 1; // PDFの現在のページ番号
let pages = []; // PDFの全ページデータ
const container = document.getElementById('container'); // PDFをレンダリングしたいコンテナ要素
const pdfPath = '/test.pdf'; // 読み込むPDFのファイルパス
pdfJs.getDocument(pdfPath).promise.then(function(pdf) {
    // 1ページ目からPDFをレンダリング
    pdf.getPage(currentPage).then(renderPage);

    // レンダリング処理
    function renderPage(page) {

        // レンダリング設定
        const viewport = page.getViewport({ scale: 1 });
        const sideMargin = 0.8; // ブラウザウィンドウ横幅の20%がマージンとなるようにする
        const scale = (window.innerWidth * sideMargin) / viewport.width;
        const scaleViewport = page.getViewport({ scale: scale });
        const canvas = document.createElement('canvas');
        const context = canvas.getContext('2d');
        canvas.height = scaleViewport.height;
        canvas.width = scaleViewport.width;
        const renderContext = {
            canvasContext: context,
            viewport: scaleViewport
        };

        // レンダリング処理
        page.render(renderContext).promise.then(function () {
            if (currentPage < pdf.numPages) {
                // 最終ページでなければ、次ページ読み込み
                pages[currentPage] = canvas;

                // 次ページ読み込み
                currentPage++;
                pdf.getPage(currentPage).then(renderPage);
            } else if (currentPage === pdf.numPages) {
                // 最終ページでレンダリング
                pages[currentPage] = canvas;
                for (let i = 1; i < pages.length; i++) {
                    container.appendChild(pages[i]);
                }
            }
        });
    }
});
```

### PDF変換用のJS

```js:worker.js
require("pdfjs-dist/es5/build/pdf.worker"); // es5のファイルを指定
```

requireを一行だけなら、`viewer.js`ですれば良いじゃないかと本来なら思いますが、
pdf.jsはPDF変換処理をバックグランドで動作させる為に別ファイルで作成する必要があります。
もし同じファイルで読み込んでしまうとブラウザに以下の警告が出ます。
`Fake Worker for Web Wroker`
この警告が出ても問題なく動作し、気にするほどパフォーマンスが低下する訳ではないようですが、やはり警告が出ると気になるので分けた方が良いと思います。

[Fake Worker for Web Wrokerの説明](https://www.codeproject.com/articles/568136/porting-and-extending-pdfjs-to-nodejs)
> Fake Worker for Web Wroker
PDF.js will fallback to "fake worker" when Web Worker is not available, the code is built in, not much works need to be done, only need to be aware that the parsing would occur in the same thread, not in background worker thread any longer. From my tests, paring performance is not a concern, either running as a web service or a command line, regular PDF forms (less than 8 pages) usually be parsed and serialized within a couple of hundreds milliseconds.

### ビューアー用のテンプレート

```html:viewer.blade.php
<!doctype html>
<html lang="ja">
<head>
    <meta charset="utf-8">

    <title>PDF Viewer</title>

    <script src="{{ mix('js/viewer.js') }}" defer></script>

    <style>
        body {
            background-color: #787878;
        }
        #container canvas {
            margin: 15px auto;
            display: block;
            box-shadow: 3px 3px;
        }
    </style>

</head>
<body>
<div id="container"></div>
</body>
</html>
```

## バンドルの設定
今回は[Laravel Mix](https://readouble.com/laravel/6.x/ja/mix.html)を使用しているので、Webpackでの設定は適宜変更してください。

```js:webpack.mix
const mix = require('laravel-mix');
mix.js('resources/js/viewer.js', 'public/js/'); // レンダリング用
mix.js('resources/js/worker.js', 'public/js/'); // PDF変換用
```

# まとめ
IE11でPDFを表示させるために[pdf.js](https://github.com/mozilla/pdf.js)を使って実装してみました。
`Adobe Acrobat Reader`をインストールすれば今回のような対応は不要ですが、全てのユーザーがインストールしている訳ではないので、できればPDFビューアーは用意しておきたいです。
また、スマホサイトでのPDFビューアーとしても利用できますので、IE11が使われなくなっても有用です。