最近项目上有要求,需要生成一个可供客户下载的pdf或者图片的许可协议,这个pdf/图片里面的内容需要是用户相关的内容,也就是内容是可变的,不过其他的样式是统一的,我也因此找了相关的功能包,发现可以使用
xhtmlrenderer+freemarker
完成这个需求,此次仅制作了pdf相关教程供大家参考,至于转换图片可以参考
基于xhtmlrenderer+freemarker的HTML转图片方法_键盘满配的博客-CSDN博客
因pdf的内容是可变的,所以单纯的html是无法满足要求的,搜索了一下发现可以使用freemarker,
freemarker是一种模板引擎,可以基于模板和要改变的数据, 用来生成输出文本,这个是很适合我们这边需求的
引入freemarker
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.28</version>
</dependency>
html文件:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<title>Title</title>
<style>
body{
font-family:SimHei;
}
.red{
color: red;
}
</style>
</head>
<body>
<h1>
许可协议
</h1>
<div class="red">
被许可方:${company}
</div>
<div class="red">
许可产品:${product}
</div>
<div class="red">
产品授权类型:${type}
</div>
<div class="red">
产品使用许可范围:${scope}
</div>
<div class="red">
产品授权用户人数:${count}
</div>
<div class="red">
产品授权有效期:${validity}
</div>
<div class="red">
许可协议编号:${license}
</div>
</body>
</html>
可以看到当前html中增加了变量${},可以利用这些变量生成可变的pdf文件,用来填充对应文字
一开始时因为没有使用图片而且自己写了一个简单的html,当时使用itext,效果也是挺好的,后来在html中加上了一些css样式和图片发现无法创建在pdf中,然后找了一下,发现xhtmlrenderer可以支持html中的样式和背景图导入
xhtmlrenderer引入:
<dependency>
<groupId>org.xhtmlrenderer</groupId>
<artifactId>flying-saucer-pdf</artifactId>
<version>9.1.16</version>
<exclusions>
<exclusion>
<groupId>bouncycastle</groupId>
<artifactId>bcmail-jdk14</artifactId>
</exclusion>
<exclusion>
<groupId>bouncycastle</groupId>
<artifactId>bcprov-jdk14</artifactId>
</exclusion>
<exclusion>
<groupId>bouncycastle</groupId>
<artifactId>bctsp-jdk14</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcmail-jdk14</artifactId>
<version>1.38</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bctsp-jdk14</artifactId>
<version>1.38</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk14</artifactId>
<version>1.38</version>
</dependency>
<dependency>
<groupId>org.xhtmlrenderer</groupId>
<artifactId>flying-saucer-pdf-itext5</artifactId>
<version>9.1.16</version>
</dependency>
测试类:
private String agreementDir = "D:/java/agreement/"; //导出位置
@Test
public void test3() throws IOException, URISyntaxException, DocumentException {
Path basePath = Paths.get(agreementDir);
if (!Files.exists(basePath) || !Files.isDirectory(basePath)) {
Files.createDirectory(basePath);
}
agreementDir += File.separator + "10095";
basePath = Paths.get(agreementDir);
if (!Files.exists(basePath) || !Files.isDirectory(basePath)) {
Files.createDirectory(basePath);
}
Map<String, Object> paramMap = new HashMap<>();
paramMap.put("company", "公司");
paramMap.put("product", "产品名称");
paramMap.put("type", "许可证授权");
paramMap.put("scope", "使用范围");
paramMap.put("count", "购买数量");
paramMap.put("validity", "有效期");
paramMap.put("license", "购买序列码");
paramMap.put("createTime", DateFormatUtils.format(new Date(), "yyyy年MM月dd日"));
//freemarker位置
String freemarker = "D:/java/freemarker/";
File dir = new File(freemarker);
String content = PDFUtils.freeMarkerRender(paramMap, "license.html", dir);
PDFEncryption pdfEncryption = new PDFEncryption();
//加密
// pdfEncryption.setUserPassword("123456".getBytes());
//只读
pdfEncryption.setAllowedPrivileges(PdfWriter.ALLOW_PRINTING);
pdfEncryption.setEncryptionType(PdfWriter.STANDARD_ENCRYPTION_128);
long l = System.currentTimeMillis();
List<String> fontPaths = new ArrayList<>();
//字体
fontPaths.add(freemarker + File.separator+ "font" + File.separator + "msyh.ttf");
//需要导入图片位置
String imgPath = "D:/java/freemarker/";
PDFUtils.createPDF(content, agreementDir + l + ".pdf",
imgPath, fontPaths, pdfEncryption);
}
PDFUtils工具类
@Slf4j
public class PDFUtils {
private static Configuration freemarkerCfg;
static {
freemarkerCfg =new Configuration();
}
/**
* @Description pdf生成工具
* @Date 10:42 2020/10/26
* @Param inputFile 输入的文件模板
* @Param outputFile 输出文件位置
* @Param imgPath 所需图片的路径
* @Param fontPath 字体选择
* @Param pdfEncryption 生成的pdf权限
* @return void
**/
public static void createPDF(String inputFile, String outputFile, String imgPath, List<String> fontPaths, PDFEncryption pdfEncryption) throws IOException, DocumentException {
OutputStream os = new FileOutputStream(outputFile);
ITextRenderer renderer = new ITextRenderer();
renderer.setDocumentFromString(inputFile);
//解决图片的相对路径问题
renderer.getSharedContext().setBaseURL("file:" + imgPath);
//pdf权限控制
// PDFEncryption pdfEncryption = new PDFEncryption();
//加密
// pdfEncryption.setUserPassword("123456".getBytes());
//只读
// pdfEncryption.setAllowedPrivileges(PdfWriter.ALLOW_PRINTING);
// pdfEncryption.setEncryptionType(PdfWriter.STANDARD_ENCRYPTION_128);
renderer.setPDFEncryption(pdfEncryption);
//解决中文支持问题
ITextFontResolver fontResolver = renderer.getFontResolver();
for (String fontPath : fontPaths) {
fontResolver.addFont(fontPath, BaseFont.IDENTITY_H, BaseFont.EMBEDDED);
}
renderer.layout();
renderer.createPDF(os);
os.close();
}
public static String freeMarkerRender(Map<String, Object> data, String htmlTmp, File dir) throws IOException {
freemarkerCfg.setDirectoryForTemplateLoading(dir);
Writer out = new StringWriter();
try {
Template template = freemarkerCfg.getTemplate(htmlTmp, "utf-8");
template.process(data, out);
out.flush();
return out.toString();
} catch (Exception e) {
log.error(e.getMessage());
} finally {
try {
out.close();
} catch (IOException ex) {
log.error(ex.getMessage());
}
}
return null;
}
}
之后基本可以完成pdf导出功能,不过需要注意的是,字体使用需要防止侵权,导入字体时需要是本公司可用的字体,否则哪天被告了可就郁闷了
关于html的创建需要注意一点,freemarker对html的模板识别是强制格式的,不能像平时使用html时可以缺少</img>等尾符,除了<head></head>中的<meta />,其他的需要严格对称,否则会导致freemarker识别错误
还有一点,css样式需要在html中,不能使用引入方式
本次使用的pdf导出只能做简单的html转pdf,实际上我们后来也没有用这个方案,毕竟要求还是挺高的,而且每次要有一个新的html转pdf会非常麻烦,然后我就找了一下市面上有没有相关工具,然后发现了这个:
可以随时调用他们的api把我们需要的页面转成各种格式,如pdf/图片之类的都行,而且也有文档的在线预览(也就是可以把当前各种格式文件转成html在浏览器中查看,这个功能感觉很实用),另一个是在线编辑,这个功能感觉很强大,可以把文档上传后直接在页面上编辑,可以实时保存和分享,如果需要更专业的功能可以上去看看,毕竟简单的xhtmlrenderer+freemarker能做的功能还是很少的