4

前端生成PDF,让后端刮目相看

 2 years ago
source link: https://segmentfault.com/a/1190000041447795
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

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打印的方式,后续还会为大家带来更多有趣的内容~觉得不错点个赞再走吧


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK