关于使用pdfjs预览PDF文件

【背景】

昨天财务的同学过来核对新的退款流程,顺便提了下之前开发的电子发票的项目,说上传电子发票PDF文件后,打开无法预览。起初以为是网络的原因,因为项目在当时开发完测试的时候是出现过这种问题的,由于这个问题是偶发性的,当时公司的网络经常出现问题,因此测试同学也就当成网络问题处理了。现在跟财务同学沟通以后,才知道原来已经不能用好几个月了。说只是公司内部的系统不能查看,在用户那边是正常的,所以就没有通知我们修复了。那怎么行,程序员的眼里怎么能容下bug。其实当时这块由于时间关系当时开发的并不理想,后面也一直没有抽出时间来改。

【需求】

先大致讲一下当时的需求吧。原需求是,财务人员导入电子发票后,可通过点击已经导入的电子发票,像图片一样展示 pdf 文件。而用户在B端查看电子发票时是会下载那个PDF文件的,所以也是财务同学说的公司内部系统无法查看,用户那边不影响的结果。

【解决方案】

后台去腾讯云拿到 pdf 文件然后经过 base64 处理后返回给前端,前端通过使用 pdfjs 将 pdf 文件显示出来。这个方案当时在做这个项目的时候就在网上找到了,后面因为 pdfjs 这个插件用起来有点麻烦,当时找到一个 vue-show-pdf 的插件可以直接用,但其实效果不怎么好,只是时间赶,而且是公司内部系统,只有财务人员能够使用,所以就粗糙的上了。

说了一大堆废话,下面说下具体实现的过程以及过程中遇到的一些问题。

首先,pdfjs 在网上找到的其实大多数都是使用 url 去显示的。通过 url 显示 pdf 的话这个比较简单,网上也很多。但是因为我们文件资源是存放在腾讯云的,涉及到前端跨域的问题。因此是由后端直接去腾讯云拿到 pdf 文件通过 base64 处理后返回给前端。前端拿到 base64 字符串后。是不能直接放到 pdfjs 中使用的,但是在pdfjs 的官方文档中,有提到使用 base64 的方法。


image.png

因此,pdfjs,实际上是支持传入 经过 base64 处理的字符串,重点就是这个 as an array。

 /**
   * 函数名:getUint8Array
   * 简介:将base64 格式的字符串转换成 uint8Array (pdf.js 无法直接接受base64 格式的参数)
   * 参数:base64_string(pdf格式的电子发票经过base64处理的字符串)
   * return:Array
   */
getUint8Array(base64Str){
      let data = base64Str.replace(/[\n\r]/g, '');  // 替换多余的空格和换行
      var raw = window.atob(data);
      var rawLength = raw.length;
            var array = new Uint8Array(new ArrayBuffer(rawLength));
            for (var i = 0; i < rawLength; i++) {
                array[i] = raw.charCodeAt(i)
            }
        return array
  },

这里涉及到使用 Uint8ArrayArrayBufferUint8Array 类型数组表示的8位无符号整数数组。内容初始化为0。一旦建立,您可以使用对象的方法或使用标准数组索引语法(即使用括号表示法)引用数组中的元素。详细的可以参考 官方文档

关于使用pdfjs预览PDF文件_第1张图片
image.png

ArrayBuffer 类型化数组,类型化数组是JavaScript操作二进制数据的一个接口。最初为了满足JavaScript与显卡之间大量的、实时的数据交换,它们之间的数据通信必须是二进制的,而不能是传统的文本格式的背景下诞生的。

charCodeAt() 方法可返回指定位置的字符的 Unicode 编码。这个返回值是 0 - 65535 之间的整数。

将返回的base64 字符串,传入getUint8Array 方法中,返回一个 array ,将这个 array 交由 pdfjs 调用。

