V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
GrapeCityChina
V2EX  ›  推广

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

  •  2
     
  •   GrapeCityChina · 2022-02-23 13:45:48 +08:00 · 1392 次点击
    这是一个创建于 1006 天前的主题,其中的信息可能已经有所发展或是发生改变。

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

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

    jjshare123
        1
    jjshare123  
       2022-02-27 03:23:58 +08:00
    牛,很好的分享
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3763 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 28ms · UTC 04:16 · PVG 12:16 · LAX 20:16 · JFK 23:16
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.