maven:
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.23</version>
</dependency>
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
<version>5.4.1</version>
</dependency>
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itext-asian</artifactId>
<version>5.2.0</version>
</dependency>
<dependency>
<groupId>com.itextpdf.tool</groupId>
<artifactId>xmlworker</artifactId>
<version>5.4.1</version>
</dependency>
Java代码:
package com.xtm.seal.utils.pdfUtil;
import com.itextpdf.text.*;
import com.itextpdf.text.pdf.*;
import freemarker.template.Configuration;
import freemarker.template.Template;
import org.apache.pdfbox.io.MemoryUsageSetting;
import org.apache.pdfbox.multipdf.PDFMergerUtility;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.rendering.PDFRenderer;
import org.xhtmlrenderer.pdf.ITextRenderer;
import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.*;
import java.util.List;
/**
* @project: xtm-print
* @description: pdfs生成工具
* @author: zhangyingbin
* @create: 2020-06-16 12:57
**/
public class PdfUtil {
private static String TEMPLATE = null;
private static String CONTRACT = null;
private static String PDFNAME = null;//pdf文件名
private static String HTMLNAME = null;//html文件名
static {
String basePath = new File("").getAbsolutePath();
TEMPLATE = basePath + "/src/main/resources/templates/";//模板存储路径
CONTRACT = basePath + "/src/main/resources/templates/result/";
String name = UUID.randomUUID().toString().replaceAll("-","")+"-"+String.valueOf(System.currentTimeMillis());
PDFNAME = name;
HTMLNAME = name;
}
/**
* @Description: 挖域表单模板生成pdf
* @Param: [templateName, map, request, response]
* @return: java.io.File
* @Author: zhangyingbin
* @Date: 2020/6/29
*/
public static File AcrobatPdfTemplate(String templateName, Map<String,Object> map) throws IOException, DocumentException {
Map<String, Object> o = new HashMap();
o.put("datemap", map);
String rootPath = new File("").getAbsolutePath();
//返回tomcat真实路径 Users/a/soft/apache-tomcat-8.5.37/
System.out.println(rootPath);
//return rootPath.substring(0, rootPath.lastIndexOf("/") + 1);
String templatePath = rootPath+"/src/main/resources/templates/"+templateName+".pdf";
//TODO 生成的新文件路径
String newPDFPath = rootPath+"/src/main/resources/templates/result/"+ UUID.randomUUID().toString().replaceAll("-","")+"-"+System.currentTimeMillis() +".pdf";
PdfReader reader;
FileOutputStream out;
ByteArrayOutputStream bos;
PdfStamper stamper;
//↓↓↓↓↓这个是字体文件
BaseFont bf = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
Font FontChinese = new Font(bf, 5, Font.NORMAL);
out = new FileOutputStream(newPDFPath);// 输出流
reader = new PdfReader(templatePath);// 读取pdf模板
bos = new ByteArrayOutputStream();
stamper = new PdfStamper(reader, bos);
AcroFields form = stamper.getAcroFields();
Map<String, String> datemap = (Map<String, String>) o.get("datemap");
form.addSubstitutionFont(bf);
for (String key : datemap.keySet()) {
String value = datemap.get(key);
form.setField(key, value,true);
}
//TODO 插入图片
//获取插入图片的位置的文本域的坐标
// int page = form.getFieldPositions("peo1_name").get(0).page;
// System.out.println("page :" + page);
// Rectangle signReact = form.getFieldPositions("peo1_name").get(0).position;
// float x = signReact.getLeft();
// float y = signReact.getBottom();
// System.out.println("x:" + x + " y:" + y);
// //插入图片
// Image image = Image.getInstance("/Users/zhangyingbin/Desktop/zyb.png");
// //获取图片页面
// PdfContentByte under = stamper.getOverContent(page);
// //图片大小自适应
// image.scaleToFit(signReact.getWidth(), signReact.getHeight());
// image.setAbsolutePosition(x, y);
// under.addImage(image);
stamper.setFormFlattening(true);// 如果为false,生成的PDF文件可以编辑,如果为true,生成的PDF文件不可以编辑
stamper.close();
Document doc = new Document();
// Font font = new Font(bf, 32);
PdfCopy copy = new PdfCopy(doc, out);//用于保存原页面内容,然后输出
doc.open();
PdfImportedPage importPage = null;
///循环是处理成品只显示一页的问题
for (int i = 0; i < reader.getNumberOfPages(); i++) {
importPage = copy.getImportedPage(new PdfReader(bos.toByteArray()), i + 1);
copy.addPage(importPage);
}
//TODO 复制第二页插入其他过长数据
// ByteArrayOutputStream bosArray[] = new ByteArrayOutputStream[reader.getNumberOfPages()];
// //复制两次
// for (int j = 0; j < 2; j++) {
// PdfReader reader1 = new PdfReader(templatePath);// 重新读取pdf模板
// bosArray[j] = new ByteArrayOutputStream();
// reader1.selectPages(String.valueOf(reader1.getNumberOfPages()));//截取模板的最后页
// PdfStamper stamper1 = new PdfStamper(reader1, bosArray[j]);
//
// AcroFields fields = stamper1.getAcroFields();
// fields.addSubstitutionFont(bf);
// fields.setField("bianhao", "12345");
// fields.setField("type", "测试活动");
// fields.setField("content", "这是第" + j + "张图片");
//
// importPage = copy.getImportedPage(reader1, 1);//输出当前页
// copy.addPage(importPage);
// }
doc.close();
out.close();
System.out.println("生成pdf文件完成~~~~~~~~~~");
return new File(newPDFPath);
}
/**
* @Description: ftl模板生成pdf
* @Param: [templateName, paramMap]
* @return: java.io.File
* @Author: zhangyingbin
* @Date: 2020/6/29
*/
public static File FtlPdfTemplate(String templateName,
Map<String, Object> paramMap) throws Exception {
// 获取本地模板存储路径、合同文件存储路径
String templatePath = TEMPLATE;
String contractPath = CONTRACT;
// 组装html和pdf合同的全路径URL
String localHtmlUrl = contractPath + HTMLNAME + ".html";
String localPdfPath = contractPath + "/";
// 判断本地路径是否存在如果不存在则创建
File localFile = new File(localPdfPath);
if (!localFile.exists()) {
localFile.mkdirs();
}
String localPdfUrl = localFile + "/" + PDFNAME + ".pdf";
templateName = templateName + ".ftl";
htmHandler(templatePath, templateName, localHtmlUrl, paramMap);// 生成html合同
pdfHandler(localHtmlUrl, localPdfUrl);// 根据html合同生成pdf合同
deleteFile(localHtmlUrl);// 删除html格式合同
System.out.println("PDF生成成功");
return new File(localPdfUrl);
}
private static void htmHandler(String templatePath, String templateName,
String htmUrl, Map<String, Object> paramMap) throws Exception {
Configuration cfg = new Configuration();
cfg.setDefaultEncoding("UTF-8");
cfg.setDirectoryForTemplateLoading(new File(templatePath));
Template template = cfg.getTemplate(templateName);
File outHtmFile = new File(htmUrl);
Writer out = new BufferedWriter(new OutputStreamWriter(
new FileOutputStream(outHtmFile)));
template.process(paramMap, out);
out.close();
}
private static void pdfHandler(String htmUrl, String pdfUrl)
throws DocumentException, IOException {
File htmFile = new File(htmUrl);
File pdfFile = new File(pdfUrl);
String url = htmFile.toURI().toURL().toString();
OutputStream os = new FileOutputStream(pdfFile);
ITextRenderer renderer = new ITextRenderer();
renderer.setDocument(url);
org.xhtmlrenderer.pdf.ITextFontResolver fontResolver = renderer
.getFontResolver();
// 解决中文支持问题
try {
fontResolver.addFont(TEMPLATE + "fonts/simsun.ttc", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
fontResolver.addFont(TEMPLATE + "fonts/SIMFANG.TTF", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
fontResolver.addFont(TEMPLATE + "fonts/MSYH.TTC", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
fontResolver.addFont(TEMPLATE + "fonts/MSYHBD.TTC", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
fontResolver.addFont(TEMPLATE + "fonts/MSYHL.TTC", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
fontResolver.addFont(TEMPLATE + "fonts/SIMLI.TTF", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
} catch (com.lowagie.text.DocumentException e) {
e.printStackTrace();
}
renderer.getSharedContext().setBaseURL("file:/" + TEMPLATE + "image");
renderer.layout();
try {
renderer.createPDF(os);
} catch (com.lowagie.text.DocumentException e) {
e.printStackTrace();
}
os.close();
}
/**
* @Description: 删除文件
* @Param: [fileUrl]
* @return: void
* @Author: zhangyingbin
* @Date: 2020/6/30
*/
public static void deleteFile(String fileUrl) {
File file = new File(fileUrl);
file.delete();
}
/**
* @Description: 获得pdf总页数
* @Param: [file] file:pdf文件
* @return: int 返回总页码
* @Author: zhangyingbin
* @Date: 2020/6/30
*/
public static int pdfPageCount(File file) throws IOException {
PdfReader pdfReader = new PdfReader(new FileInputStream(file));
int pageCount = pdfReader.getNumberOfPages();
return pageCount;
}
/**
* @Description: pdf 转图片
* @Param: [file, imgType] file:pdf文件,imgType:要生成图片类型(png,jpg等)
* @return: java.util.List<java.lang.String> 返回生成的图片名称集合
* @Author: zhangyingbin
* @Date: 2020/6/30
*/
public static java.util.List<String> pdfToImage(File file, String imgType) throws IOException {
List<String> pdfImages = new ArrayList<>();
PDDocument doc = PDDocument.load(file);
PDFRenderer renderer = new PDFRenderer(doc);
int pageCount = doc.getNumberOfPages();
for (int i = 0; i < pageCount; i++) {
BufferedImage image = renderer.renderImageWithDPI(i, 144); // Windows native DPI
// BufferedImage srcImage = resize(image, 240, 240);//产生缩略图
String pdfImageName = UUID.randomUUID().toString().replaceAll("-", "") + "." + imgType;
ImageIO.write(image, imgType, new File(TEMPLATE + "pdfImage/" + pdfImageName));
pdfImages.add(pdfImageName);
}
return pdfImages;
}
/**
* @Description: 多个pdf合并成一个pdf
* @Param: [files]
* @return: java.io.File
* @Author: zhangyingbin
* @Date: 2020/6/30
*/
public static File mulPdfToOne(List<File> files) throws IOException {
// pdf合并工具类
PDFMergerUtility mergePdf = new PDFMergerUtility();
for (File f : files) {
if (f.exists() && f.isFile()) {
// 循环添加要合并的pdf
mergePdf.addSource(f);
}
}
String onePdfName =TEMPLATE+"mulPdf/"+ UUID.randomUUID().toString().replaceAll("-","")+".pdf";
// 设置合并生成pdf文件名称
mergePdf.setDestinationFileName(onePdfName);
// 合并pdf
mergePdf.mergeDocuments(MemoryUsageSetting.setupMainMemoryOnly());
return new File(onePdfName);
}
}
ftl模板:
<!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">
<head>
<style type="text/css">
/*解决html转pdf文件中文不显示的问题*/
body {
font-family: FangSong;
}
/*设定纸张大小*/
/* A4纸 */
/* @page{size:210mm*297mm} */
@page {
size: a4;
margin-left: 115px;
margin-right: 120px;
margin-top: 120px;
@bottom-center {
content: element(footer)
};
}
#info {
color: black;
font-family: FangSong;
line-height: 150%;
margin: 0;
font-size: 18px;
}
span {
line-height: 150%;
}
.h {
margin-top: 9px;
margin-bottom: 9px;
font-family: Microsoft YaHei;
font-size: 18px;
font-weight: 400;
}
img {
width: 440px;
height: auto;
/*max-width: 440px;*/
/*max-height: 100%;*/
}
.footer {
position: running(footer)
}
#pagenumber:before {
content: counter(page);
}
#pagecount:before {content: counter(pages);
}
</style>
</head>
<body style="font-family: FangSong;line-height:1">
<div class='footer' style="width: 100%">
<p style="text-align: center">-<span id="pagenumber"></span>-</p>
<br/>
</div>
<div style="width:100%;line-height:1">
<div>
<p style="text-align: center;line-height: 115%;font-weight: bold;font-family: SimSun;font-size: 29px;">
活动基本信息</p>
</div>
<div style="line-height: 150%;width:100%;">
<h2 class="h">活动类型</h2>
<p id="info">${activityType!" "}</p>
<h2 class="h">活动参加人数</h2>
<p id="info">${activityPeopleCount!" "}</p>
<h2 class="h">活动举办地所在区</h2>
<p id="info">${activityCounties!" "}</p>
<h2 class="h">活动名称</h2>
<p id="info">${activityName!" "}</p>
<h2 class="h">活动时间</h2>
<p id="info">${activityTime!" "}</p>
<h2 class="h">活动地点</h2>
<p id="info">${activityLocation!" "}</p>
<h2 class="h">活动拟发票数量</h2>
<p id="info">${activityTicketCount!" "}</p>
<h2 class="h">售票座位图</h2>
<div style="width: 440px">
<#list siteImage as image>
<img src='${image!"data:image/png;base64"}' alt="" />
</#list>
</div>
<h2 class="h">安全工作人员数量</h2>
<p id="info">${securityWorkerCount!" "}</p>
<h2 class="h">活动概况</h2>
<div id="info">${activityInfo!" "}</div>
</div>
</div>
</body>
</html>
ftl模板技巧:
模板分页:
.pageNext {
page-break-after: always;
}
<div class='pageNext'></div><!--在需要分页的位置添加-->
页眉页脚设置:
参考:https://cloud.tencent.com/developer/ask/44759
<html>
<head>
<style>
div.header {
display: block; text-align: center;
position: running(header);
}
div.footer {
display: block; text-align: center;
position: running(footer);
}
div.content {page-break-after: always;}
@page {
@top-center { content: element(header) }
}
@page {
@bottom-center { content: element(footer) }
}
</style>
</head>
<body>
<div class='header'>Header</div>
<div class='footer'>Footer</div>
<div class='content'>Page1</div>
<div>Page2</div>
</body>
</html>
页脚添加页码:
@page {
size: a4;
margin-left: 115px;
margin-right: 120px;
margin-top: 120px;
@bottom-center {
content: element(footer) //页脚设置
};
}
.footer {
position: running(footer)
}
#pagenumber:before {
content: counter(page);
}
#pagecount:before {content: counter(pages);
}
<div class='footer' style="width: 100%">
<p style="text-align: center">共<span id="pagecount"></span>页,第<span id="pagenumber"></span>页面</p>
<br/>
</div>
表格内容过长分页换行:
引用 :https://blog.youkuaiyun.com/qq_31980421/article/details/79662988
table{
page-break-inside:auto;-fs-table-paginate:paginate;border-spacing:0;table-layout:fixed; word- break:break- strict; cellspacing:0;cellpadding:0 ;border: solid 1px #ccc; padding: 2px 2px;}
tr { page-break-inside:avoid; page-break-after:auto;}默认换行