likes
comments
collection
share

前端接入 pdfjs-dist 渲染 pdf 文件踩坑

作者站长头像
站长
· 阅读数 18

由于最终渲染页面属于自己定制的,所以这边通过直接引用 pdfjs-dist下的插件,只处理canvas 渲染部分,其他功能在此基础上自行定制,下面遇到的问题也是在使用插件进行 canvas 渲染时出现的问题

1、工程接入 pdfjs 时在安卓60版本下渲染失败,并报错

由于 pdfjs-dist 中语法偏高,如含有 ||= 等高级语法,所以在使用时,需要额外给pdfjs-dist进行 babel 再编译。

在webpack 中的rule下新增以下部分,需要注意使用"@babel/preset-env"时需要设置 modulescommonjs,否则默认 auto的情况编译出的代码是无法直接使用的

{
  test: /.m?js$/,
  include: [
    path.resolve("node_modules/pdfjs-dist/"),
  ],
  use: {
    loader: "babel-loader",
    options: {
      presets: [
        [
          require("@babel/preset-env"),
          {
            modules: "commonjs"
          }
        ]
      ],
      plugins: ["@babel/plugin-transform-runtime"]
    }
  }
},

2、node环境下部分机型使用 pdfjs 时无法渲染,如鸿蒙 p30

验证时发现使用时提示

structuredClone is not defined

发现 structuredClone 的兼容性更差,不知道为什么上面配置的 babel 降级怎么没有将这个语法给降了。

查看 github 官方 issue ,发现确实有这个问题,在使用时使用 pdfjs-dist/legacy/build下面对应的文件即可解决,官方文档中也有对应的说明

然后我思考了一下,发现这个目录下是解决兼容问题,那么 问题1 是不是也能通过这个问题解决,然后验证了一下,发现真不能,有些语法仍需要做 babel 处理,如 Private class fields等高级语法仍在该版本插件下使用

3、node环境下使用 pdf.worker.js,无报错,也无渲染

由于在 node 环境下通过将 pdf.worker.js 设置为 pdfjsGlobalWorkerOptions.workerSrc 无法生效,所以这里需要使用 pdf.worker.entry.js作为 workerSrc,对应 issue

此处注意使用的是 legacy下的还是 build下的需要与 pdfjs 引用的保持一致

4、体积问题

直接使用插件时体积较大,考虑到使用min版本时,pdf.work.entry 没有对应的min版本,查看源码如下:

(typeof window !== "undefined"
  ? window
  : {}
).pdfjsWorker = require("./pdf.worker.js");

那么我使用时是否直接按照上述方式调整,来降低 worker 的对应的体积呢,现象是能,整理数据发现,体积的增量几乎就在 worker 的体积上体现了

const pdfjsLib = require("pdfjs-dist/legacy/build/pdf.min.js");
pdfjsLib.GlobalWorkerOptions.workerSrc = (typeof window !== "undefined"
  ? window
  : {}
).pdfjsWorker = require("pdfjs-dist/legacy/build/pdf.worker.min.js");
原工程1119k
接入build版本3538k+2419k
接入 legacy 版本4035k+3916k
worker min 版本worker 非 min 版本
接入build min 版本2450k +1331k3657k +2538k
接入 legacy min 版本2793k +1674k3735k +3616k

根据工程的兼容性和体积需求,目前考虑使用 接入 legacy 的 min 版本且 worker min 版本

5、跨域问题

当 pdfjs 加载远程pdf时,不免会出现 CORS 跨域的问题,3种解决方案,主要都是通过下载文件后再访问来解决

1、将 pdf 下载到本地,放到工程中进行本地引用,此方式不仅增加工程体积,还不灵活

2、将 pdf 下载到静态资源服务器上,在工程中访问静态资源服务器上的资源进行实现,此方式需要手动操作,比较低效

3、通过调用服务器接口由服务器进行资源下载,待下载完成后由服务器提供对应的资源的 ArrayBuffer 进行访问,此方式比较灵活

根据我们工程的现状, 选择了第三种方式

附录

其实好像大部分问题都能在官方说明里找到,只不过遇到的问题可能在文档里没有说那么细,但仔细查找下 issue 也是能找到对应的解决方案。

下面简单写个使用的例子吧

const pdfjsLib = require("pdfjs-dist/legacy/build/pdf.min.js");
pdfjsLib.GlobalWorkerOptions.workerSrc = (typeof window !== "undefined"
  ? window
  : {}
).pdfjsWorker = require("pdfjs-dist/legacy/build/pdf.worker.min.js");

function init(data) {
  // data可以为url,也可以为服务端返回的 arrayBuffer 或服务端返回的 base64,前端转的 arrayBuffer
  try {
    const loadingTask = pdfjsLib.getDocument(data);
    this.pdf = await loadingTask.promise;
    this.pageNum = await this.pdf.numPages || 0;
    console.log('pageNum', this.pageNum)
    this.initCanvas();
    this.renderPdf(1);
  } catch (err) {
    this.onError({
      message: "pdf 文件加载失败",
      code: 1
    })
  }
}

function renderPdf(num = 1) {
    this.pdf.getPage(num).then((page) => {
      // 设置canvas相关的属性
      const canvas = document.getElementById("pdfCanvas_" + num);
      const ctx = canvas.getContext("2d", {
        alpha: false
      });
      const dpr = window.devicePixelRatio || 1;
      const bsr =
        ctx.webkitBackingStorePixelRatio ||
        ctx.mozBackingStorePixelRatio ||
        ctx.msBackingStorePixelRatio ||
        ctx.oBackingStorePixelRatio ||
        ctx.backingStorePixelRatio ||
        1;
      const ratio = dpr / bsr;
      const viewport = page.getViewport({ scale: this.pdfScale * ratio }); // 设置缩放比率
      const viewRatio = viewport.height / viewport.width;
      canvas.width = viewport.width * ratio;
      canvas.height = viewport.height * ratio;
      canvas.style.width = (this.container.clientWidth - 20) * this.pdfScale + "px";
      canvas.style.height = (this.container.clientWidth - 20) * viewRatio * this.pdfScale + "px";
      ctx.setTransform(ratio, 0, 0, ratio, 0, 0);
      const renderContext = {
        canvasContext: ctx,
        viewport: viewport
      };
      // 数据渲染到canvas画布上
      page.render(renderContext);
      if (this.pageNum > num) {
        setTimeout(() => {
          return this.renderPdf(num + 1);
        });
      }
    });
  }