11
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Nuxt.jsでpdf.jsを使うときにハマったこととその解決策

Last updated at Posted at 2021-07-27

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が呼ばれるとダメなようです。
そこで、renderingTaskrender処理が終わっていない間はコンポーネントのプロパティに持たせるようにして、次の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
    },

参考

11
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?