改造PDF.js源码,实现审批系统对PDF阅读器的集成(五)—— 定制化源码的正确打开方式

1,702 阅读8分钟

有不少朋友对我开放出来的源码表示感兴趣,并做了一些调试,还有一些朋友直接整合到自己的业务系统里去使用了。在这个过程中也向我提出了不少问题,包括如何使用源码、如何调用一些API、如何打包程序等等。因此,我们写了这篇博客来详细的讲解一下如何使用我定制化开发过的PDF.js源码。

下载、安装依赖与调试和打包

针对PDF.js做的二次开发,我已经将源码开放了出来,具体位置在:

https://gitee.com/gu_yongqiang/pdf.js-4.0.379

下载、安装依赖

首先我们需要将代码clone到本地来:

git clone https://gitee.com/gu_yongqiang/pdf.js-4.0.379.git

然后检查一下node.js的版本是否满足要求,我使用的版本可以正常打包,具体版本信息如下:

E:\pdf.js-4.0.379>node -v
v18.20.4

紧接着我们需要使用npm进行安装。不过在安装之前,我们需要先将npm的镜像设置成国内的镜像,避免依赖拉不下来。

npm config set registry https://registry.npmmirror.com

当然,光是设置好基础的镜像其实还是不行的。因为pdf.js还用到了一些别的组件,纵使我们设置完基本的镜像以后,这些组件依旧会从国外的网站上下载依赖,而不受我们配置镜像的影响。这就会导致npm install在进行到某些环节的时候,会卡着不动。

不过该问题已经被解决。我在根目录增加了一个.npmrc文件,用来解决core-js无法下载的问题。具体内容如下:

# 解决 core-js无法下载编译的问题
canvas_binary_host_mirror=https://registry.npmmirror.com/-/binary/canvas

上述的准备工作完毕后,直接使用npm install就可以进行安装依赖了:

npm install

我本地下载和编译完全部依赖,大约需要3-10分钟。其中绝大部分时间是在处理core-js。下载完,控制台具体提示如下:

E:\pdf.js-4.0.379>npm install
npm warn deprecated urix@0.1.0: Please see https://github.com/lydell/urix#deprecated
......
npm warn deprecated core-js@2.6.9: core-js@<3.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Please, upgrade your dependencies to the actual version of core-js.

> postinstall
> cross-env PUPPETEER_PRODUCT=firefox node node_modules/puppeteer/install.mjs

Firefox Nightly (131.0a1) downloaded to C:\Users\Administrator\.cache\puppeteer\firefox\win64-131.0a1

added 1551 packages in 9m

211 packages are looking for funding
  run `npm fund` for details

本地调试代码的方式

下载完项目所需的依赖后,使用以下命令就可以在本地启动调试(需要用到gulp,如果没有gulp可能需要使用npm install gulp -g安装一下):

gulp proxyServerX

启动完毕后,就可以在浏览器输入http://127.0.0.1:8888/web/viewer.html来进行访问了。

打包PDF.js源码与使用http-server访问

打包PDF.js源码较为简单,使用以下命令即可实现:

gulp generic

打包结束后,根目录下会多一个build目录,而build目录下又有一个generic目录,该目录就是打包后的文件。

我们进入/build/generic目录后,在该目录下启动一个http-server(如果没有的话使用npm install http-server -g安装即可):

E:\pdf.js-4.0.379\build\generic>http-server

Starting up http-server, serving ./
....
Available on:
  http://192.168.0.101:8080
  http://127.0.0.1:8080

打包结束后,访问http://127.0.0.1:8080/web/viewer.html即可访问PDF.js阅读器。

暴露出来可供调用的API

我对PDF.js的改动有两方面,一方面是直接深入了PDF的源码,通过改动一些源码的实现方式,加强了PDF.js的一些功能。另一方面则是编写了一些扩展代码。这些扩展代码能够将业务系统和PDF.js整合起来。

两个重要的二次开发的文件

为了实现对PDF.js的扩展和对业务系统的整合,我编写了两个比较重要的文件。分别是/web/custom.js/web/custom_util.js。这两个文件由于是我自己开发的,相较于PDF本身的源码,更符合国内的代码开发风格,因此也更容易阅读。这两个文件暴露了一些API,供开发者调用,从而使得开发者能够实现更好地对PDF阅读器的控制。

其中暴露的方法包括选中选中批注跳转批注展示批注隐藏批注删除批注启动时自动初始化批注启动时自动跳转批注增删改批注时的回调。这些API对于一些要整合PDF阅读器的场景已经足够使用了。

下面详细的介绍各个暴露出来的API实现方式以及调用方式。

