前端生成PDF,讓後端刮目相看

葡萄城技術團隊發表於2022-02-23

PDF 簡介

PDF 全稱Portable Document Format (PDF)(便攜文件格式),該格式的顯示與作業系統、解析度、裝置等因素沒有關係,不論是在Windows,Unix還是在蘋果公司的Mac OS作業系統中PDF格式都通用。Adobe公司在1993年為了文件傳輸創造了這個檔案格式,這個格式使用PostScript頁面描述語言,適用於列印影像和文字(無論是在紙、膠片或非物質的CRT都可)。PDF是基於頁面描述語言。它既可以像程式程式碼一樣具有可讀性,又能表示出可任意放大和縮小的向量圖。

PDF檔案格式可以將文字、字型、格式、顏色及獨立於裝置和解析度的圖形影像等封裝在一個檔案中,該格式檔案還可以包含超文字連結、聲音和動態影像等電子資訊,支援特長檔案,整合度和安全可靠性都較高。

為什麼PDF 檔案能夠如此盛行

很多人所吐槽,說PDF 既不能編輯,也不好複製內容,更無法直接轉換成Word,為什麼要用PDF來傳輸資料呢?

殊不知,大家吐槽的缺點,正是因為它優點的過於強大而引起的。

PDF的產生之初的目的,是為了適應紙媒的印刷行業。PDF 原本並非為小螢幕電子閱讀設計的檔案標準,它來自於印刷——基於紙張大小進行的排版。我們可以把它當成紙質文稿的電子化,非電子文字,而是電子化的印刷了東西的紙張。它存在的目的是為了實現批量精準的印刷,保證在多個螢幕,多個系統,多終端中檔案格式都能儲存相對位置,展示佈局都不會出現格式錯亂,保證了列印到紙張上的格式完全一致,而不會內容格式面目而非。

試想,如果我們需要列印一份保險認購書,保險業務人員使用 iPad 列印的PDF 檔案和使用PC 電腦列印出來的檔案格式相差很大,頁數不一致,換行不一致,那到底如何保證保險認購書的法律效應呢。 一份保單可以有多種格式,那就無法信任任何一份保單了。正如你面前有多個時鐘,我們也就無法獲取當前準確時間。

如果你實現過類似於列印頁面,列印表單等功能,你可能會深有體會這其中的坑,吃過的苦只有自己清楚。

因為將網頁儲存為PDF 讓使用者預覽或下載不失為一種保證格式在各終端一致的好方法。

除此之外,PDF 的優勢除了跨平臺,相容性高,也 最大程度降低了檢視成本 ,終端使用者不需要安裝一套沉重全功能的Adobe才能讀到 PDF檔案,只要客戶機器上有瀏覽器就可以檢視PDF內容。這也就是終端使用者無論是手機端 iOS, Android,還是老的PC,新的PC機器都可以隨時隨地開啟PDF 檔案,支援閱讀的方式非常多樣便捷,而不是像Excel檔案必須要office才能夠讀取。

再加上PDF 也可以進行小範圍的編輯,安全屬性的設定,如加密,加密列印等功能,實用性也是上升到另一個層次。

前端生成PDF 檔案應用場景

隨著移動網際網路的發展,手機端增長需求暴增,網際網路系統越多越多,新型系統都是為了更方便快捷解決使用者而應用而生的,而使用者需求也隨著技術的發展悄然發生改變。

"全民皆網民"的階段,再不是基本功能滿足就可以站住腳的時代,使用者體驗及互動需求更加迫切,使得從機器時代的設計到人性化的設計,更加易用性。

前後端分離的技術架構暢行,讓專業的人做專業的事情,開發更加高效暢通,因此在前端生成和展示PDF檔案的需求也是比較普遍,我們總結一下PDF的常見應用場景:

  1. 專案中預覽PDF 檔案,並且提供搜尋能力
  2. 手機端預覽PDF 檔案
  3. 使用者填寫表單,生成PDF 檔案,使用者直接下載儲存
  4. 線上生成PDF 合同,列印