/*将解码后的值传给PDFJS.getDocument(),交给pdf.js处理*/
        showPdfFile(data) {
            let pdfView = this.$refs.pdf;
            pdfView.innerHTML = "";
            const CMAP_URL = 'https://cdn.jsdelivr.net/npm/[email protected]/cmaps/';
            PDFJS.getDocument({data: data ,cMapUrl: CMAP_URL,cMapPacked: true}).then(pdf => {
                pdf.getPage(1).then(page => {
                    let scale = 1.5;    // 默认1.5倍缩放
                    let viewport = page.getViewport(scale);
                    if(viewport.height > window.screen.height){
                        scale = (window.screen.height / viewport.height).toFixed(1);
                        viewport = page.getViewport(scale);
                    }
                    let canvas = document.createElement('canvas');
                    let canvasContext = canvas.getContext('2d');
                    canvas.width = viewport.width;
                    canvas.height = viewport.height;
                    pdfView.appendChild(canvas);
                    // 将页面呈现到画布上
                    let renderContext = {
                        canvasContext: canvasContext,
                        viewport: viewport
                    }
                    page.render(renderContext);
                    this.isShowPDF = true;
                },err => {
                    // PDF loading error
                    console.error(err);
                });
            });
        },

这里有几个地方需要注意一下:

  • 电子发票上传后,点击预览发现 pdf 中的中文字符不现实了。网上查询了一下,是因为中文的编码问题,引入pdfjs 的编码文件。在使用的时候传入即可
  const CMAP_URL = 'https://cdn.jsdelivr.net/npm/[email protected]/cmaps/';
  PDFJS.getDocument({data: data ,cMapUrl: CMAP_URL,cMapPacked: true}).then()
  • 电子发票字体有些模糊不清。尝试调整了缩放比例,可以满足需求。网上有些提到使用了canvas 导致的字体很不清晰的问题,在电子发票上能够清晰显示,之前在做电子合同项目的时候,尝试了相同的方法,文字比较多还涉及了表格,不过效果还行。
  let scale = 1.5;    // 默认1.5倍缩放
  let viewport = page.getViewport(scale);
  if(viewport.height > window.screen.height) {
          scale = (window.screen.height / viewport.height).toFixed(1);
          viewport = page.getViewport(scale);
  }

在这里做了一些兼容处理,因为之前的测试数据上传的是一个随便找的 pdf 文件,是高大于宽的,在1.5倍缩放的情况下,超出了屏幕的显示范围,且无法滚动的情况,所以在这稍微做了下简单的处理,改变了一下缩放的比例,保证不会超出屏幕的显示,因为这里的功能主要是预览导入的电子发票,尺寸一般都是差不多的,所以没有做比较复杂的处理了。

另外,因为跟财务同学确认过,发票都是单张的,所以没有做分页的处理了。之前在做电子合同的项目的时候,做过分页的处理,直接贴个代码参考下吧

      showPdf(){
            let pdfView = document.getElementById('pdf-view');
            const CMAP_URL = 'https://cdn.jsdelivr.net/npm/[email protected]/cmaps/';
            pdfjsLib.getDocument({data: this.getUint8Array,cMapUrl: CMAP_URL,cMapPacked: true,})
                .then(pdf => {
                    that.pageCount = pdf.numPages;
                    for(var i=1;i {
                            let scale = 1.0;
                            let viewport = page.getViewport(scale);
                            let canvas = document.createElement('canvas');
                            let canvasContext = canvas.getContext('2d');
                            canvas.width = viewport.width;
                            canvas.height = viewport.width * 841.229/ 592.28;
                            pdfView.appendChild(canvas);
                            // 将页面呈现到画布上
                            let renderContext = {
                                canvasContext: canvasContext,
                                viewport: viewport
                            }
                            page.render(renderContext);
                        },err => {
                            // PDF loading error
                            console.error(err);
                        });
                    }
                });
        },

合同这里因为是按A4纸规格设计的,所以做了尺寸的处理。

    canvas.width = viewport.width;
    canvas.height = viewport.width * 841.229 / 592.28;

最后因为各种原因,合同这块涉及打印的一些问题,并没有采用 pdfjs 的方案。说实话电子合同这块的坑蛮多的!后面会抽空把合同这块踩过的一些坑写一下。

你可能感兴趣的:(关于使用pdfjs预览PDF文件)