JavascriptでPDFファイルを表示する方法

2024年1月23日

Javascript アプリケーション

eyecatch ホームページでPDFを設置しているサイトがありますが、ほとんどの場合、ダウンロードして勝手に見てくれ的な設置方法になっています。 最近のインターネットブラウザであれば、PDFをブラウザの別タブで表示できたりもしますが、ホームページのモードやテイストなども切り替わってしまい、なんだかもどかしく感じます。 ていうか、そんなサイト使いづれ〜〜〜!と思うのは自分だけでしょうか? 以前からPDFjsの存在は知っていたんですが、nodejsじゃないと使えないな〜と勝手に思っていたんですが、全然簡単に使えるライブラリだったので、コレを利用して便利にPDFファイルをコントロールしてみようと思います。 注意:javascriptのpdfjsの機能説明やリファレンス解説をする内容ではないので、スニペット備忘録程度にソースコードを参照してください。

ホームページにおけるPDFファイルの扱い方について

今回PDFデータを便利に扱いたいと考えたのは、色々なサイトでPDFダウンロードするのが、本当にめんどくさいと思ったのと、先日から作っている自炊用ブックリーダー(自炊にちょうどいいブックリーダーアプリを開発する話)でPDFをページごとに画像として扱う処理を便利にしたかったからです。 PDFファイルは、ページをレンダリングして使うだけじゃなく、中にテキストデータも持っていたり、ベクターデータを持っていたり、非常に高機能なファイルなのですが、今回は1ページ毎を画像として取り出してみたいと思います。

ソースコード

構成

root-directory/ ├ index.htmnl ├ style.css └ js/ ├ main.js ├ build/cmaps/

PDFjsの取得

次のサイトから、ソースコードをダウンロードしてください。 https://mozilla.github.io/pdf.js/getting_started/#download Prebuilt (modern browsers)をダウンロードしておけば間違いないでしょう(恐らく) ダウンロードしたファイルを解凍すると、buildというフォルダがあるので、それをjsフォルダにコピーしてください。 次に、webフォルダの中にあるcmapsというフォルダも同じ場所にコピーしておいてください。

index.html

<link rel="stylesheet" href="style.css"/> <script type="module" src="js/main.js"></script> <input type="file" name="pdf_upload"/> <div class="image-area"></div>

style.css

.image-area{ display:flex; gap:20px; flex-wrap:wrap; margin:20px; padding:10px; border:1px dotted black; } .image-area:empty{ display:none; } .image-area > *{ width:150px; height:150px; background-color:#eee; } .image-area img{ width:100%; height:100%; object-fit:contain; box-shadow:4px 4px 10px rgba(0,0,0,0.5); } .image-area img:hover{ width:300px; height:300px; box-shadow:10px 10px 20px rgba(0,0,0,0.5); position:relative; z-index:10; border:2px solid black; background-color:#eee; }

main.js

import * as PdfJs from "./build/pdf.js" class Main{ constructor(){ PdfJs.GlobalWorkerOptions.workerSrc = `js/build/pdf.worker.js`; this.init() } get pdf_upload(){ return document.querySelector(`input[type="file"][name="pdf_upload"]`) } get img_area(){ return document.querySelector(`.image-area`) } // 初期設定 init(){ this.pdf_upload.addEventListener("change" , this.load.bind(this)) } // 選択されたPDFの読み込み load(e){ const pdf_file = e.target.files[0] const reader = new FileReader(); reader.onload = this.loaded.bind(this) reader.readAsArrayBuffer(pdf_file) } // 読み込んだPDFをライブラリで読み取る処理 loaded(e){ const pdf_data = e.target.result PdfJs.getDocument({ data : pdf_data, cMapUrl : `js/cmaps/`, cMapPacked : true, }).promise.then(this.view.bind(this)) } // レンダリング表示 async view(pdf){ for(let i=1; i<=pdf.numPages; i++){ const pdf_page = await pdf.getPage(i) const canvas = await this.get_canvas(pdf_page) const imgData = this.canvas2img(canvas) const img = await this.create_image(imgData) const page = document.createElement("div") page.className = "page" page.setAttribute("data-num" , i) this.img_area.appendChild(page) page.appendChild(img) } } // canvasエレメントの作成 async get_canvas(page){ const canvas = document.createElement("canvas") const viewport = page.getViewport({ scale: 1 }) canvas.width = viewport.width canvas.height = viewport.height const ctx = canvas.getContext('2d') const task = page.render({ canvasContext: ctx, viewport: viewport, }) await task.promise // レンダリング設定完了待ち return canvas } canvas2img(canvas){ const base64 = canvas.toDataURL(Main.mime) const tmp = base64.split(",") const bin = atob(tmp[1]) const mime = tmp[0].split(':')[1].split(';')[0] const buf = new Uint8Array(bin.length) for (let i=0; i<bin.length; i++) { buf[i] = bin.charCodeAt(i) } const blob = new Blob([buf], {type: mime}) return URL.createObjectURL(blob); } async create_image(data){ return new Promise((resolve, reject) => { const img = new Image() img.src = data img.onload = (e => resolve(e.target)) img.onerror = reject }) } } switch(document.readyState){ case "complete": case "interactive": new Main() break default: window.addEventListener("DOMContentLoaded" , (()=>new Main())) }

CDNを使うパターン

上記フォルダを設置しなくても、下記のようにmain.jsに記述することで簡単に構築することもできます。 import * as PdfJs from 'https://cdn.jsdelivr.net/npm/pdfjs-dist@4.0.379/build/pdf.min.mjs' class Main{ constructor(){ PdfJs.GlobalWorkerOptions.workerSrc = `https://cdn.jsdelivr.net/npm/pdfjs-dist@4.0.379/build/pdf.worker.mjs`; this.init() } ... // 読み込んだPDFをライブラリで読み取る処理 loaded(e){ const pdf_data = e.target.result PdfJs.getDocument({ data : pdf_data, cMapUrl : `https://cdn.jsdelivr.net/npm/pdfjs-dist@4.0.379/cmaps/`, cMapPacked : true, }).promise.then(this.view.bind(this)) }

実行

httpプロトコルで立ち上げないとjsのモジュール読み込みエラーがでるので、dockerやsimple-webや、VScodeのGoLiveなどで実行してください。 テキトウなPDFを選択すると、画像の用に中のページを画像に変換して一覧表示するようにしています。 もちろん、中のページを1ページずつ画像として取り出すこともできるし、今回はやっていませんが、テキストを取得することもできます。

デモ

あとがき

HTML5でかなり便利になったFILE apiを便利に使いこなすことで、Webサイトの利便性も格段に向上する事が分かりますね。 今回は、ローカルにあるPDFをFileReaderを使ってデータ取得しましたが、サーバーに置いてあるPDFファイルも便利にajaxで読み込んで同じ用に分解して使うことも可能です。 公系のページや、家電メーカーなどの説明書などPDFをダウンロードさせるパターンがほとんどなようなので、できればこうしてWebコンテンツとして、便利にユーザーインターフェイスも整えた形で提供してもらいたいモノですね。 もし可能なら、PDF便利読み取りツールなどを自分で作るというのもアリかもしれませんね。 もし不便と感じているメーカーの方や、総務省関連の方などあれば、是非お問い合わせください。