当前位置:Java -> 一个解决企业开发痛点的PDF框架

一个解决企业开发痛点的PDF框架

什么是南湖打印-Java?

Nanhu-print-java是一个用Java语言实现的PDF生成框架。

用户可以准备JSON格式的业务数据和XML格式的配置文件,然后调用nanhu-print-java框架API来完成生成PDF文件的过程。

Nanhu-print-java的GitHub地址可以在这里找到。

这里有一个开发视频

南湖打印-Java的诞生背景

我所在的公司想要实现自定义打印功能,需要满足以下功能:

  1. 定义一些模板。对于每个模板的每个字段,标签可以自定义、显示或隐藏,表格的每一列的宽度可以自定义,用户可以在浏览器端选择这些模板的字段。
  2. 模板的页眉。表格的页眉需要在每一页上显示,并且需要固定的信息,如金额汇总和签名,需要固定在页面底部。

公司在开始阶段准备了两种技术方案:

  1. 前后端分别实现。前端使用JS实现页面和控制页面字段,后端使用Java调用iText pdf库生成pdf进行打印。
  2. 前端实现几乎所有功能。在前端几乎实现页面后,JS将生成的HTML发送到后端,后端使用Java调用HTML-to-PDF框架生成PDF进行打印。

值得考虑的是,页面上每个字段都要做复杂的显示和隐藏操作,而直接通过Java开发接口进行界面显示是非常麻烦的。开发完成后,需求变更、字段增减都要涉及程序修改,代码维护难度较大。因此,第一种方案被否定。

因此,公司选择了第二种方案,使用JS在前端生成HTML,然后发送到后端进行HTML转PDF。

在实施过程中,遇到了难以解决的问题:

  1. 前端HTML和后端生成的PDF在字体、页面样式等方面存在明显差异。对于较长的文档,在文档内部存在较长的表格时,打印时会出现在多个页面上。前端的JS代码根据定义的高度生成div,并逐一添加页面内容,当内容超过一页时,重新生成div并添加内容。div数组形成多个页面。将这些多个页面发送到后端后,使用HTML转PDF框架生成PDF,发现前端显示的HTML页面由于前端窗口和后端Linux环境的字体渲染差异,以及前端窗口环境的浏览器与后端环境中的html-to-pdf框架对CSS样式的解析不同,导致后端生成的PDF出现页面内容过小或过大的问题,打印效果不好。例如,一个A4页,前端HTML页面填满整个div,显示非常美观,但是生成pdf后,由于字体大小较小或框架转换等问题,生成的pdf页面内容非常小,整个pdf页面底部存在大量空白。由于HTML转PDF的不透明性,这个问题难以解决。
  2. 页面上存在复杂的JS控制显示和隐藏,页面代码难以维护。例如,当表格的某一列调整列宽时,此列的文字可能会换行。这时,原本在表格末尾的内容会被推出页面边界,需要触发页面元素的高度控制代码。经过一段失败的开发和测试,作者开发了“南湖打印-Java”框架,比较顺利地解决了公司遇到的问题。南湖打印-Java定义了XML格式文件,其中既有if、forEach、set等动态标签,也有table、div、span等静态标签,限制用户只能使用这些标签。在前端上通过南湖打印-JS,解析XML生成HTML,显示在浏览器中。在后端上通过解析XML生成PDF,使得PDF的字体等样式不受前端环境的影响,较好地解决了前端显示和后端打印之间的差异问题。用户开发过程主要是在XML格式中配置文件,基本上没有复杂的代码控制,也达到了大大提高开发效率、降低维护成本的效果。

南湖打印-Java的基本工作流程

南湖打印-Java的基本工作流程

首先,南湖打印-Java是一个PDF打印框架,定义了自己的XML模型格式文件。当用户编写XML格式文件时,需要遵循XSD文件的元素定义。

如上图所示,在使用南湖打印-Java时,用户需要准备XML模型文件和要打印的业务数据,然后调用南湖打印-Java的框架API完成生成PDF文件的过程。

示例代码如下:

示例代码

南湖打印-Java的主要功能

每一页都有固定的页眉,最后一页有固定的页脚

在企业应用程序中,账单打印是一个常见的功能。通常需要在页面顶部显示标题和公司名称,并在页面底部显示表格金额、日期、公司签名和其他信息的摘要。

如果文档中的表格信息比较长且有多页,通常需要每页都在顶部显示表头行信息。

使用南湖打印Java框架,用户可以通过配置方便快捷地完成这些功能。

简化的配置内容如下:

	 <body>

        <params>

            <param name="extendToFillBody" value="default"></param>

        </params>

        <table>

            <thead showPosition="firstPage">

                BillTitle,,,,,,

            </thead>

            <thead showPosition="everyPage">

                table head content,,,,,,

            </thead>

            <tbody>

                table body content,,,,,,

            </tbody>

            <tloop>

                last page fill content,,,,,,

            </tloop>

            <tbottom>

                last page bottom content,,,,,,

            </tbottom>

        </table>

    </body>


