前言:
尝试了不少通过模板文件导出pdf文件的方法,要么实现起来复杂,要么实现效果不理想,经过反复查找资料和尝试发现此方法是最理想的。
本博客又经大量网友实践及建议,经过几次完善修改,又日趋完善,在此表示感谢。
一,依赖jar包
<!-- freemarker 读取html模板文件 -->
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.23</version>
</dependency>
<!-- xml 将html模板文件转换成pdf -->
<dependency>
<groupId>org.xhtmlrenderer</groupId>
<artifactId>flying-saucer-pdf</artifactId>
<version>9.0.9</version>
</dependency>
二,模板文件(为html,命名后缀为.ftl)
SIMSUN.TTC文件来自自己计算机C:\WINDOWS\Fonts文件夹里面的宋体
注:这里的文件名是小写的,代码中是大写的,要保持一致,要么修改文件名为全部大写,要么修改代码中的文件名为小写。
pdf测试模板.ftl内容:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title></title>
<style>
*{
margin: 0;
padding: 0;
box-sizing: border-box;
}
body{
font-family: SimSun;
}
section{
display:block;
margin: 20px 10px;
}
.title{
text-align: center;
}
.preface p{
line-height: 30px;
}
.preface p.content{
text-indent: 2em;
}
section > table{
table-layout: fixed;
width: 100%;
margin: 20px 0px;
text-align:center;
word-wrap:break-word;
}
section table td{
padding:5px 0px;
}
</style>
</head>
<body>
<!-- 标题 start -->
<section class="title">
<h2>某报告</h2>
</section>
<!-- 标题 end -->
<!-- 前言 start -->
<section class="preface">
<p>尊敬的用户:</p>
<p class="content">内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容</p>
</section>
<!-- 前言 end -->
<!-- 汇总统计信息 start -->
<section class="count-info">
<h4>汇总统计信息</h4>
<table border="1" cellspacing="0" cellpadding="0">
<tr>
<td>本月笔数</td>
<td>近三个月数量对比</td>
</tr>
<tr>
<td>${curr}</td>
<td>
<table width="80%" border="1" cellspacing="0" cellpadding="0" style="margin: 5px auto;">
<tr>
<td>${one}</td>
<td>${two}</td>
<td>${three}</td>
</tr>
</table>
</td>
</tr>
</table>
</section>
<!-- 汇总统计信息 end -->
<!-- 明细 start -->
<section class="detail">
<h4>明细</h4>
<table border="1" cellspacing="0" cellpadding="0">
<tr>
<td width="5%">序号</td>
<td width="15%">列1</td>
<td width="12%">列2</td>
<td width="12%">列3</td>
<td width="12%">列4</td>
<td>列5</td>
</tr>
<#list detailList as ad>
<tr>
<td>${ad_index+1}</td>
<td>${ad.column1}</td>
<td>${ad.column2}</td>
<td>${ad.column3}</td>
<td>${ad.column4}</td>
<td>${ad.column5}</td>
</tr>
</#list>
</table>
</section>
<!-- 明细 end -->
</body>
</html>
三,工具类实现
package test.pdf;
import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.io.StringWriter;
import java.util.Locale;
import java.util.Map;
import org.xhtmlrenderer.pdf.ITextRenderer;
import com.lowagie.text.pdf.BaseFont;
import freemarker.template.Configuration;
import freemarker.template.Template;
public class PDFTemplateUtil {
/**
* 通过模板导出pdf文件
* @param data 数据
* @param templateFileName 模板文件名
* @throws Exception
*/
public static ByteArrayOutputStream createPDF(Map<String,Object> data, String templateFileName) throws Exception {
// 创建一个FreeMarker实例, 负责管理FreeMarker模板的Configuration实例
Configuration cfg = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS);
// 指定FreeMarker模板文件的位置
cfg.setClassForTemplateLoading(PDFTemplateUtil.class,"/templates");
ITextRenderer renderer = new ITextRenderer();
OutputStream out = new ByteArrayOutputStream();
try {
// 设置 css中 的字体样式(暂时仅支持宋体和黑体) 必须,不然中文不显示
renderer.getFontResolver().addFont("/templates/font/SIMSUN.TTC", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
// 设置模板的编码格式
cfg.setEncoding(Locale.CHINA, "UTF-8");
// 获取模板文件
Template template = cfg.getTemplate(templateFileName, "UTF-8");
StringWriter writer = new StringWriter();
// 将数据输出到html中
template.process(data, writer);
writer.flush();
String html = writer.toString();
// 把html代码传入渲染器中
renderer.setDocumentFromString(html);
// 设置模板中的图片路径 (这里的images在resources目录下) 模板中img标签src路径需要相对路径加图片名 如<img src="images/xh.jpg"/>
String url = PDFTemplateUtil.class.getClassLoader().getResource("images").toURI().toString();
renderer.getSharedContext().setBaseURL(url);
renderer.layout();
renderer.createPDF(out, false);
renderer.finishPDF();
out.flush();
return (ByteArrayOutputStream)out;
} finally {
if(out != null){
out.close();
}
}
}
}
注:SIMSUN.TTC为宋体,可在C:\Windows\Fonts中查找
四,控制类实现
package test.controller;
import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import test.domain.PDFDataTest;
import test.pdf.PDFTemplateUtil;
@RestController
@RequestMapping("/pdf")
public class PdfController {
@RequestMapping("/export")
public void exportPdf(HttpServletResponse response) throws Exception{
ByteArrayOutputStream baos = null;
OutputStream out = null;
try {
// 模板中的数据,实际运用从数据库中查询
Map<String,Object> data = new HashMap<>();
data.put("curr", 1);
data.put("one", 2);
data.put("two", 1);
data.put("three", 6);
List<PDFDataTest> detailList = new ArrayList<>();
detailList.add(new PDFDataTest(123456,"测试","测试","测试","测试"));
detailList.add(new PDFDataTest(111111,"测试","测试","测试","测试"));
detailList.add(new PDFDataTest(222222,"测试","测试","测试","测试"));
data.put("detailList", detailList);
baos = PDFTemplateUtil.createPDF(data, "pdf测试模板.ftl");;
// 设置响应消息头,告诉浏览器当前响应是一个下载文件
response.setContentType( "application/x-msdownload");
// 告诉浏览器,当前响应数据要求用户干预保存到文件中,以及文件名是什么 如果文件名有中文,必须URL编码
String fileName = URLEncoder.encode("月度报告.pdf", "UTF-8");
response.setHeader( "Content-Disposition", "attachment;filename=" + fileName);
out = response.getOutputStream();
baos.writeTo(out);
baos.close();
} catch (Exception e) {
e.printStackTrace();
throw new Exception("导出失败:" + e.getMessage());
} finally{
if(baos != null){
baos.close();
}
if(out != null){
out.close();
}
}
}
}
注意:模板中占位符值不能为null
附:PDFDataTest类
package test.domain;
public class PDFDataTest {
private Integer column1;
private String column2;
private String column3;
private String column4;
private String column5;
public PDFDataTest(Integer column1, String column2, String column3, String column4, String column5) {
super();
this.column1 = column1;
this.column2 = column2;
this.column3 = column3;
this.column4 = column4;
this.column5 = column5;
}
public Integer getColumn1() {
return column1;
}
public void setColumn1(Integer column1) {
this.column1 = column1;
}
public String getColumn2() {
return column2;
}
public void setColumn2(String column2) {
this.column2 = column2;
}
public String getColumn3() {
return column3;
}
public void setColumn3(String column3) {
this.column3 = column3;
}
public String getColumn4() {
return column4;
}
public void setColumn4(String column4) {
this.column4 = column4;
}
public String getColumn5() {
return column5;
}
public void setColumn5(String column5) {
this.column5 = column5;
}
}
五、实现效果
六、横向显示
在css文件中添加
@page {
size: landscape;
}
纵向:portrait
横向:landscape
效果:
竖屏:
横屏:
更多关于打印设置可参考博客:css @page规则控制打印设置选项