關于使用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 的方案。說實話電子合同這塊的坑蠻多的!后面會抽空把合同這塊踩過的一些坑寫一下。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 斗雞 憶斗雞 一 瘋狂歲月 在鄉下看見這雄糾糾的大公雞,恩緒一下子給拉到十幾年前那個賭斗雞的時候。...
    長嘯若爾蓋閱讀 280評論 0 1
  • 如果有一天,你從很高的地方墜落該怎么辦。就像從水晶鞋上崴了腳的灰姑娘,沒有安慰沒有同情,只是冰冷的嘲笑和白眼。你還...
    梵baba閱讀 237評論 0 1