Java通过html模板(freemarker模板)生成PDF文件

目录
  1. 需求
  2. 解决
    1. 关键的Maven依赖配置
    2. 关键Java代码
    3. freemarker模板示例
      1. 一些需要注意的地方

需求

为方便定制,项目有了通过html模板生成动态生成PDF的需求。本文使用的是flying-saucer-pdf-itext5 + freemarker的方案。

解决

关键的Maven依赖配置

1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>org.xhtmlrenderer</groupId>
<artifactId>flying-saucer-pdf-itext5</artifactId>
<version>9.1.5</version>
</dependency>
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.23</version>
</dependency>

关键Java代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public void generate(String templateContent, Map<String, String> dataMap, String fontPath, File file) {
FileOutputStream outputStream = null;
ITextRenderer renderer = new ITextRenderer();
try {
Configuration cfg = new Configuration();
StringTemplateLoader stringLoader = new StringTemplateLoader();
stringLoader.putTemplate("myTemplate", templateContent);
cfg.setTemplateLoader(stringLoader);
Template template = cfg.getTemplate("myTemplate", "utf-8");
String htmlData = FreeMarkerTemplateUtils.processTemplateIntoString(template, dataMap);
outputStream = new FileOutputStream(file);

ITextFontResolver fontResolver = renderer.getFontResolver();
// 解决中文乱码问题,fontPath为中文字体地址
fontResolver.addFont(fontPath, BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
renderer.setDocumentFromString(htmlData);

renderer.layout();
renderer.createPDF(outputStream);
} catch (DocumentException | IOException | TemplateException e) {
log.error("生成失败", e);
} finally {
renderer.finishPDF();
IOUtils.closeQuietly(outputStream);
}
}

其中:

参数templateContent为freemarker模板内容,可直接从项目文件中读取或者定义在数据库中方便定制;

参数dataMap为freemark模板数据;

参数fontPath是为了解决生成的PDF乱码问题,将中文字体位置传入并进行配置同时在模板文件中也需要进行配置;

参数file则是为了将PDF生成至文件。

freemarker模板示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0"/>
<title></title>
<style type="text/css">
@page {
size: 210mm 297mm;
margin:0 auto;padding:0;
}
* {margin:0;padding:0;}
body {background: #fff;font-family: 'Microsoft YaHei'}
</style>
</head>
<body>
<h1>${name!''}标题</h1>
<#if isSigned?exists><span>内容</span></#if>
<img style="width="60" height="60" src="data:image/png;base64,${stamp!''}" />
</body>
</html>
一些需要注意的地方

style中font-family配合java程序中的字体设置可解决PDF中文乱码的问题。

${name!’’}的写法会将 dataMap中 key 为name的 value值渲染进freemarker模板。!’’的写法是为了设置空默认值,从而当dataMap中没有key为name时使用默认值且程序不会报错。

<#if isSigned?exists></#if>的写法让存在isSigned值时显示if里的内容。

img src的写法配合dataMap中图片转base64后stamp值 可用于显示图片。

页眉页脚及打印样式可通过css中的@page等打印属性进行设置。