基于FreeMarker生成PDF文件组件
背景说明: 流程平台在很多流程场景中,存在下载批复单的场景,原老流程平台下载批复单均按照java代码,遵从 pdf 的 SDK 标准格式填充,代码依次根据逻辑填充,此方式会存在大量的冗余代码并且重复开发工作量较大.
解决方案: 为减少工作量,从Html生成pdf,可减少较多重复工作量,并且html可以给业务人员展示 生成后的批复单 样式,减少重复沟通的成本。
技术组件jar包
- PDF组件包:itextpdf 5.5.13.3
- 通过html生成pdf包:xmlworker 5.5.13.3
- PDF中文支持包组件:itext-asian 5.2.0
- 支持css样式渲染PDF组件:flying-saucer-pdf-itext5 9.1.22
- 转换html为标准xhtml包:jtidy r938
- freemarker模板引擎:freemarker 2.3.31
- POM引用包:
<!-- 通用生成PDF逻辑 -->
<!-- pdf:start -->
<!-- https://mvnrepository.com/artifact/com.itextpdf/itextpdf -->
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
<version>5.5.13.3</version>
</dependency>
<dependency>
<groupId>com.itextpdf.tool</groupId>
<artifactId>xmlworker</artifactId>
<version>5.5.13.3</version>
</dependency>
<!-- 支持中文 -->
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itext-asian</artifactId>
<version>5.2.0</version>
</dependency>
<!-- 支持css样式渲染 -->
<dependency>
<groupId>org.xhtmlrenderer</groupId>
<artifactId>flying-saucer-pdf-itext5</artifactId>
<version>9.1.22</version>
</dependency>
<!-- 转换html为标准xhtml包 -->
<dependency>
<groupId>net.sf.jtidy</groupId>
<artifactId>jtidy</artifactId>
<version>r938</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.freemarker/freemarker -->
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.31</version>
</dependency>
<!-- pdf:end -->
模块目录规范
一、从html(即:ftl)生成pdf,html模板放在该目录: resouces/pdf_template
- word转换为pdf网址:http://www.docpe.com/word/word-to-html.aspx
- 字体在线转换工具:https://www.fontke.com/tool/convfont
二、 从html(即:ftl)生成pdf字体,放在该目录: resouces/pdf_ttc
- TTF和TTC字体格式解释
- 1) TTF(TrueType Font)是TrueType字体文件,由 Apple 和 Microsoft 共同推出的一种字体文件格式。
- 2) TTC(TrueType Collection)是TrueType字体集成文件,亦称为字体集。 由 Microsoft 开发的一种新的字体格式标准,在同一文件结构中包含多个字体,以便更有效地共享轮廓数据,缩减字体文件大小。
字体对应关系 (使用规则,格式必须为.ttf/.ttc)
- 字体目前是供html生成pdf中文字使用。[HTML中的ftl(freemarker)模板技术,遵循该语法]。
- 可添加多个字体文件,注意说明标记(font-family必须与字体文件名保持一致,否则会导致中文不显示)
- 新增字体后,需要新增字体引用方可在html中使用:
GeneratePdfUtil.generatePDF(String pdfUrl){
//参数:simSunFilePath 字体绝对路径,BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED 两个参数固定。
fontResolver.addFont(simSunFilePath, BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
}
- 特别说明: ttf的文件名,必须与在flt中的文字font-family一致,否则不会显示文字
(例如:字体文件为CHXBS.ttf(彩虹小标宋),html的style中的 font-family:CHXBS; 必须为“CHXBS”(区分大小写),否则中文字体不显示)
字体名称 | 字体文件名 | HTML(即:ftl模板) 中文字体设置说明(style中设置) |
---|---|---|
宋体 | SimSun.ttc | font-family:SimSun; |
彩虹小标宋 | CHXBS.ttf | font-family:CHXBS; |
微软雅黑 | YaHei.ttf | font-family:YaHei; |
代码开发帮助文档
resources/simple下为对pdf对于的template生成的文件pdf,可参考
- 1)pdf-chxbs.html 为彩虹小标宋 字体生成的对应pdf文件:pdf-cgxbs.pdf
- 2)pdf-simsun.html 为宋体 字体生成的对应pdf文件:pdf-simsun.pdf
- 3)pdf-simple.html 为模拟流程平台的批复单 字体(彩虹小标宋+宋体)生成的对应pdf文件:pdf-simple.pdf
操作步骤如下:
一、在word中画出需要的模板,如示例:resources/sample/cp_tmp.docx
特别注意:word中仅支持三种字体“宋体”、“彩虹小标宋”、“微软雅黑”。
二、将编辑完成的word,上传至如下“网址”,转换为html文档。
word转换html网址:http://www.docpe.com/word/word-to-html.aspx
上传网址word,转换为html文件,其中word的 输出值 使用占位符 ${}包含。
例如:${XMMC} ,表示 项目名称 信息
三、检查并编辑HTML调整格式规范。
特别注意:
- 1、编辑生成的HTML代码必须 标签有闭合,即:
<div> </div> 成对出现,否则解析xml报错。
- 2、font-family:检查,为固定的3中字体,否则生成pdf中文不显示。
- 3、${}占位符检查,转换的html中会出现被 <> 标签隔开需要调整代码。
1、编辑html代码,将代码中如下内容替换为支持的文字格式,必须为如下写法,否则pdf转换文字不显示
| 宋体 | font-family:SimSun;
| 彩虹小标宋 | font-family:CHXBS;
| 微软雅黑 | font-family:YaHei;
2、检查生成的HTML代码中所有的font-family,取值为 以上3中字体(必须检查font-family:SimSun; /font-family:CHXBS; / font-family:YaHei;)
3、排查生成的HTML代码中,存在${XXX}的占位符被HTML转换后,<span>隔开的情况,进行归整(特别注意成对标签闭合检查)
4、检查完成后,可打开html进行预览,给业务确认表单格式及样式。
四、HTML存在判断场景,可以使用freemaker标签进行判断即可。
freemaker标签语法(pdf-simple.html 中的“分管领导意见”有 示例代码 )
- 基本语法请自行度娘。
- 如下为判断和相关迭代指令用法
- a)判断指令
-- if elseif else
<#if key == 1>
-- key=1时显示
<#elseif key == 2>
-- key=2时显示
<#else>
-- 其他情况时显示
</#if>
-- switch case
<#switch key>
<#case 1>
-- key=1时显示
<#break>-- 有该标签时当key=1时不会判断下面其他条件
<#case 2>
-- key=2时显示
<#break>
<#default>
-- 其他情况时显示
</#switch>
- b. 遍历指令
-- list遍历 list要遍历的集合,item为遍历出的单个数据 <#else>可选
<#list list as item>
${item} -- 获取单个数据展示
<#else>
-- 集合为空时显示
</#list>
-- list遍历
<#list list as item>
${item} <#sep>, </#sep> -- <#sep>可为每个遍历出的元素之前添加分割元素
</#list>
-- list遍历
<#list list as item>
${item}
<#if item == "C">
<#break> -- <#break>可在遍历过程中停止之后的遍历
</#if>
</#list>
-- list遍历: 遍历项 索引(0开始) 行号(1开始) 单双行(odd/even)
<#list list as item>
${item} ${item?index} ${item?counter} ${item?item_parity}
</#list>
-- map遍历
<#list map?keys as key>
${key} ${map[key]}
</#list>
五、java执行html转换pdf生成代码
- 详见GeneratePdfUtil.main()
- 通过html代码(即:freemaker标签语法) 生成pdf文件(实际为ftl文件,区别是改了后缀名,底层freemaker技术实现)
- java调用代码:
/**
* 获取模板路径和所在dir目录
*/
String tplNameSimple = "pdf-simple.html";
String TEMPLATES_PATH = getResouceFLTDirPath(tplNameSimple);
/**
* 参数MAP,用于填充html中的变量
*/
Map<String, Object> paramMap = new HashMap<String, Object>();
paramMap.put("PFBH", "JXXT-PF-2022-01");
//批复信息
paramMap.put("DEPTNAME", "财富管理事业部");
paramMap.put("CSKSRQ", "建信信托-私人银行家族信托单一信托1001号");
paramMap.put("PFJG", "项目评审管理委员会");
paramMap.put("PFRQ", "2022年01月01日");
paramMap.put("PFYJ", "否决,项目存在风险xxx等");
//项目信息
paramMap.put("XMMC", "建信信托-私人银行家族信托单一信托1001号");
paramMap.put("XMGM", "20000万元");
paramMap.put("FYMC", "36个月");
paramMap.put("XMLY", "建行推荐");
paramMap.put("ZHRZCB", "1%-2%/年");
paramMap.put("ZJYYFS", "(2015)*****字第0**20-1**0号用于xxx项目投资、境外投资等");
//意见详情
//是否显示分管领导意见 标记
paramMap.put("FGYJ_ISSHOW", "false");
paramMap.put("JLYJ", "同意,乐乐。2022年03月31日");
paramMap.put("FZRYJ", "同意,芳芳。2016年10月31日 00时00分");
paramMap.put("ZYYJ", "同意,木木。2016年10月31日 00时00分");
paramMap.put("FGYJ", "同意,冬冬。2016年10月31日 00时00分");
//业务部门最后署名信息
paramMap.put("YWBM", "项目综合管理部");
paramMap.put("PFRQ", "2022年12月31日");
/**
* ContractDynamicParam 构建生成pdf的代码,如下为传入参数:
* TEMPLATES_PATH :模板所在目录,固定写法即可。
* tplNameSimple : 模板文件名,为新增的模板文件名,带后缀.html
* "E:\": 为pdf输出的目录,可动态获取存储到服务器。
* "pdf-simple.pdf" 为生成的pdf名称,带后缀名。
* paramMap :为生成的html占位符数据map
*/
ContractDynamicParam xbsPararm = new ContractDynamicParam(TEMPLATES_PATH, tplNameSimple,
"E:\", "pdf-simple.pdf", paramMap);
GeneratePdfUtil.GenerateContract(xbsPararm);
System.out.println("====生成pdf-simple 批复单 - PDF成功====");