iText + freemarker 根据html模板导出pdf 带页眉,页眉横线,页脚
1.引入依赖
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
<version>5.5.13</version>
</dependency>
<dependency>
<groupId>com.itextpdf.tool</groupId>
<artifactId>xmlworker</artifactId>
<version>5.5.13</version>
</dependency>
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itext-asian</artifactId>
<version>5.2.0</version>
</dependency>
<dependency>
<groupId>org.xhtmlrenderer</groupId>
<artifactId>flying-saucer-pdf-itext5</artifactId>
<version>9.0.3</version>
</dependency>
2.html模板:
<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/html">
<style>
.line {
text-decoration: underline;
}
.headCenter {
height: 40px;
}
body {
margin-left:30px;
margin-right:30px;
font-size: 16px;
font-family: SimSun;
}
.tdCenter {
text-align: center;
}
.fontsize5 {
font-size: 14px;
margin-top: 15px;
}
div {
line-height: 30px;
}
table, th, td{
border:1px solid #000000;
height:30px;
}
table {
border-collapse: collapse;
table-layout: fixed;
word-break:break-all;
font-size: 10px;
width: 100%;
}
td {
width: 60px;
word-break:break-all;
word-wrap : break-word;
}
.spanWeight {
font-weight: bold;
}
.div5Padding {
padding: 5px 0px;
}
p {
text-indent:1.5em;
line-height: 30px;
margin:0px auto
}
th {
text-align:center
}
.borderNone {
border:0px solid black;
}
</style>
<body>
<div class="headCenter"></div>
<div class="headCenter"></div>
<table cellspacing="0" cellpadding="0" class="borderNone" style="margin-top: 20px;">
<tr>
<td class="borderNone"><strong>合同编号:</strong><span class="line">{(test)!} </span></td>
</tr>
</table>
<#--list 多层嵌套-->
<#if testList??>
<#list testList as item>
<p>
{subItem.test}
</p>
</#list>
</#if>
</p>
</#list>
</#if>
</body>
</html>
3.导出工具类
注意:数据名称要和模板里的字段名称对上。我这里的object是一个实体类。
另外在项目的resources新建一个templates放置模板,在linux部署也可以找到该模板。
public class PdfUtil {
@Resource
FreeMarkerConfigurer freeMarkerConfigurer;
public void doExport(HttpServletResponse response,String htmlString,String fileName) throws Exception {
response.setContentType("application/pdf");
response.setCharacterEncoding("utf-8");
response.setHeader("Pragma", "public");
response.setHeader("Cache-Control", "max-age=30");
response.setHeader("Content-disposition","attachment;filename=" + new
String(fileName.getBytes("utf-8"),"ISO-8859-1" )+ ".pdf");
OutputStream out;
out = response.getOutputStream();
//设置文档大小
Document document = new Document();
document.setMargins(40,40,60,60);
PdfWriter writer = PdfWriter.getInstance(document, out);
writer.setStrictImageSequence(true);
//输出为PDF文件
convertToPDF(writer,document,htmlString);
}
/**
* @description PDF文件生成
*/
private void convertToPDF(PdfWriter writer,Document document,String htmlString){
writer.setPageEvent(new PDFBuilder());
document.open();
MyFontsProvider fontProvider = new MyFontsProvider();
fontProvider.addFontSubstitute("lowagie", "simsun");
fontProvider.setUseUnicode(true);
CssAppliers cssAppliers = new CssAppliersImpl(fontProvider);
HtmlPipelineContext htmlContext = new HtmlPipelineContext(cssAppliers);
htmlContext.setTagFactory(Tags.getHtmlTagProcessorFactory());
try {
XMLWorkerHelper.getInstance().parseXHtml(writer,document,
new ByteArrayInputStream(htmlString.getBytes("UTF-8")),
XMLWorkerHelper.class.getResourceAsStream("/default.css"),
Charset.forName("UTF-8"),fontProvider);
} catch (IOException e) {
e.printStackTrace();
throw new BusinessException("PDF文件生成异常");
}finally {
document.close();
}
}
/**
* @description 获取模板并填充数据
*/
public String getContent(String fileName, Object data) throws Exception{
// 创建一个Configuration对象
Configuration configuration = new Configuration(Configuration.getVersion());
// 告诉config对象模板文件存放的路径。
configuration.setDefaultEncoding("utf-8");
//从config对象中获得模板对象。需要制定一个模板文件的名字。
Template template = freeMarkerConfigurer.getConfiguration().getTemplate(fileName+".ftl");
StringWriter writer = new StringWriter();
//模版和数据匹配
template.process(data, writer);
writer.flush();
writer.close();
String html = writer.toString();
return html;
}
/**
* 设置字符集
*/
public static class MyFontsProvider extends XMLWorkerFontProvider {
public MyFontsProvider(){
super(null, null);
}
@Override
public Font getFont(final String fontname, String encoding, float size, final int style) {
String fntnames = fontname;
Font FontChinese = null;
if (fntnames == null) {
fntnames = "宋体";
}
if (size == 0) {
size = 4;
}
try{
//注意这里有一个,1
BaseFont bfChinese = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", false);
FontChinese = new Font(bfChinese);
}catch (Exception e){
e.printStackTrace();
}
if(FontChinese==null){
FontChinese = super.getFont(fntnames, encoding, size, style);
}
return FontChinese;
}
}
}
设置页眉页脚等
设置页眉地下的横线的时候,无论如何只能使得一边没有,所有加一张图片盖住另一边,有会的同学可以告诉我。
public class PDFBuilder extends PdfPageEventHelper {
/**
* 页眉
*/
public String header = "";
/**
* 文档字体大小,页脚页眉最好和文本大小一致
*/
public int presentFontSize = 10;
/**
* 文档页面大小,最好前面传入,否则默认为A4纸张
*/
public Rectangle pageSize = PageSize.A4;
/**
* 模板
*/
public PdfTemplate total;
/**
* 基础字体对象
*/
public BaseFont bf = null;
/**
* 利用基础字体生成的字体对象,一般用于生成中文文字
*/
public Font fontDetail = null;
/**
* 利用基础字体生成的字体对象,一般用于生成中文文字
*/
public Font fontDetail1 = null;
public void setHeader(String header) {
this.header = header;
}
/**
*
* @see PdfPageEventHelper#onOpenDocument(PdfWriter,
* Document)
*/
@Override
public void onOpenDocument(PdfWriter writer, Document document) {
// 共 页 的矩形的长宽高
total = writer.getDirectContent().createTemplate(50, 50);
}
/**
*
*关闭每页的时候,写入页眉,写入'第几页共'这几个字。
* @see PdfPageEventHelper#onEndPage(PdfWriter,
* Document)
*/
@Override
public void onEndPage(PdfWriter writer, Document document) {
this.addPage(writer, document);
}
/**
* 加分页
*/
@SneakyThrows
public void addPage(PdfWriter writer, Document document){
InputStream is = null;
ByteArrayOutputStream baos = null;
InputStream is1 = null;
ByteArrayOutputStream baos1 = null;
//设置分页页眉页脚字体
Image image = null;
Image whiteImage = null;
try {
final String picPath = "img/img001.png";
final String picPath1 = "img/img002.png";
Resource resource = new ClassPathResource(picPath);
Resource resource1 = new ClassPathResource(picPath1);
is = resource.getInputStream();
baos = new ByteArrayOutputStream();
int i;
while ((i = is.read()) != -1) {
baos.write(i);
}
is1 = resource1.getInputStream();
baos1 = new ByteArrayOutputStream();
int j;
while ((j = is1.read()) != -1) {
baos1.write(j);
}
// 页眉logo图片实例
image = Image.getInstance(baos.toByteArray());
whiteImage = Image.getInstance(baos1.toByteArray());
if (bf == null) {
bf = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", false);
}
if (fontDetail == null) {
// 数据体字体
fontDetail = new Font(bf, presentFontSize, Font.NORMAL);
}
if (fontDetail1 == null) {
// 数据体字体
fontDetail1 = new Font(bf, 16, Font.BOLD);
}
} catch (DocumentException | IOException e) {
e.printStackTrace();
}finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (baos != null) {
try {
baos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
PdfContentByte directContent = writer.getDirectContent();
//1、生成右侧页眉
// document.right(-20) 方法设置页眉的左右间距
// document.top(+20) 方法设置页眉的上下间距
ColumnText.showTextAligned(directContent,
Element.ALIGN_RIGHT, new Phrase(" 测试", fontDetail),
document.right(150), document.top(-13), 0);
//设置线条
Phrase p2 = new Phrase();
p2.add(new Chunk(new LineSeparator()));
ColumnText.showTextAligned(directContent,
Element.ALIGN_LEFT, p2,
document.left(), document.top(-8), 0);
Phrase p3 = new Phrase();
p3.add(new Chunk(whiteImage, 0, -30));
ColumnText.showTextAligned(directContent,
Element.ALIGN_RIGHT, p3,
document.right(-45), document.top(-8), 0);
// !!!! 最重要的是这个, 如果页眉需要设置图片的话,需要在Phrase对象中添加一个Chunk对象,在Chunk对象中添加图片信息即可
Phrase p1 = new Phrase("", fontDetail);
p1.add(new Chunk(image, 0, -30));
// 1、写入左侧页眉
ColumnText.showTextAligned(directContent,
Element.ALIGN_LEFT, p1,
document.left(50), document.top(-40), 0);
// 2、生成右侧页脚
ColumnText.showTextAligned(directContent,
Element.ALIGN_LEFT, new Phrase("第" + document.getPageNumber() + "页", fontDetail),
document.left(230), document.bottom(-30), 0);
//设置线条
ColumnText.showTextAligned(directContent,
Element.ALIGN_LEFT, p2,
document.left(), document.bottom(-20), 0);
//设置图片遮住多余的线条
ColumnText.showTextAligned(directContent,
Element.ALIGN_RIGHT, p3,
document.right(-45), document.bottom(-20), 0);
//加这个为了模板的替换
directContent.addTemplate(total, document.left(260), document.bottom() - 30);
//第一页
if(document.getPageNumber()==1) {
ColumnText.showTextAligned(directContent,
Element.ALIGN_LEFT, new Phrase("测试测试测试", fontDetail1),
document.left(150), document.top(30), 0);
ColumnText.showTextAligned(directContent,
Element.ALIGN_CENTER, new Phrase("测试", fontDetail1),
document.left(260), document.top(60), 0);
}
}
/**
*
*关闭文档时,替换模板,完成整个页眉页脚组件
* @see PdfPageEventHelper#onCloseDocument(PdfWriter,
* Document)
*/
@Override
public void onCloseDocument(PdfWriter writer, Document document) {
// 7.最后一步了,就是关闭文档的时候,将模板替换成实际的 Y 值,至此,page x of y 制作完毕,完美兼容各种文档size。
total.beginText();
// 生成的模版的字体、颜色
total.setFontAndSize(bf, presentFontSize);
//页脚内容拼接 如 第1页/共2页
String foot2 = "总" + (writer.getPageNumber()) + " 页";
// 模版显示的内容
total.showText(foot2);
total.endText();
total.closePath();
}
}