Java生成pdf,兼容富文本内容
使用技术,freemark + jsoup + flying saucer
使用freemark替换模板文件中指定的占位符,生成一个完整的的html字符串,
使用jsoup对html进行格式化,
使用flying saucer 将整个html进行pdf转换(flying saucer对css的支持不是很完整,存在连续中文换行问题,需要在转换的时候特殊处理)
- maven地址
<!--freemarker-->
<!--https://mvnrepository.com/artifact/org.freemarker/freemarker-->
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.23</version>
</dependency>
<!--JavaHTMLParser-->
<!--https://mvnrepository.com/artifact/org.jsoup/jsoup-->
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.10.2</version>
</dependency>
<dependency>
<groupId>org.xhtmlrenderer</groupId>
<artifactId>flying-saucer-pdf</artifactId>
<version>9.0.8</version>
</dependency>
<dependency>
<groupId>org.xhtmlrenderer</groupId>
<artifactId>flying-saucer-pdf-itext5</artifactId>
<version>9.1.5</version>
</dependency>
- 模板文件生成
- 先将wrod的格式内容定义好,如果需要插入参数的地方以${xxx}为表示,例:${product}
模板例子:
2. 将word文档另存为 “筛选过的网页(*.htm;*.html)” 的文件,打开该文件检查每个变量(${product})是否完整,有可能在${}中出现其他代码,需要删除。
3. 检查文件没问题之后,将文件改成后缀为ftl的文件,引入到项目中,和生成的word的模板文件不同,html模板不需要引入图片字段占位符,html可以直接通过img标签展示图片
2. 获取模板文件,生成html
freemark获取模板
下面这种方式能获取模板,但是在项目打包之后无法获取jar包内的文件
Configuration configuration=newConfiguration(Configuration.getVersion());
configuration.setDefaultEncoding(StandardCharsets.UTF_8.toString());
configuration.setDirectoryForTemplateLoading(newFile(templatePath));
Template template=configuration.getTemplate("xxx.ftl");
通过流的形式直接创建模板对象
Configuration configuration=newConfiguration(Configuration.getVersion());
configuration.setDefaultEncoding(StandardCharsets.UTF_8.toString());
configuration.setDirectoryForTemplateLoading(newFile(templatePath));
InputStream inputStream=newFileInputStream(newFile(templatePath+"/"+templateName));
InputStreamReader inputStreamReader=newInputStreamReader(inputStream,StandardCharsets.UTF_8);
Template template=newTemplate("xxx.ftl",inputStreamReader,configuration);
- 通过template替换ftl中的占位符,再将结果写入字符流中,返回结果字符串
dataMap为Map格式,占位符为key,结果值为value
StringWriter stringWriter = new StringWriter();
BufferedWriter wirter = new BufferedWriter(stringWriter);
template.process(dataMap,writer);
return writer.toString();
- flying saucer对html有严格的检查,必须要以<a></a>的形式存在,使用 jsoup 对html进行格式化操作
Document doc = Jsoup.parse(htmlStr);
Elements imageElements = doc.select("img");
For(Element ele : Elements ) {
…
}
Jsoup对html有很好的支持,可以对一些代码添加、修改样式。
flying saucer 是基于Itext的包,itext存在连续中文不换行的问题,可以通过 jsoup 修改所有的文字,在文字后面加上空格,这样就能修复无法自动换行的问题
- 加载中文字体
flying saucer 不支持中文,需要加载中文字体。
在ftl的模板文件中的body标签引入字体
例: <body style="font-family:'Arial Unicode MS'">
在代码中字体文件:
ITextRenderer renderer = new ITextRenderer();
// mac
renderer .getFontResolver().addFont("/library/fonts/Arial Unicode.ttf", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
// linux:
renderer .getFontResolver().addFont("/usr/share/fonts/TTF/ARIALUNI.TTF", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
// windows:
renderer .getFontResolver().addFont("C:/Windows/Fonts/ARIALUNI.TTF", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
在项目中文字可以放在项目目录里面
ClassPathResource resource = new ClassPathResource("ARIALUNI.TTF");
renderer .getFontResolver().addFont(resource.getPath(), BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
源码如下:
<<CreateHtmlByFreemarker.java>>
package org.java.export.plugin.example;
import java.io.*;
import java.util.HashMap;
import java.util.Map;
import freemarker.core.ParseException;
import freemarker.template.Configuration;
import freemarker.template.MalformedTemplateNameException;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import freemarker.template.TemplateNotFoundException;
public class CreateHtmlByFreemarker {
public static void main(String[] args) {
String str = getHtmlStr();
System.out.println(str);
}
public static String getHtmlStr(){
// step1 创建freeMarker配置实例
Configuration configuration = new Configuration();
StringWriter stringWriter = new StringWriter();
BufferedWriter writer = new BufferedWriter(stringWriter);
try {
// step2 获取模版路径
String templatePath = Class.class.getResource("/ftl").getPath();
templatePath = java.net.URLDecoder.decode(templatePath,"utf-8");//这里我的路径有空格添加此处理
configuration.setDirectoryForTemplateLoading(new File(templatePath));
StringBuilder sb = new StringBuilder();
sb.append("<div>");
sb.append("<img style='height:100px;200px;display:block;' src='https://dss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=2534506313,1688529724&fm=26&gp=0.jpg' />");
sb.append("</br><span>wesley 演示 导出富文本!@@#######¥¥%%%%………………&&&**~~~~~~&&&&&&&&、、、、、、、、</span>");
sb.append("</br><span>----多图分割线---</span>");
sb.append("<img style='height:100px;200px;display:block;' src='https://dss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=2534506313,1688529724&fm=26&gp=0.jpg' />");
sb.append("</br><span>中国梦,幸福梦!</span>");
sb.append("</div>");
sb.append("<table style='border: 0.5px solid #000' border='0' cellspacing='0' cellpadding='0'>");
sb.append("<tr>");
sb.append(" <th style='border: 0.5px solid #000'>Month</th>");
sb.append("<th style='border: 0.5px solid #000'>Savings</th>");
sb.append("</tr>");
sb.append("<tr>");
sb.append("<td style='border: 0.5px solid #000'>January</td>");
sb.append("<td style='border: 0.5px solid #000'>$100</td>");
sb.append("</tr>");
sb.append("</table>");
// step3 创建数据模型
Map<String, Object> dataMap = new HashMap<String, Object>();
dataMap.put("name", "wesley");
dataMap.put("datetime","2017-05-10");
dataMap.put("title","演示demo");
dataMap.put("context1", sb.toString());
dataMap.put("context2", sb.toString());
dataMap.put("context3", sb.toString());
dataMap.put("context4", sb.toString());
dataMap.put("context5", sb.toString());
dataMap.put("context6", sb.toString());
// step4 加载模版文件
Template template = configuration.getTemplate("title.ftl");
// step5 生成数据
template.process(dataMap, writer);
String htmlStr = stringWriter.toString();
System.out.println("^^^^^^^^^^^^^^^^^^^^^^^^user.ftl 文件创建成功 !");
return htmlStr;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
<<CreatePDFByHtml.java>>
package org.java.export.plugin.example;
import com.itextpdf.text.pdf.BaseFont;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.xhtmlrenderer.pdf.ITextFontResolver;
import org.xhtmlrenderer.pdf.ITextRenderer;
import java.io.*;
public class CreatePDFByHtml {
public static void main(String[] args) throws IOException {
String pdfPath = "/Users/liqi/Desktop/1.pdf";
String htmlFilePath = "/Users/liqi/Desktop/1586671323385.html";
createPDFByHtml( pdfPath,htmlFilePath);
}
/**
* 该方法用来将指定的word文件转换成pdf文件(使用flying saucer技术)
* @param pdfPath:生成后的pdf所在目录,包括目录+pdf名称+.+pdf
* @param htmlFilePath:需要进行转换的html文件所在目录,包括目录+html名称+.+html
* */
public static boolean createPDFByHtml(String pdfPath, String htmlFilePath){
boolean result = false;
//1、判断给定的文件是否是html文件:是htm格式结尾,或者以html格式结尾
if(htmlFilePath.toUpperCase().endsWith(".HTM") ||
htmlFilePath.toUpperCase().endsWith(".HTML")){//两种格式都是扫描文件格式
try {
OutputStream os = new FileOutputStream(pdfPath);
ITextRenderer renderer = new ITextRenderer();
String str = CreateHtmlByFreemarker.getHtmlStr();
System.out.println(str);
System.out.println("--------------=============");
Document doc = Jsoup.parse(str);
System.out.println(doc.html());
Elements elements = doc.select("img");
int i=0;
for (Element element : elements){
element.attr("id",i+"");
i++;
}
String content=doc.html();
content = content.replace(" "," ");
content = content.replace("<meta http-equiv="Content-Type" content="text/html; charset=utf-8">","<meta http-equiv="Content-Type" content="text/html; charset=utf-8"></meta>");
content = content.replace("(filtered)">","(filtered)"></meta>");
content = content.replaceAll("<br>","<br></br>");
for (Element element : elements){
String startStr = element.outerHtml();
String endStr = element.outerHtml()+"</img>";
content = content.replace(startStr,endStr);
}
ITextFontResolver fontResolver = renderer.getFontResolver();
// fontResolver.addFont("/Users/liqi/ideaWorkspace/java-export-word-plugin-master/PluginExample/src/main/resources/ftl/simhei.ttf", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
//fontResolver.addFont("/Users/liqi/ideaWorkspace/java-export-word-plugin-master/PluginExample/src/main/resources/ftl/simsun.ttc", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
/* // mac
fontResolver.addFont("/library/fonts/Arial Unicode.ttf",
BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
// linux:
fontResolver.addFont("/usr/share/fonts/TTF/ARIALUNI.TTF",
BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
// windows:
fontResolver.addFont("C:/Windows/Fonts/ARIALUNI.TTF",
BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED); */
// 解决中文支持问题
fontResolver.addFont("/library/fonts/Arial Unicode.ttf",
BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
renderer.setDocumentFromString(content);
renderer.layout();
renderer.createPDF(os);
os.close();
result = true;
} catch (Exception e) {
result = false;
e.printStackTrace();
}
}else{
result = false;
}
return result ;
}
}
还有另一种方案也能实现该功能
使用Itext绘制pdf表格,前端使用canvas将富文本生成图片,在使用IText将图片插入到指定位置中,这也是可以实现的