掌握PDF.js:轻松加载与渲染PDF的秘笈

2,663 阅读6分钟

引言

随着数字化时代的到来,pdf已经逐渐成为信息传递的一种重要格式,在现代网页应用中,用户日益需要直接在浏览器中预览和交互pdf文档,这种需求使得pdf预览功能变得越来越普遍,因此此类的开发场景也越发常见。PDF.js作为一个开源的JavaScript库,为我们提供了强大的工具来在浏览器中渲染pdf文件,使得开发者能够轻松实现这一功能并且能够完成许多复杂的开发需求。

然而,在实际使用PDF.js插件的过程中,我们常常会遇到各种问题,会踩各种各样的“坑”。因此,本文将深入探讨PDF.js插件的使用方法,并且分享一些常见的“坑”以及相应的解决办法,帮助开发者更顺利地在项目中集成和使用这一强大工具。

PDF预览方案对比

  • 使用HTML标签:预览效果最简单,但兼容性最差,仅支持部分PC端的浏览器,对于移动端的兼容效果不尽如人意。
    • iframe标签:将另一个HTML页面嵌入到当前页面中。
    <iframe src="./test.pdf" height="900px;" width="800px"></iframe>
    
    • embed 标签:将外部内容嵌入文档中的指定位置。
    <embed src="./test.pdf" type="application/pdf" width="100%" height="100%" />
    
    • object标签:引入一个外部资源。
    <object data="./test.pdf" type="application/pdf" width="100%" height="100%"></object>
    
  • 使用第三方插件:功能强大,可对pdf文件进行相关操作,PC端兼容性较好,但移动端存在一些比较常见的“坑”。
    • PDF.js:提供了一个完整的PDF查看器,且可以自定义和扩展,拥有更加广泛的适用性。
    • PDFObject.js:只提供了基本的PDF查看功能,兼容性取决于浏览器内置的PDF查看器。
  • PDF文件转化成图片进行展示:兼容性最好,但由于是图片格式无法对文件进行其他操作。

PDF.js使用方式

PDF.js插件渲染pdf文件一共有两种方式:

  • 手动加载:使用 PDF.js 的 JavaScript API 自定义pdf的加载和渲染过程,提供更灵活的控制。
  • 使用内置视图器(viewer):是 PDF.js 提供的一个完整的 HTML 视图组件,允许开发者直接加载pdf文件,并提供默认的用户界面,包括工具栏、缩放控制、页面导航、打印、下载等。

在这两种方式中,第一种是通过使用插件中的pdfjsLib.getDocument方法手动获取和渲染pdf文件,这就方便我们根据实际开发需求实现自定义的用户界面和交互,而第二种使用插件的内置视图器可通过iframe或直接访问viewer.html并提供PDF文件的URL来进行渲染。在实际开发场景中,我们可根据具体的开发需求去进行选择,下面对这两种方式分别进行简单的使用方式介绍:

1、手动加载

插件下载

PDF.js支持两种安装方式,分别是使用npm进行安装同时通过import导入进行使用以及在官网下载后将打包好的pdfjs静态资源放在项目的public目录下,本文着重介绍第二种安装方式及其使用。

[下载地址](PDF.js - Getting Started (mozilla.github.io))

image.png

引入

PDF.js插件最新版本为4.7.76,由于该版本采用了 ES6 模块化语法,这意味着脚本被设计为以模块的形式加载,允许使用 importexport 语句来管理依赖关系和代码结构。为了确保模块正确加载并运行,必须使用 pdf.mjs 文件,因此在引入pdf.mjs脚本时需要在<script>标签中添加type="module"属性并使用import语句进行导入。

<script type="module">
    import * as pdfjsLib from './public/pdf/build/pdf.mjs';
</script>
指定工作线程脚本的路径
pdfjsLib.GlobalWorkerOptions.workerSrc = './public/pdf/build/pdf.worker.mjs';
具体步骤
  • 创建一个容器来展示pdf;
  • 指定想要预览的pdf文件地址;
  • 使用pdfjsLib.getDocument方法加载指定的PDF文档,并在promise.then((pdf) => {})回调中进行渲染相关操作:
    • 使用pdf.numPages获取PDF文档的总页数
    • 定义一个renderPage函数,函数中调用getPage()函数从PDF文件中获取特定页面将其渲染为图像并显示在canvas上
    • 根据文档页数循环调用renderPage函数,将此文档的所有页面渲染显示在网页上
