关于使用freemarker和xhtmlrenderer来实现动态生成pdf
1 PDF处理
1.1 数据准备
- 将生产的PDF所需数据封装为Map集合中
- 例:
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46public class MetaData {
//类
public static class Person {
public String name;
public String sex;
public Person(String name, String sex) {
this.name = name;
this.sex = sex;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
}
/**
* 获取数据
* @return Map<String, Object>
*/
public static Map<String, Object> getData() {
Map<String, Object> data = new HashMap<>();
data.put("single", "普通数据");
data.put("person", new Person("莱特雷", "男"));
List<String> list = new ArrayList<>();
list.add("Hello");
list.add("World");
list.add("!");
data.put("lists", list);
return data;
}
}
1.2 模板准备
- (1)创建html文件,编写pdf模板
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
<html lang="en">
<head>
<meta charset="UTF-8"/> <!--注意:在meta这里补充一个'/',默认生成没有-->
<title>pdf模板测试</title>
<style>
@page {
/* 页面大小:示例中设置为A4纸的大小 */
size: 210mm 297mm;
margin: 15mm 15mm 15mm 15mm;
}
body {
width: 178mm;
font-family: SimSun; /* 设置字体(宋体) */
font-size: 18px;
}
table {
width: 100%;
border-collapse: collapse;
}
thead th {
border: 1px solid #000;
padding: 5px 5px;
}
tr td {
border: 1px solid #000;
padding: 5px 5px;
}
</style>
</head>
<body>
<h2>PDF模板测试</h2>
<p>
这里是Map中key的数据:
<span>${single}</span>
</p>
<p>
Map中对象数据:
<span>${person.name}</span>
,
<span>${person.sex}</span>
</p>
<table>
<thead>
<tr>
<th>序号</th>
<th>数据</th>
</tr>
</thead>
<tbody>
<#list lists as item> <!-- 被遍历的数据需要用此进行封装, lists为map中的key,item为遍历的元素' -->
<tr>
<td>${item_index + 1}</td> <!--需要获取遍历数据的下标,可以通过xxx_index来获取-->
<td>${item}</td>
</tr>
</#list>
</tbody>
</table>
</body>
</html>
- (2)模板准备好,就修改后缀名为ftl
1.3 工具类准备
- (1)依赖导入
1
2
3
4
5
6
7
8
9
10
11
12
13<!-- freemarker -->
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.31</version>
</dependency>
<!-- 将HTML文件转化为PDF文件 -->
<dependency>
<groupId>org.xhtmlrenderer</groupId>
<artifactId>flying-saucer-pdf</artifactId>
<version>9.1.20</version>
</dependency>
- (2)准备字体文件
- (3)工具类代码
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83/**
* FreeMarker - Pdf工具类
* @author letere
* @create 2021-05-14 15:28
*/
public class PdfUtil {
//编码格式
private static final String ENCODING = "UTF-8";
//ftl模板位置
private static final String TEMPLATE_PATH = "/ftl/templates/pdf";
//字体(宋体)文件路径
private static final String FONT = "/ftl/font/simsun.ttc";
//FreeMarker配置
private static final Configuration CFG = new Configuration(Configuration.VERSION_2_3_22);
static {
//设置模板所在文件夹
CFG.setClassForTemplateLoading(PdfUtil.class, TEMPLATE_PATH);
// setEncoding这个方法一定要设置国家及其编码,不然在ftl中的中文在生成html后会变成乱码
CFG.setEncoding(Locale.getDefault(), ENCODING);
// 设置异常处理器,这样的话就可以${a.b.c.d}即使没有属性也不会出错
CFG.setTemplateExceptionHandler(TemplateExceptionHandler.IGNORE_HANDLER);
}
/**
* 创建PDF文件
* @param data 数据
* @param templateFileName 模板名
* @param outputStream 输出流
*/
public static void create(Map<String, Object> data, String templateFileName, OutputStream outputStream) throws Exception{
//将数据写进模板,得到模板字符串
String templateStr = writeTemplate(data, templateFileName, outputStream);
//渲染字符串形成pdf
renderPDF(templateStr, outputStream);
}
/**
* 将数据动态写进pdf模板
* @param data 数据
* @param templateFileName 模板名
* @param outputStream 输出流
* @return String
*/
private static String writeTemplate(Map<String, Object> data, String templateFileName, OutputStream outputStream) throws Exception{
//获取ftl模板
Template template = CFG.getTemplate(templateFileName, ENCODING);
//将数据写入到ftl模板中
StringWriter writer = new StringWriter();
template.process(data, writer);
writer.flush();
//返回写入后的模板字符串
return writer.toString();
}
/**
* 渲染pdf模板形成PDF
* @param templateStr 模板字符串
* @param outputStream 输出流
*/
private static void renderPDF(String templateStr, OutputStream outputStream) throws Exception{
//防止依赖冲突,导致找不到TransformerFactory,设置TransformerFactory位置
System.setProperty("javax.xml.transform.TransformerFactory", "com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl");
ITextRenderer renderer = new ITextRenderer();
//设置字体样式
renderer.getFontResolver().addFont(FONT, BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
//渲染模板字符串
renderer.setDocumentFromString(templateStr);
renderer.layout();
//生成pdf
renderer.createPDF(outputStream, false);
renderer.finishPDF();
outputStream.flush();
}
}
1.4 测试
- (1)测试代码
1
2
3
4
5
6
7
8
9
10
11
12
public void pdfUtilTest() throws Exception{
//获取输出流
String fileName = "./src/main/resources/template/test.pdf";
OutputStream outputStream = new FileOutputStream(fileName);
//获取数据
Map<String, Object> data = MetaData.getData();
//创建PDF
PdfUtil.create(data, "pdfTemplate.ftl", outputStream);
}
- (2)测试效果