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

2024/01/23

Javascript Tool アプリケーション

t f B! P L
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便利読み取りツールなどを自分で作るというのもアリかもしれませんね。 もし不便と感じているメーカーの方や、総務省関連の方などあれば、是非お問い合わせください。

人気の投稿

このブログを検索

ごあいさつ

このWebサイトは、独自思考で我が道を行くユゲタの少し尖った思考のTechブログです。 毎日興味がどんどん切り替わるので、テーマはマルチになっています。 もしかしたらアイデアに困っている人の助けになるかもしれません。

ブログ アーカイブ