在每一页的任意位置显示页码

对于多页文档,有必要在表头显示页码,也可能需要在表尾显示页码。

用户可以使用以下配置方法,在页面的任意位置实现与页码相关的信息。

   <div>

        <params>

            <param name="customContent" value="com.hongjinqiu.nanhuprint.eval.custom.CustomPageNumber" />

            <param name="customContentFormat" value="{currentPageNumber} of {totalPageNumber}" />

        </params>

    </div>


使用模板打印

对于快递订单等文档,在开发PDF打印模板时,需要使用图片作为背景。当用户使用南湖打印Java开发这种类型的版本时,可以设置背景图片,然后调整文本的填充值:

	 <div backgroundSize="contain" width="100px" height="420px">

        <css>

            <backgroundImage js="url('http://xxxx.png')" />

        </css>

        <div paddingLeft="10px" paddingTop="24px"><span value="InvoiceCode" /></div>

    </div>


水印

南湖打印Java框架支持通过配置实现文本水印或图片水印。

配置如下:

图片水印

	 <div fontWeight="bold" paddingTop="10">

        <params>

            <param name="waterMark" value="default" />

            <param name="waterMarkOpacity" value="0.9" />

            <param name="waterMarkOffsetX" value="-150" />

            <param name="waterMarkOffsetY" value="0" />

            <param name="waterMarkImage" value="http://localhost:8891/images/camel.png" />

            <param name="waterMarkImageWidth" value="200" />

            <param name="waterMarkImageHeight" value="78" />

            <param name="waterMarkRotation" value="45" />

            <param name="waterMarkLayer" value="default" />

        </params>

    </div>


文本水印

	<div>

        <params>

            <param name="waterMark" value="default" />

            <param name="waterMarkText" value="I am waterMarkText" />

            <param name="waterMarkOpacity" value="0.5" />

            <param name="waterMarkTextFontSize" value="24" />

            <param name="waterMarkOffsetX" value="0" />

            <param name="waterMarkOffsetY" value="100" />

            <param name="waterMarkRotation" value="45" />

            <param name="waterMarkLayer" value="under" />

        </params>

    </div>


交替打印表行不同的背景

如果有一个长表格,需要为每一行显示不同的背景颜色,可以通过配置轻松实现。

配置示例如下:

	<forEach var="item" itemsJs="data.contentList" varStatus="index">

        <set valueJs="'white'" var="loopBgColor" />

        <if testJs="index %2 == 0">

            <set valueJs="'orange'" var="loopBgColor" />

        </if>

        <tr fontFamily="abc" backgroundColor="js:loopBgColor">

            <td width="100%">

                <div paddingTop="20">

                    <span value="js:item"/>

                </div>

            </td>

        </tr>

    </forEach>


使用动态标签实现复杂的显示逻辑

南湖打印Java框架支持动态标签:if、forEach、macroRef、set、Macro。

配置示例如下:

	<if testJs="index %2 == 0"></if>

    <forEach var="item" itemsJs="data.contentList" varStatus="index"></forEach>


用于重复块引用的宏

如果页面上有重复的代码显示块,可以将重复的代码显示块放入宏标签中,然后在其他位置引用它们。

定义宏代码块:

	<macro name="addressBillingMacro">

        <div cls="f12">

            <span value="wwww"/>

        </div>

    </macro>


引用宏代码块:

<macroRef name="addressBillingMacro"/>


减小单元格字体大小以显示完整内容

在文档打印中,有时单元格宽度是固定的,但单元格内容太长,可以通过scaleToFitContentByPdf参数进行配置,轻松实现内容缩放。

配置示例:

	<div width="20px" scaleToFitContentByPdf="true">

        <span value="RMB 999,999,999.99" />

    </div>


单元格宽度可以根据单元格内容动态改变

如果希望文字不会缩小或换行,而是希望单元格宽度随内容改变,可以进行如下配置:

	<td textAlign="left">

        <params>

            <param name="calcWidth" value="com.hongjinqiu.nanhuprint.eval.custom.CalcWidth" />

            <param name="calcWidthTagId" value="leftIssueBy" />

        </params>

        <div id="leftIssueBy" cls="f13 bodyLineHeight" whiteSpace="nowrap" paddingRight="5px" >

            <span value="ISSUED BY:" />

        </div>

    </td>


数量、单价、金额等字段的格式化

数量、单价、金额等字段通常需要进行格式化和显示。

用户可以通过应用程序来格式化这些字段的值传递给框架。

这些字段的格式化也可以通过框架提供的配置来实现:

	<span value="js:item.item_price" format="num"/>

    <span value="js:item.item_price" format="unitPrice"/>

    <span value="js:item.item_price" format="amt"/>


推荐阅读: 远程办公会是未来的趋势吗

本文链接: 一个解决企业开发痛点的PDF框架