簡單總結生成 PDF 的三類需求:

  1. 線上預覽,直接開啟現有的PDF檔案進行瀏覽確認資訊。
  2. 實現線上生成PDF檔案,根據使用者的上下文資訊,如新提交的表單資訊,客戶資訊,採購資訊等即時生成個性化的PDF檔案,供使用者檢視或下載。
  3. 列印,將已有或已生成的PDF 檔案直接列印。

在前端生成PDF 檔案是非常普遍的需求,幾乎業務複雜的系統都會有這樣的要求。

前端生成PDF 檔案難點

前端生成PDF檔案的難點在於,前端純依賴於客戶端的瀏覽器資源,可用的資源有限制,終端多樣性,導致生成PDF 難度也比服務端增加了不少。以ActiveReportsJS前端報表控制元件為示例,它提供了前端的PDF 匯出能力,但在匯出PDF 檔案之前,我們需要注意以下幾個問題:

  • ActiveReportsJS元件是前端控制元件,整體執行都基於Web瀏覽器環境來執行。
  • 桌面報表設計器 是基於 Electron使用Chromium來顯示使用者介面。
  • Web 線上設計器 和 報表 viewer 元件在使用者計算機的瀏覽器中執行的 Web 應用程式。
  • PDF, Excel 和 HTML 作為生成器,基於瀏覽器環境來測量並生成報表內容。
  • 報表由文字內容組成,瀏覽器通過基於glyphs(字形)來渲染的字型形狀。字型資源包含將字元編碼對映到代表這些字元的字形的資訊。因此,瀏覽器需要訪問正確的字型資源,才能夠按照預期顯示文字。

因此在前端生成PDF有三座大山需要克服:

  • 瀏覽器。瀏覽器可謂百家齊鳴,不過現在的主流瀏覽器數量也還好,不過三四家而已,如Chrome, FireFox,Safari,Edge,瀏覽器,當然還有國內稱霸的360瀏覽器。每個瀏覽器對於文字內容,甚至CSS 屬性處理都不一致,而正因為各家有各家的標準,會出現我們在Chrome中可以正常使用所有功能,而火狐使用PDF時,內容無法正常顯示,但列印功能正常。
  • 解析度。如果要列出天下所有的解析度,恐怕一張A3紙都無法完全輸出了,如果基於Dom 渲染的網頁,遇到解析度差異大的終端,那麼放大縮小的問題完全無法解決。
  • 字型。英文和數字等Unicode字元都可以保證PDF 正常顯示,但如果頁面中包含中文字元,在生成PDF 時是基於字形繪製的,如果提供的字形與實際頁面展示的字形不一致,那導致生成PDF並不是所見即所得的效果,可能對於一些格式要求比較嚴格的檔案,精確到換行字元,行數,邊距等都會是災難性問題,因此提供正確的字型也是PDF生成時,保證格式一致是最重要的一點。

常用的前端生成PDF 檔案方法

方法一

html2canvas+ jsPdf的方法將HTML 轉換成圖片後,在將圖轉PDF檔案

適用場景:適用單頁PDF檔案,且終端裝置一致

示例程式碼:

HTML:

<html>

  <body>
    <header>This is the header</header>
    <div id="content">
      This is the element you only want to capture
    </div>
    <button id="print">Download Pdf</button>
    <footer>This is the footer</footer>
  </body>

</html>


CSS:

body {
  background: beige;
}

header {
  background: red;
}

footer {
  background: blue;
}

#content {
  background: yellow;
  width: 70%;
  height: 100px;
  margin: 50px auto;
  border: 1px solid orange;
  padding: 20px;
}


JS:

$('#print').click(function() {

  var w = document.getElementById("content").offsetWidth;
  var h = document.getElementById("content").offsetHeight;
  html2canvas(document.getElementById("content"), {
    dpi: 300, // Set to 300 DPI
    scale: 3, // Adjusts your resolution
    onrendered: function(canvas) {
      var img = canvas.toDataURL("image/jpeg", 1);
      var doc = new jsPDF('L', 'px', [w, h]);
      doc.addImage(img, 'JPEG', 0, 0, w, h);
      doc.save('sample-file.pdf');
    }
  });
})



缺點:

  • 生成的PDF檔案由圖片構成,內容無法拷貝,放大後不清晰
  • 分頁列印位置無法控制

