ホームページで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便利読み取りツールなどを自分で作るというのもアリかもしれませんね。
もし不便と感じているメーカーの方や、総務省関連の方などあれば、是非お問い合わせください。
0 件のコメント:
コメントを投稿