关于pdf.js,这是mozilla开发和维护的开源软件,和所有mozilla的软件一样,他们的功能十分强大,迭代速度也飞快,但文档一般跟不上开发速度,所以当你尝试使用它时,你会发现你找不到文档,或者能找到的文章用的是老旧的版本。

mozilla团队坦言,他们不会为第三方框架提供支持,也就是说,没有官方对vue, react, angular等框架的绑定,也没有使用教程。我在网上找到的通常都是2.5以下版本的教程。不是不能用,但是我更倾向于使用最新的稳定版本。


pdf.js是轻量级渲染引擎,不是排版引擎。这一点非常重要。pdf.js的工作原理是,在canvas上以图片形式绘制pdf内容。另外用dom渲染文字,以供选择和批注。

我这篇博客只写如何渲染,更多内容未来会更新。


使用过程分如下步骤:

  1. 加载库,设置worker(worker相当于另一个进程,用于执行较大运算量的后台工作)
  2. 加载文件
  3. 按页渲染pdf
  4. 渲染text layer

加载库,设置worker

// 加载库,设置worker
import * as pdfjsLib from "pdfjs-dist/build/pdf";
import "pdfjs-dist/web/pdf_viewer.css";
pdfjsLib.GlobalWorkerOptions.workerSrc =
  "https://cdn.jsdelivr.net/npm/pdfjs-dist@3.5.141/build/pdf.worker.min.js";

template:

<div id="container">
    <canvas id="theCanvas"></canvas>
</div>

加载pdf文件:

注意,这里会返回一个task,可以保存在变量中复用

this.loadingTask = pdfjsLib.getDocument(pdfUrl);

渲染canvas

const canvas = document.getElementById("theCanvas");
const context = canvas.getContext("2d");
const scale = 1.5;

this.loadingTask.promise.then((pdf) => {
    pdf.getpage(1)
        .then((page) => {
            const viewport = page.getViewport({ scale });
            canvas.height = viewport.height;
            canvas.width = viewport.width;
            const renderContext = {
              canvasContext: context,
              viewport: viewport,
            };
            const renderTask = page.render(renderContext);

        })
})

渲染文字dom:

const container = document.getElementById("container");

this.loadingTask.promise.then((pdf) => {
    pdf.getpage(1)
        .then((page) => {
            const textlayer = document.createElement("div");
            textlayer.className = "textLayer";
            container.appendChild(textlayer);

            page.getTextContent().then((textContent) => {
              pdfjsLib.renderTextLayer({
                textContentSource: textContent,
                container: textlayer,
                viewport: viewport,
                textDivs: [],
              });
            });
        })
})

分页:

<div class="page-nav">
  <button @click="prev">prev</button>
  <span>{{ currentPage }}/ {{ numPages }} </span>
  <button @click="next">next</button>
  <input
    type="number"
    v-model="selectedPage"
    @input="gotoSelectedPage"
    min="1"
    max="numPages"
  />
</div>
export default {
  data() {
    return { 
      currentPage: 1,
      numPages: 0,
      selectedPage: 1
    };
  },
  methods: {
    renderPage(num) {
      const container = document.getElementById("container");
      const canvas = document.getElementById("theCanvas");
      const context = canvas.getContext("2d");
      const scale = 1.5;
      this.loadingTask.promise.then((pdf) => {
        pdf
          .getPage(num)
          .then((page) => {
            const viewport = page.getViewport({ scale });
            const textlayer = document.createElement("div");
            textlayer.className = "textLayer";
            container.appendChild(textlayer);
            canvas.height = viewport.height;
            canvas.width = viewport.width;
            const renderContext = {
              canvasContext: context,
              viewport: viewport,
            };
            const renderTask = page.render(renderContext);
            page.getTextContent().then((textContent) => {
              pdfjsLib.renderTextLayer({
                textContentSource: textContent,
                container: textlayer,
                viewport: viewport,
                textDivs: [],
              });
            });
          })
      });
    },
    next() {
      if (this.currentPage < this.numPages) {
        this.currentPage++;
        this.renderPage(this.currentPage);
      }
    },
    prev() {
      if (this.currentPage > 1) {
        this.currentPage--;
        this.renderPage(this.currentPage);
      }
    },
    gotoSelectedPage() {
      if (this.selectedPage >= 1 && this.selectedPage <= this.numPages) {
        this.currentPage = this.selectedPage;
        this.renderPage(this.currentPage);
      }
    },
  }
},

特别注意,由于pdf.js要求container div必须是absolute position,我们的分页nav是放在下面的,所以要动态地设置top. 

this.loadingTask.promise.then((pdf) => {
  \\...
  pdf
    .getPage(num)
    .then((page) => {
    \\...
        const renderTask = page.render(renderContext);
    \\...
        return renderTask.promise;
    }).then(() => {
        const canvas = document.getElementById("theCanvas");
        this.canvasHeight = canvas.offsetHeight;
    });
})

我们在page.render会返回一个promise,我们可以根据这个promise判断canvas是否渲染完毕,渲染完毕后,动态更新height property. 然后再绑定即可。

export default {
  data() {
    return {
    \\ ..
      canvasHeight:0
    }
  },
  computed: {
    pageNavTop(){
      var v = this.canvasHeight + 40;
      return `${v}px`;
    },
  }
  \\...
}

技术细节也就差不多这些,具体请见codesandbox

pdf.js-3.5-vue

或github

pdf.js-vue

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