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

2022-02-23 13:45:48 +08:00
 GrapeCityChina

为什么 PDF 文件能够如此盛行

很多人所吐槽,说 PDF 既不能编辑,也不好复制内容,更无法直接转换成 Word ,为什么要用 PDF 来传输资料呢?

殊不知,大家吐槽的缺点,正是因为它优点的过于强大而引起的。

PDF 的产生之初的目的,是为了适应纸媒的印刷行业。PDF 原本并非为小屏幕电子阅读设计的文件标准,它来自于印刷——基于纸张大小进行的排版。我们可以把它当成纸质文稿的电子化,非电子文本,而是电子化的印刷了东西的纸张。它存在的目的是为了实现批量精准的印刷,保证在多个屏幕,多个系统,多终端中文件格式都能保存相对位置,展示布局都不会出现格式错乱,保证了打印到纸张上的格式完全一致,而不会内容格式面目而非。

试想,如果我们需要打印一份保险认购书,保险业务人员使用打印的 PDF 文件和使用 PC 电脑打印出来的文件格式相差很大,页数不一致,换行不一致,那到底如何保证保险认购书的法律效应呢。 一份保单可以有多种格式,那就无法信任任何一份保单了。正如你面前有多个时钟,我们也就无法获取当前准确时间。

如果你实现过类似于打印页面,打印表单等功能,你可能会深有体会这其中的坑,吃过的苦只有自己清楚。

因为将网页保存为 PDF 让用户预览或下载不失为一种保证格式在各终端一致的好方法。

除此之外,PDF 的优势除了跨平台,兼容性高,也 最大程度降低了查看成本 ,终端用户不需要安装一套沉重全功能的 Adobe 才能读到 PDF 文件,只要客户机器上有浏览器就可以查看 PDF 内容。这也就是终端用户无论是手机端 iOS, Android ,还是老的 PC ,新的 PC 机器都可以随时随地打开 PDF 文件,支持阅读的方式非常多样便捷。

再加上 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 文件之前,我们需要注意以下几个问题:

因此在前端生成 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');
    }
  });
})



缺点:

方法二

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>

缺点:

方法三

使用 ActiveReportsJS直接在线设计布局,并直接生成 PDF 文件

优点: 简单易用,可视化操作,所见即所得,代码量少,适用于多平台,保证 PC 端,Web 端,手机端三端一致。

缺点:需要配相应字体,能够满足精准生成 PDF 的需求。适用于保险业,金融业,检测业等对于 PDF 文件格式要求严格的的行业。

字体信息通常包含:

接下来我们一起来看看具体实现过程。

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

1194 次点击
所在节点    推广
1 条回复
jjshare123
2022-02-27 03:23:58 +08:00
牛,很好的分享

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/835904

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX