关于使用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。一旦建立,您可以使用对象的方法或使用标准数组索引语法(即使用括号表示法)引用数组中的元素。详细的可以参考 官方文档

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/pdfjs-dist@2.0.943/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/pdfjs-dist@2.0.943/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/pdfjs-dist@2.0.943/cmaps/';
            pdfjsLib.getDocument({data: this.getUint8Array,cMapUrl: CMAP_URL,cMapPacked: true,})
                .then(pdf => {
                    that.pageCount = pdf.numPages;
                    for(var i=1;i<pdf.numPages;i++){
                        pdf.getPage(i).then(page => {
                            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 的方案。说实话电子合同这块的坑蛮多的!后面会抽空把合同这块踩过的一些坑写一下。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容

  • 斗鸡 忆斗鸡 一 疯狂岁月 在乡下看见这雄纠纠的大公鸡,恩绪一下子给拉到十几年前那个赌斗鸡的时候。...
    长啸若尔盖阅读 237评论 0 1
  • 如果有一天,你从很高的地方坠落该怎么办。就像从水晶鞋上崴了脚的灰姑娘,没有安慰没有同情,只是冰冷的嘲笑和白眼。你还...
    梵baba阅读 218评论 0 1