方法二

jsPDF 直接基於Dom物件生成PDF 檔案

jsPDF,支援新增頁碼

適用場景: 適合簡單的頁面佈局,如常規的二維表,但複雜的報表樣式定義Dom元素,使用起來就異常複雜了。

<script>
    function demoFromHTML() {
        var pdf = new jsPDF('p', 'pt', 'letter');
        // source can be HTML-formatted string, or a reference
        // to an actual DOM element from which the text will be scraped.
        source = $('#content')[0];

        // we support special element handlers. Register them with jQuery-style 
        // ID selector for either ID or node name. ("#iAmID", "div", "span" etc.)
        // There is no support for any other type of selectors 
        // (class, of compound) at this time.
        specialElementHandlers = {
            // element with id of "bypass" - jQuery style selector
            '#bypassme': function (element, renderer) {
                // true = "handled elsewhere, bypass text extraction"
                return true
            }
        };
        margins = {
            top: 80,
            bottom: 60,
            left: 40,
            width: 522
        };
        // all coords and widths are in jsPDF instance's declared units
        // 'inches' in this case
        pdf.fromHTML(
        source, // HTML string or DOM elem ref.
        margins.left, // x coord
        margins.top, { // y coord
            'width': margins.width, // max width of content on PDF
            'elementHandlers': specialElementHandlers
        },

        function (dispose) {
            // dispose: object with X, Y of the last line add to the PDF 
            //          this allow the insertion of new lines after html
            pdf.save('Test.pdf');
        }, margins);
    }
 </script>

缺點:

  • 多平臺之間展示有差異,如手機端展示的Dom結構和電腦端佈局有很大不同
  • 對中日韓文的字型支援不佳,會出現亂碼
  • 佈局在不同瀏覽器中有差異

方法三

使用 ActiveReportsJS直接線上設計佈局,並直接生成PDF 檔案

優點: 簡單易用,視覺化操作,所見即所得,程式碼量少,適用於多平臺,保證PC端,Web端,手機端三端一致。

缺點:需要配相應字型,能夠滿足精準生成PDF 的需求。適用於保險業,金融業,檢測業等對於PDF檔案格式要求嚴格的的行業。

字型資訊通常包含:

  • 字型名稱: 字型ID 如 Arial, Calibri, 或 Times New Roman
  • 字型樣式: 正常 或 斜體
  • 字型粗細: 較細,細體,正常,適中,粗體,較粗
  • 字型系列通常由多個字型組成,通常由單獨的檔案表示。

接下來我們一起來看看具體實現過程。

在報表Viewer中顯示報表,將報表匯出為PDF或託管報表設計器元件的應用程式應使用與為獨立設計器應用程式建立的配置相同的配置。 最簡單的方式是複製Fonts 資料夾和 fontsConfig.json 檔案到專案的 assets 資料夾下面. 此資料夾因不同的前端框架而異。 示例如下:

RegisterFonts 方法是個非同步函式,並會返回 Promise 物件。 也可以呼叫此方法的程式碼可以等待,直到返回Promise結果後,再在檢視器元件中載入報表或匯出報表。

{
    "name": "Montserrat",
    "weight": "900",
    "style": "italic",
    "source": "assets/Fonts/Montserrat/Montserrat-BlackItalic.ttf"
}  

<script src="https://cdn.grapecity.com/activereportsjs/2.latest/dist/ar-js-core.js"></script>
<script>
  GC.ActiveReports.Core.FontStore.registerFonts(
    "/resources/fontsConfig.json" // replace the URL with the actual one
  )
</script>  


var pageReport = new ARJS.PageReport();
            pageReport.load('Quotation.rdlx-json')
                .then(function() { return pageReport.run() })
                .then(function(pageDocument) { return PDF.exportDocument(pageDocument, settings) })
                .then(function(result) { result.download('arjs-pdf') });


HTML 展示效果圖:

PDF 展示效果圖:

參考示例:https://demo.grapecity.com.cn/activereportsjs/demos/api/export/purejs

本文為大家介紹了三種不同方式實現了各種PDF列印的方式,後續還會為大家帶來更多有趣的內容~覺得不錯點個贊再走吧

相關文章