連載一覧
-
PDFを閲覧+αするクロム拡張の楽な作り方
- クロム拡張でWeb上のPDFファイルを閲覧できる様になる
- ただし、URLは謎形式で手入力する必要あり
-
PDFを閲覧+αするクロム拡張の楽な作り方②
- 普通のURL入力やリンククリックのPDF閲覧に対応
- 普通にPDFビューアとして使える様になる
-
PDFを閲覧+αするクロム拡張の楽な作り方③
- PDF閲覧に独自処理を追加
拙作のWebにリンク&ノートを追加できるクロム拡張も、PDF対応する予定ですので、よろしくお願いします。
前回のまとめ
Web上でPDF閲覧にスクリプト処理を追加するのは、pdf.jsライブラリのおかげで想像よりはるかに簡単に済みます。前回まで、 ほぼノーコードの以下の手順で、PDFファイルビューア ができました。
- host_permissions・declarativeNetRequest・web_accessible_resourcesのクロム拡張設定
- pdf.jsのダウンロード・解凍・配置
- pdf.jsライブラリ内のコードを3行だけコメントアウト
たったこれだけで、普通に使う分には全く問題の無いPDFビューアの完成です。唯一気になるのは、PDFファイル閲覧時のURL欄が chrome-extension://[クロム拡張ID]/pdfjs-2.14.305-dist/web/viewer.html?file=[PDFのURL] と謎形式なので、コピペして他人と共有しにくいこと位ですが、実はほとんどのクロム拡張PDFビューアも同じ状態です。知る限りPDF Viewer for Vimium Cだけがiframe内にviewer.htmlを配置することで解決しています。
今回の概要
今回、 作ったPDFビューアに独自処理を追加 する方法をまとめます。
- PDFビューア独自機能を介せずDOMを直接操作する方法
- PDFビューア独自機能を利用する方法
の2本立てです。
PDFビューア独自機能を介せずDOMを直接操作する方法
作ったPDFビューアは、viewer.htmlが内部でviewer.jsなどを呼んで、以下の要素をDOMに追加することで、PDFを描画しています。
- HTML5の CANVAS 要素内にテキスト以外を描画
- テキストだけは別に SPAN の集合として描画
つまり、PDFビューアを無視して、DOM上のこれらの要素にアクセスすれば、PDFファイルに応じた処理ができます。実現するには、以下の手順が簡単でしょう。
viewer.htmlに独自スクリプト呼び出し追加
- クロム拡張フォルダ
- pdfjs-2.14.305-dist
- web
- viewer.html
- web
- pdfjs-2.14.305-dist
の中に、以下の様な記述を追加して、PDF閲覧時に独自スクリプトも呼び出すようにします。
独自スクリプト追加
クロム拡張フォルダの直下に、以下のスクリプトを 文字コードUTF-8で 追加します。処理の内容はコメントを見てください。
const observer = new MutationObserver(() => {
// 変更があったら、body以下のテキスト全文にNULLが含まれるかのチェック
const text = document?.body?.textContent
if (typeof text === "string" && text.indexOf("NULL") >= 0) {
// NULLがあったらツッコむ
alert("ヽ( ・∀・)ノ┌┛ガッΣ(ノ`Д´)ノ")
}
})
addEventListener("load", () => {
// body以下の変更を監視
observer.observe(document.body, {
subtree: true,
childList: true
})
})
クロム拡張をリロードして、これとかこれを開くとツッコまれて、想定通り大変ウザいです。これがツッコまれないのは、「N U L L」と間に空白文字が入っているからです。 まだ修行が足りなかったか。
勘のよい方は、my_script.jsではDOM変化のたびに最大一度しかツッコんでいないのに
- ファイルを開いただけで何度もツッコまれた
- ページをめくっていったら、追加でツッコまれた
- 後の方のページにNULLと書いてあるのに、開いただけではツッコまれなかった
という点に気づかれたかもしれません。PDF.js付属のビューアは、巨大PDFファイルでも快適に閲覧できるよう、現在表示している周辺しかDOMに実体化せず、ページ移動などをトリガに 必要に応じて追加で実体化&不要そうなDOM要素の開放 を自動でしてくれます。何という上げ膳据え膳…
ただしこのため、 PDFファイル全体を対象に処理をしたい場合、DOMだけ相手にしていてはダメ、ということになります。 DOMに実体化していない部分へのアクセスには、PDFビューア独自機能へのアクセスが必要 です。
PDFビューア独自機能を利用する方法
ここからはJavaScriptではやりにくいので、TypeScriptで書きます。
必要なパッケージのインストール
- (nvm+)npm
- 未導入なら、この辺などを参考に、導入してください
- TypeScript
- 未導入なら、クロム拡張フォルダで npm install --save-dev typescript などを実行して、導入してください
- pdfjs-dist
- 未導入なら、クロム拡張フォルダで npm install --save-dev pdfjs-dist などを実行して、導入してください
pdfjsは連載一回目で配置していますが、なぜここで再度パッケージを追加するかというと、
- 導入済みの方は、webサーバなどで閲覧機能だけ利用する想定のパッケージ
- 閲覧の便宜のため、viewer.htmlが付属している
- TypeScriptの開発に重要な型情報が付いていない
- 今回導入する方は、pdfjsライブラリをプログラム上で利用する想定のパッケージ
- viewer.htmlが付属してない
- TypeScriptの開発に重要な型情報が付いている
という違いがあります。バージョンが同じなら、内部のviewer.jsなどは同じようですが、違うなら新しい方に合わせるなど調整しておくのが無難です。
TypeScriptで処理実装
クロム拡張フォルダの直下に、以下のスクリプトを 文字コードUTF-8で 追加します。処理の内容はコメントを見てください。VSCodeなどIDEで作業すると、as PDFDocumentProxy などで型を付与したあとは、入力補助も効いていい感じです。型の詳細は、この辺を参考にしてください。
import {type PDFDocumentProxy, type PDFPageProxy} from "pdfjs-dist"
import {type TextItem} from "pdfjs-dist/types/src/display/api"
let pdfViewerInitializeMonitor
addEventListener("load", () => {
pdfViewerInitializeMonitor = setInterval(() => {
// viwer.jsがwindowオブジェクトに追加しているAPIアクセスオブジェクトを取得
const viewer = (window as any)["PDFViewerApplication"]
// HTMLとしてのロード後も続くPDFのロードを待つ
if (viewer && viewer.pdfDocument && viewer.downloadComplete) {
// ロードが終わったら、1秒ごとのロード完了モニタを解除して処理本体を呼ぶ
clearInterval(pdfViewerInitializeMonitor)
myFunc(viewer.pdfDocument as PDFDocumentProxy)
}
}, 1000)
})
async function myFunc(doc: PDFDocumentProxy): Promise<void> {
const pageCount = doc.numPages
for (let i = 1; i <= pageCount; ++i) {
// 全ページをチェックして、NULLを見つけたらツッコむ
if (await pageHasText(doc.getPage(i), "NULL")) {
alert("ヽ( ・∀・)ノ┌┛ガッΣ(ノ`Д´)ノ")
return
}
}
}
async function pageHasText(page: Promise<PDFPageProxy>, text: string): Promise<boolean> {
// ページのテキストを得る
const textContent = await (await page).getTextContent()
for (const item of textContent.items) {
const textItem = item as TextItem
const t = textItem.str
// 指定のテキストが見つかればtrueを返す
if (t.indexOf(text) >= 0) {
return true
}
}
return false
}
TypeScriptのトランスパイル
クロム拡張フォルダで npx tsc my_script.ts --target es2015 --moduleResolution node を実行するだけで、トランスパイルされたmy_script.jsができます。targetなどは、トランスパイルを成功させるためのおまじないです。
クロム拡張をリロードして、後の方のページにNULLと書いてあるPDFを開くと、きちんと最初にツッコまれます。PDFビューア独自機能により、DOM範囲外の情報にアクセスできたことが分かります。
なお、実行時にエラーが出た場合、viewer.htmlへのmy_script.js登録時にtype="module"を忘れているためかもしれません。
最後に
PDFビューアへの独自処理追加、いかがだったでしょうか? 思ったより簡単にPDFを扱えることが分かって頂ければ幸いです。