Nuxt.jsでpdf.jsを使おうとしたらいくつか困るポイントがあり、またドンピシャな解決策もなかなか見つからなかったので備忘録的に残しました。
体系だった使い方は、参考にあげたGoodpatchさんの記事がよくまとまっています。
しかし、この記事通りにやってもハマると思うので、合わせて読むと良いと思います!
ちなみに、この段階で私が使っていたNuxtのバージョンは2.15.7です。
また、pdf.jsはnpmでインストールできるpdfjs-dist
を使っています。
pdfjs-distのバージョン
この記事を書いている時点で最新のpdfjs-distのバージョンは2.9.359ですが、新しいバージョンでは新しいJavascriptの記法をつかっているということだと思われ、pdf.jsが呼ばれるタイミングでSyntax Errorが起きてしまいます。
WebpackやBabelに精通しているわけではないので深追いはせず、pdfjs-distのバージョンをもどすのが手っ取り早いと思い2.5.207に戻すことで対処しました。
CMap、worker
pdf.jsでPDF内の文字を表示させるには、CMapが必要です。
また、こちらは絶対必要というわけではないですが、パフォーマンス的に処理をメインスレッドから逃すためにWeb Workerを使うためのスクリプトも使うことができると望ましいです。
ブラウザがこれらのファイルを読みにいけるようになっている必要があります。Nuxtを使っている場合は、staticディレクトリの中に配置してしまうのが手っ取り早いと思います。(参考で挙げたように、クロスオリジン関連には気をつけましょう)
これらはnode_modules/pdfjs-dist/
のなかに含まれているので、
cp -r node_modules/pdfjs-dist/cmaps/ static/cmaps/
cp node_modules/pdfjs-dist/build/pdf.worker.min.js static/
とstatic/
の中にコピーしておいて、コンポーネントの中でそれぞれの位置を指定します。
<template>
...
</template>
<script>
import * as PDFJS from 'pdfjs-dist'
PDFJS.GlobalWorkerOptions.workerSrc = '/pdf.worker.min.js' // ← workerスクリプトを配信するパスを指定
export default {
...
async mounted() { // 別にmountedである必要はないです
this.pdf = await PDFJS.getDocument({
data: <PDFファイルのデータ>,
cMapUrl: '/cmaps/', // ←ここでCMap関連のファイルを配信するパスを指定
cMapPacked: true
}).promise
},
...
}
</script>
CMapについて詳しくは↓
レンダータスクのキャンセル
最初はこんな感じで、表示するページを表すthis.currentPageNum
が変化するたびに同じcanvasを使いまわして新しいページを表示するようにしていました。
参考のGoodpatchさんの記事と同じ内容です。
以下はmethods
の中に書くメソッドです。
async renderPage() {
const page = await this.pdf.getPage(this.currentPageNum)
const canvas = this.$refs.previewZone
const viewport = page.getViewport({ scale: 1 })
const context = canvas.getContext('2d')
const renderingTask = page.render({
canvasContext: context,
viewport: viewport,
})
await this.renderingTask.promise
},
だいたいこれで動くのですが、ページ切替を早く行うとpdfjs Cannot use the same canvas during multiple render() operations
というエラーが起きていました。
まだ前のrender
が終わっていないのに次のrender
が呼ばれるとダメなようです。
そこで、renderingTask
をrender
処理が終わっていない間はコンポーネントのプロパティに持たせるようにして、次のrender
が来たときに前のが終わっていなかったらキャンセルするようにしました。
async renderPage() {
if (this.renderingTask) {
await this.renderingTask.cancel()
this.renderingTask = null
}
const page = await this.pdf.getPage(this.currentPageNum)
const canvas = this.$refs.previewZone
const viewport = page.getViewport({ scale: 1 })
const context = canvas.getContext('2d')
this.renderingTask = page.render({
canvasContext: context,
viewport: viewport,
})
this.renderingTask._internalRenderTask.callback = () => {
this.renderingTask = null
}
await this.renderingTask.promise
},
参考