利用 PDF.js 构建 PDF 的网页端渲染

2018年7月18日

在网页中显示 PDF 文件听起来是一件奇怪的事,但有时我们的确需要在网页中实时显示 PDF 内容。GitHub 便在 2015 年支持了这一功能,其中的关键技术是 Mozilla 开源的 JavaScript 库 PDF.js

PDF.js 利用 HTML5 的 canvas 技术提供了一种不依赖于本地系统的 PDF 客户端渲染方式。它的基本原理是将 PDF 页面渲染为固定大小的位图,而后加载在 canvas 元素中。如果读取的 PDF 文件与渲染它的网页处在同一个服务器上时,使用这个库可能是最方便的一种方式(这是由于 PDF.js 不支持跨域)。

我们首先看这篇文章最终想要得到的结果。这是利用 PDF.js 完成的一个 PDF 渲染组件,在桌面设备中可以正常渲染出页面;而出于性能和可用性考虑,我们屏蔽了智能手机的客户端渲染,并提供一个说明和下载链接。

下载 pdf-tutorial-sample.pdf 下载

PDF 预览在手机上不可用。您可以直接下载该文件.

接下来我们就一步一步实现这个结果。

引入 PDF.js

PDF.js 由 pdf.jspdf.worker.js 两个子库组成。其中 pdf.worker.js 主要提供了一些在线编辑和打印等富文本操作的功能,我们在这里暂时用不到,关于它的具体实例可以见 Mozilla 官方给出的 Demo。我们这里仅需要引入 pdf.js 即可实现与 GitHub 类似的渲染功能。

最简便的方法是利用 CDN 加载。在 HTML 文件的 <head> 标签里加载即可:

<script       
src="https://cdn.bootcss.com/pdf.js/2.0.489/pdf.min.js"></script>

我这里使用了国内比较稳定的 BootCDN,如果在主要用户在国外,也可以使用 cdnjs 等服务。

如果使用 node 进行开发,也可以选择把库安装在本地项目中:

$ yarn add pdfjs-dist

基本用法

PDF.js 使用 JavaScript 的 Promises 技术完成 PDF 的异步加载。我们先准备好 PDF 文件 pdf-tutorial-sample.pdf,然后尝试一下 PDF.js 是否可以正确得到文件的页数:

pdfjsLib.getDocument('pdf-tutorial-sample.pdf').then(pdf => {
  console.log(pdf.numPages);
});

// 结果:1

看来它可以正确得到文件的页数。上面这个例子告诉我们,pdfjsLibgetDocument 方法可以接受 PDF 文件名,返回一个 Promise 对象,如果读取成功的话会将 pdf 对象传入回调函数。

我们接着对 pdf 对象进行操作。在这之前,我们先在 HTML 文件里创建一个 canvas 画布:

<body>
  <canvas id="pdf-tutorial-canvas"></canvas>
</body>

这样我们就可以在 JS 里操作 DOM。我们先设定 PDF 渲染的缩放大小 scale 为 1,试着渲染一个页面:

var canvas = document.getElementById('pdf-tutorial-canvas');
var scale = 1;
pdfjsLib.getDocument('pdf-tutorial-sample.pdf').then(pdf => {
  pdf.getPage(1).then(page => {  // 得到 PDF 文件的第一页
    var viewport = page.getViewport(scale);  // 根据 PDF 页面信息得到视图
    var context = canvas.getContext('2d');  // 指定 canvas 画布按 2D 方式渲染

    // 设定画布大小
    canvas.height = viewport.height;
    canvas.width = viewport.width;

    // 设定渲染参数
    var renderContext = {
      canvasContext: context,
      viewport: viewport
    };

    // 渲染页面
    page.render(renderContext)
  });
});

可以看到,pdf 对象的 getPage 方法可以得到读取页面并进行异步操作,将其渲染到 canvas 上。默认地,PDF.js 以 72 DPI 的分辨率渲染出了页面。