选中批注

选中批注实现相对简单,直接调用custom_util#selectEditor即可,该方法调用了批注的选中方法。

跳转批注

跳转批注,通过计算批注所在的PDF页面的位置,以及批注相对于其所在的页面的位置,得到一个相对于body的高度,然后将PDF.js scroll到这个高度。就可以实现批注的跳转了。直接调用EditorDisplayController#jump方法即可跳转到指定批注。

隐藏批注

隐藏批注实际上是通过先移除批注,但是保留批注的参数来实现的,实现原理较为简单。直接调用EditorDisplayController#hide方法就可以将某个批注隐藏起来。隐藏批注不会触发批注的钩子事件。

展示批注

展示批注实际上就是根据批注的参数,重新构造一个批注,实现原理较为简单。直接调用EditorDisplayController#show就可以实现。展示批注不会触发钩子的批注事件。

删除批注

删除批注就是直接移除批注,直接调用EditorDisplayController#remove即可实现。参数有两个,分别是批注id是否触发钩子。如果触发钩子的话,会执行回调结果。

EditorDisplayController

该控制器是用来控制上述五种对批注跳转的方法的。默认挂在了window.annotationEditorController下。具体的代码可以再custom.jscustom_util.js两个文件中可以查看。

启动时自动初始化批注

如果一个文档已经有若干批注了,并且需要在PDF启动的时候,对这些批注进行初始化处理,那么开发者可以在定义一下window.initPdfDocumentAnnos方法,在viewer.html里我已经给出了一个示例。

custom.js中,二次开发的代码增加了两个监听器。其中一个监听器会在PDF.js的UI管理器启动完成的时候,执行一些方法,还有一个会在某一个PDF页面加载完毕的时候,执行某些方法。通过这种方式,可以实现启动时自动初始化一批批注。

具体代码如下:

const initCustom = () => {
  getEventBus().on("annotationeditoruimanager", function () {
    ...
    const params = window.initPdfDocumentAnnos();
    ...
    controller.renderPreparedLayerAnnotations(editorManager.map);
  });

  getEventBus().on("annotationeditorlayerrendered", function (e) {
      ....
      // pageNumber要转换成layer的下标,要减1
      controller.renderPreparedLayerAnnotations(
        editorManager.map,
        e.pageNumber - 1
      );
    });
}

annotationeditoruimanager事件在UI管理器初始化完成的时候,执行一段代码。这段代码会调用window.initPdfDocumentAnnos方法,获取所有的默认批注,并将这些批注加入到批注管理器当中去。但是此时并不渲染,因为PDF.js使用的是懒加载的方式,因此批注需要等到对应页面渲染完成的时候,才会进行加载。

annotationeditorlayerrendered在某一个页面渲染完成的时候,执行一段代码。通过监听这个事件,我们可以实现,在某一个页面加载完毕后的时候,将这个页面的批注同步渲染出来。

启动结束时的操作——自动跳转到某个批注

在PDF阅读器启动完毕后,可能我们还需要执行一些操作。这些操作可以放在window.afterDocumentLoaded这个方法里,我们可能会使用这个方法来控制PDF阅读器在启动结束后跳转到某个批注,或者进行一些其它操作。总之,将阅读器加载完毕后(此时页面未必渲染完成)想要执行的代码,放在这个方法里,等到相应的时间点,方法就会被执行。

增删改批注时的回调

我通过大量修改PDF.js的源码,实现了对批注增加了一些钩子。这些钩子能够监听批注的变化。具体代码在custom.js中,具体内容如下。

function saveAnnotationToDiscFunc() {
  return window.saveAnnotationToDisc;
}

class EditorLifecycleInterceptor {
  // editor展示在页面上之后,要做的事
  postEditorShow(params) {
    const func = saveAnnotationToDiscFunc();
    func("add", params);
  }

  // editor在页面上经过变化之后,要做的事
  postEditorModify(params) {
    const func = saveAnnotationToDiscFunc();
    func("modi", params);
  }

  // editor在页面上消失之前要做的事
  postEditorRemove(params) {
    const func = saveAnnotationToDiscFunc();
    func("del", params);
  }
}

通过上述的代码可知,在批注发生变化的时候,我们获取了window.saveAnnotationToDisc方法,并对该方法进行调用。具体的参数有两个,一个是处理的类型。分别有三种,增加批注的add、修改批注的modi、删除批注的del。第二个参数则是批注的参数信息。通过这个钩子,开发者可以在批注发生变化的时候,及时将批注的信息同步到数据库当中去。并在后续PDF阅读器打开的时候,再将这些批注从后台拿出来,实现对批注一个完整的管理。