注意事项

若在预览pdf文件时出现内容显示不全的情况时,大概率是因为未正确配置cMapUrl 或 cMapPacked,导致某些字符无法正确映射,从而影响文本的显示。解决的办法就是在pdfjsLib.getDocument()中配置CMap文件地址以及将cMapPacked设为true,这项配置可以将CMap文件压缩后再显示,这样能减少网络传输的时间。 代码如下:

pdfjsLib.getDocument({
      url: urlParam, //指定文件路径;
      cMapUrl: "https://cdn.jsdelivr.net/npm/pdfjs-dist@2.2.228/cmaps/",
      cMapPacked: true
})

配置cMap文件地址前:

image.png

配置后(部分发票内容已经过打码处理):

image.png

完整代码
<!DOCTYPE html>
<html>
<head>
  <title>PDF.js demo</title>
  <style>
     #pdf-container{
        width: 100%;
        text-align: center;
     }
     canvas{
        width: 100%;
     }
  </style>
</head>
<body>
  <div id="pdf-container"></div>
  <script type="module">
    import * as pdfjsLib from './public/pdf/build/pdf.mjs';
    // 指定工作线程脚本的路径
    pdfjsLib.GlobalWorkerOptions.workerSrc = './public/pdf/build/pdf.worker.mjs';

    let container = document.getElementById('pdf-container');
     let urlParam = "./public/invoice.pdf";

    pdfjsLib.getDocument({
      url: urlParam, //指定文件路径
      cMapUrl: "https://cdn.jsdelivr.net/npm/pdfjs-dist@2.2.228/cmaps/",
      cMapPacked: true
    }).promise.then(function(pdf) {
      // 获取 PDF 文件的总页数
      let numPages = pdf.numPages;
      function renderPage(pageNumber) {
          pdf.getPage(pageNumber).then(function(page) {
              let scale = 1.2; // 设置缩放比例
              let viewport = page.getViewport({ scale: scale }); // 获取视口以适应缩放
              let canvas = document.createElement('canvas'); // 创建一个canvas元素
              let context = canvas.getContext('2d'); // 获取canvas的绘图上下文
              canvas.width = viewport.width; // 设置canvas宽度
              canvas.height = viewport.height; // 设置canvas高度
              // 渲染页面
              page.render({
                  canvasContext: context, // 渲染到的canvas上下文
                  viewport: viewport // 视口设置
              });
              // 添加 canvas 到 DOM
              container.appendChild(canvas); // 将canvas添加到指定的容器中
          });
      }
      // 渲染所有页面
      for (let i = 1; i <= numPages; i++) {
        renderPage(i);
      }
    });
  </script>
</body>
</html>

2、PDF.js Viewer

pdf.js viewerPDF.js 自带的一个完整的PDF查看器(Viewer),它不仅仅是一个简单的pdf渲染工具,还为用户提供了诸多高级功能,类似于浏览器内置的pdf阅读器,这个查看器可以用来快速加载、浏览、搜索、缩放以及批注pdf文档等功能。

使用查看器

通常情况下,我们一般使用iframe标签来嵌入PDF.js查看器,代码如下:

<iframe id="pdfViewer" src="./public/pdf/web/viewer.html?file=https://mozilla.github.io/pdf.js/web/compressed.tracemonkey-pldi-09.pdf"></iframe>
  • src属性:设置为 PDF.js 查看器的 HTML 文件,并通过 file 参数指定要加载的pdf文件的 URL

image.png

注意事项

若运行项目后无法正常预览pdf文件,且控制台中出现Error: file origin does not match viewer's的报错信息,这是由于跨域引发的问题,最简单的解决办法就是让查看器不去判断是否跨域,只需要注释掉web/viewer.mjs文件中的以下代码即可:

if (fileOrigin !== viewerOrigin) {
    throw new Error("file origin does not match viewer's");
}

总结

在实际的开发需求中,一般来说我们使用第一种通过调用pdfjsLib.getDocument()手动加载pdf文件的方式较多,它赋予了开发者更多的控制权,并且这种方式可以更容易地适应更多复杂的开发场景,比如在某些情况下可能需要从不同的服务器或API获取pdf文件,而不仅仅依赖于静态链接。

总而言之,合理利用好这些具备强大功能的插件,可以帮助我们节省开发时间,提高效率,还能提升项目的质量和可维护性,这对于我辈开发者来说至关重要!