支持作者
最便宜的卫生纸
一 简介
因公司业务需求需要做一个通过模版导出PDF的功能,模版是word文档,模版中包含图片,文字,水印,分页。遇到这种没做过的功能,没什么可说的,直接问度娘。结果度娘给不了我想要的,只好去github上下了一些相关的项目,通过筛选和实验终于找到了不错的代码,但是在使用过程中也是踩了无数坑,随记录下来。
二 思路
通过模版导出PDF的方法有很多,但是思路都大致相同,总结起来就是 1 渲染模版 2 导出模版。以为之前使用过freemarker,所以我这里渲染模版的技术用的是freemarker,然后在使用itextpdf将渲染好的模版导出。
三 踩过的坑
1 Win下中文不显示 2 图片不显示 3 Linux下中文不显示 4 图片样式跑偏 5 分页加水印
四 上代码
4.1 准备工作
4.11首先准备好一个word模版,如下图所示
4.1.2 将word模版通过wps或其他工具另存为 转为HTML模版.
转为html后 ${}会被 分开,通过在线格式化工具排版HTML,将${}之间多余的代码删掉,重新恢复到${},批量操作即可.
4.1.3 新建一个ftl文件,将HTML代码拷贝进去 如下图所示
4.1.4 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.2</version>
</dependency>
<dependency>
<groupId>com.itextpdf.tool</groupId>
<artifactId>xmlworker</artifactId>
<version>5.4.1</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</artifactId>
<version>9.0.3</version>
</dependency>
4.2 写代码 新建一个PDF工具类
4.2.1 第一步 渲染模版 代码如下
//获取模板并填充数据
public static String getContent(String fileName,Object data) throws Exception{
// 创建一个Configuration对象
Configuration configuration = new Configuration(Configuration.getVersion());
Configuration config = new Configuration(Configuration.getVersion());//FreeMarker配置
// 告诉config对象模板文件存放的路径。
configuration.setDirectoryForTemplateLoading(new File(PdfUtil.class.getClassLoader().getResource("templates/").getPath()));
// 设置config的默认字符集。一般是utf-8
configuration.setDefaultEncoding("utf-8");
//从config对象中获得模板对象。需要制定一个模板文件的名字。
Template template = configuration.getTemplate(fileName+".ftl");
StringWriter writer = new StringWriter();
//模版和数据匹配
template.process(data, writer);
writer.flush();
String html = writer.toString();
return getImgs(html);
}
4.2.2 第二步 替换模版中的图片路径 代码如下
//替换html中的图片路径
private static String getImgs(String content) {
String img = "";
Pattern p_image;
Matcher m_image;
String str = "";
String[] images = null;
String regEx_img = "(<img.*src\\s*=\\s*(.*?)[^>]*?>)";
p_image = Pattern.compile(regEx_img, Pattern.CASE_INSENSITIVE);
m_image = p_image.matcher(content);
if(m_image!=null){
while (m_image.find()) {
img = m_image.group();
Matcher m = Pattern.compile("src\\s*=\\s*\"?(.*?)(\"|>|\\s+)")
.matcher(img);
if(m!=null){
while (m.find()) {
String tempSelected = m.group(1);
str = tempSelected;
String classpath= PathUtil.getRootPath();
String ulr=classpath.substring(5,classpath.length());
content=content.replace(str,ulr+"templates/"+str);
}
}
}
}
return content;
}
工具类 PathUtil
/**
* @author:change
* @description:获取绝对路径
* @date:2018/6/28 0028
*/
public class PathUtil {
static String path1;
static String download;
public static String getRootPath() {
String line = File.separator;
String path = Thread.currentThread().getContextClassLoader().getResource("").toString();
//windows下
if ("\\".equals(line)) {
/* path = path.replace("/", "\\"); // 将/换成\\*/
path1 = path;
}
//linux下
if ("/".equals(line)) {
path = path.replace("\\", "/");
path1 = path;
}
return path1;
}
public static void main(String[] args) {
System.out.println(getRootPath());
System.out.println(PdfUtil.class.getClassLoader().getResource("").getPath());
}
}
4.2.3 设置pdf字符集 代码如下
/**
* 设置字符集
*/
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{
BaseFont bfChinese = BaseFont.createFont("STSong-Light",
"UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
FontChinese = new Font(bfChinese, 12, Font.NORMAL);
}catch (Exception e){
e.printStackTrace();
}
if(FontChinese==null){
FontChinese = super.getFont(fntnames, encoding, size, style);
}
return FontChinese;
}
}
4.2.4 生产PDF并下载 代码如下
/**
* 生成PDF到输出流中(ServletOutputStream用于下载PDF)
* @param templateName ftl模板名称
* @param data 输入到FTL中的数据
* @param response HttpServletResponse
* @return
*/
public static OutputStream exportToResponse(String templateName, Object data,
HttpServletResponse response)throws Exception{
TEMPLATENAMES=templateName;
String html= getContent(templateName,data);
response.reset();
response.setContentType("application/pdf;charset=utf-8");
response.setHeader("Content-Disposition", "attachment;filename="+ new String((templateName + ".pdf").getBytes(), "iso-8859-1"));
try{
OutputStream out = null;
ITextRenderer render = null;
out = response.getOutputStream();
//设置文档大小
Document document = new Document(PageSize.A4);
PdfWriter writer = PdfWriter.getInstance(document, out);
writer.setStrictImageSequence(true);
//输出为PDF文件
convertToPDF(writer,document,html);
return out;
}catch (Exception ex){
System.out.println(ex);
throw new SimpleException("PDF export to response fail");
}
}
/**
* @description PDF文件生成
*/
private static void convertToPDF(PdfWriter writer,Document document,String htmlString)throws Exception{
document.open();
MyFontsProvider fontProvider = new MyFontsProvider();
fontProvider.addFontSubstitute("lowagie", "garamond");
fontProvider.setUseUnicode(true);
CssAppliers cssAppliers = new CssAppliersImpl(fontProvider);
HtmlPipelineContext htmlContext = new HtmlPipelineContext(cssAppliers);
htmlContext.setTagFactory(Tags.getHtmlTagProcessorFactory());
XMLWorkerHelper.getInstance().getDefaultCssResolver(true);
if(TEMPLATENAMES.equals("合同")||TEMPLATENAMES.equals("合同模版"))
writer.setPageEvent(new BackGroundImage());
if(TEMPLATENAMES.equals("设计合同"))
writer.setPageEvent(new SheJiGroundImage()); //这里初始化分页监听类
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 SimpleException("PDF文件生成异常");
}finally {
document.close();
}
}
4.2.5 设置水印和分业 新建一个类继承pdfPageEventHelper ,该类用来监听pdf分页 代码如下
package com.rxjy.commons.pdf;
import com.itextpdf.text.*;
import com.itextpdf.text.pdf.PdfPageEventHelper;
import com.itextpdf.text.pdf.PdfWriter;
import com.rxjy.constructionContract.myutils.PathUtil;
import java.io.IOException;
/**
* @author:change
* @description:监听分页
* @date:2018/7/2 0002
*/
public class BackGroundImage extends PdfPageEventHelper {
String classpath= PathUtil.getRootPath();
String ulr=classpath.substring(5,classpath.length());
@Override
public void onEndPage(PdfWriter writer, Document document) {
try {
if(document.getPageNumber()==1){ //第一页
Image image = Image.getInstance(ulr+"templates/合同.files/合同39.png"); //甲方
Image image1 = Image.getInstance(ulr+"templates/合同.files/合同242.png"); //乙方
/* 设置图片的大小 */
image.scaleAbsolute(128, 128);
image1.scaleAbsolute(128, 128);
/* 设置图片的位置 */
image.setAbsolutePosition(70,600);
image1.setAbsolutePosition(300,600);
document.add(image);
document.add(image1);
//二维码
Image code = Image.getInstance(ulr+"templates/合同.files/合同二维码.png");
code.scaleAbsolute(50,50);
code.setAbsolutePosition(500,750);
document.add(code);
}
if(document.getPageNumber()==4){ //第四页
Image image = Image.getInstance(ulr+"templates/合同.files/合同39.png"); //甲方
Image image1 = Image.getInstance(ulr+"templates/合同.files/合同242.png"); //乙方
/* 设置图片的大小 */
image.scaleAbsolute(128, 128);
image1.scaleAbsolute(128, 128);
/* 设置图片的位置 */
image.setAbsolutePosition(70,350);
image1.setAbsolutePosition(300,350);
document.add(image);
document.add(image1);
}
//分页水印
Image yejiao = Image.getInstance(ulr+"templates/合同.files/合同"+document.getPageNumber()+".png");
yejiao.scaleAbsolute(144,44);
yejiao.setAbsolutePosition(450,30);
document.add(yejiao);
} catch (BadElementException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (DocumentException e) {
e.printStackTrace();
}
}
}
4.2.6 调用PDF工具类 代码如下
@ApiOperation("导出合同")
@GetMapping("/importPdfByTemplates")
void importPdfByTemplates(HttpServletResponse response,String rwdid,String templateName)throws Exception{
Map<String,Object> map=heTongService.htInfoMap(rwdid); //该数据的key对应模版中的${}中的字段
PdfUtil.exportToResponse(templateName,map,response);
}
五 测试
导出的PDF若遇到样式上的细微问题。可以在ftl中使用html代码调整 咨询相关问题请加群.
效果图