Spring Boot Freemark HTML 生成 PDF、生成水印Logo、docx文件生成PDF,Jar包运行可读取模板文件、字体文件
Spring Boot Freemark HTML 生成 PDF,Jar包运行可读取模板文件、字体文件
·
用于通过模板生成PDF,在项目中生成个人授权协议函、个人电子保单、流水报表,数据报表等,将HTML静态模板写出来后,将数据替换成动态数据即可。
<!-- html2pdf -->
<dependency>
<groupId>org.xhtmlrenderer</groupId>
<artifactId>flying-saucer-pdf</artifactId>
<version>9.1.16</version>
</dependency>
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
<version>5.5.13</version>
</dependency>
<!-- freemarker -->
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.31</version>
</dependency>
<!-- docx4j docx2pdf -->
<dependency>
<groupId>org.docx4j</groupId>
<artifactId>docx4j</artifactId>
<version>6.1.2</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.docx4j</groupId>
<artifactId>docx4j-export-fo</artifactId>
<version>6.0.0</version>
</dependency>
templates.ftl 放入resource/templates文件目录中
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title>Title</title>
<style>
@page {
size: 210mm 290mm; /* size: 210mm 297mm; 设置纸张大小:A4(210mm 297mm)、A3(297mm 420mm) 横向则反过来 */
/*margin-bottom: 1cm;*/
/*padding: 1em;*/
/*页眉*/
/*@top-center {*/
/* content: counter(page);*/
/* font-family: SimSun;*/
/* font-size: 15px;*/
/* color:#000;*/
/*};*/
/*@top-right{*/
/* background: url("https://dpxdata.oss-cn-beijing.aliyuncs.com/upload/kbt/null_1660715685801.png") top right no-repeat;*/
/* width: 195px;height: 50px;*/
/* content: "logo";*/
/* font-family: SimSun;*/
/* font-size: 15px;*/
/* !*color:#000;*!*/
/*};*/
/*页脚*/
@bottom-center{
content: counter(page);
font-family: SimSun;
font-size: 15px;
color:#000;
};
/*@bottom-right{*/
/* content: "第" counter(page) "页 共" counter(pages) "页";*/
/* font-family: SimSun;*/
/* font-size: 15px;*/
/* color:#000;*/
/*};*/
}
* {
page-break-inside: always;
}
</style>
</head>
<body style="font-family: SimSun; font-size: 14px">
<div style="">
${text!''}
</div>
</body>
</html>
simsun.ttc 为字体文件
1.HTML 生成 PDF
HttpHeaders 写法
@GetMapping("/pdf")
public ResponseEntity<?> export(HttpServletResponse response) {
try {
HttpHeaders headers = new HttpHeaders();
Map<String, Object> dataMap = new HashMap<>(16);
dataMap.put("text", "张三");
String htmlStr = FreeMarkUtils.freemarkerRender(dataMap, "templates.ftl");
byte[] pdfBytes = FreeMarkUtils.createPDF(htmlStr, "simsun.ttc");
if (pdfBytes != null && pdfBytes.length > 0) {
String fileName = System.currentTimeMillis() + (int) (Math.random() * 90000 + 10000) + ".pdf";
headers.setContentDispositionFormData("attachment", fileName);
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
return new ResponseEntity<byte[]>(pdfBytes, headers, HttpStatus.OK);
}
} catch (Exception e) {
e.printStackTrace();
}
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
return new ResponseEntity<>("{ \"code\" : \"404\", \"message\" : \"not found\" }", headers, HttpStatus.NOT_FOUND);
}
Response 写法
@GetMapping("/pdf")
public void export(HttpServletResponse response) {
InputStream inputStream = null;
try {
// 设置参数
Map<String, Object> dataMap = new HashMap<>(16);
dataMap.put("text", "张三");
String htmlStr = FreeMarkUtils.freemarkerRender(dataMap, "template.ftl");
byte[] data = FreeMarkUtils.createHtml2Pdf(htmlStr, "simsun.ttc");
// 设置文件名
String fileName = System.currentTimeMillis() + (int) (Math.random() * 90000 + 10000) + ".pdf";
// 浏览器直接下载文件
generateFile(response, data, fileName);
// 获取输入流
inputStream = new ByteArrayInputStream(data);
// 输入流保存阿里云
final String url = OssBootUtil.upload(inputStream, "upload/agree/" + fileName);
log.info(url);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (inputStream != null) {
inputStream.close();
}
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
@GetMapping("/doc")
public void doc(HttpServletResponse response) {
Map<String, Object> dataMap = new HashMap<>();
dataMap.put("name", "zhangsan");
String fileName = System.currentTimeMillis() + (int) (Math.random() * 90000 + 10000) + ".doc";
generateFile(response, FreeMarkUtils.freemarkerRender(dataMap, "test.ftl").getBytes(StandardCharsets.UTF_8), fileName);
}
@GetMapping("/doc2pdf")
public void doc2pdf(HttpServletResponse response) {
Map<String, Object> dataMap = new HashMap<>();
dataMap.put("name", "张三");
final byte[] data = FreeMarkUtils.createDocx2Pdf(dataMap, "document.ftl", "templates/test3.docx");
String fileName = System.currentTimeMillis() + (int) (Math.random() * 90000 + 10000) + ".pdf";
generateFile(response, data, fileName);
}
/**
* 下载文件
* @param response 相应
* @param data 数据
* @param fileName 文件名
*/
private void generateFile(HttpServletResponse response, byte[] data, String fileName) {
response.setHeader("content-Type", "application/octet-stream");
response.setCharacterEncoding("utf-8");
response.setHeader("Content-disposition", "attachment;filename=" + fileName);
try {
response.getOutputStream().write(data);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
response.getOutputStream().close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
2. docx 文件生成PDF
新建word 2007 test3.docx
复制文件,重命名为test3.zip,解压后
复制word/document.xml文件到项目中 document.ftl,更改文件内容变量
导出PDF工具类
package com.dpxdata.backend.report;
import cn.hutool.core.io.file.FileMode;
import com.itextpdf.text.*;
import com.itextpdf.text.Image;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.pdf.*;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateExceptionHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.WordUtils;
import org.docx4j.Docx4J;
import org.docx4j.convert.out.FOSettings;
import org.docx4j.fonts.IdentityPlusMapper;
import org.docx4j.fonts.Mapper;
import org.docx4j.fonts.PhysicalFonts;
import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.stereotype.Component;
import org.springframework.ui.freemarker.FreeMarkerTemplateUtils;
import org.springframework.ui.freemarker.SpringTemplateLoader;
import org.xhtmlrenderer.pdf.ITextFontResolver;
import org.xhtmlrenderer.pdf.ITextRenderer;
import javax.swing.*;
import java.awt.*;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.Enumeration;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;
/**
* @Description: pdf 导出工具类
* @Author: LiSaiHang
* @Date: 2022/1/7 11:30 上午
*/
@Component
@Slf4j
public class FreeMarkUtils {
private FreeMarkUtils() {
}
private volatile static Configuration configuration;
static {
if (configuration == null) {
synchronized (FreeMarkUtils.class) {
if (configuration == null) {
configuration = new Configuration(Configuration.VERSION_2_3_28);
}
}
}
}
/**
* freemarker 引擎渲染 html
*
* @param dataMap 传入 html 模板的 Map 数据
* @param fileName 模板文件名称
* eg: "templates/template.ftl"
* @return
*/
public static String freemarkerRender(Map<String, Object> dataMap, String fileName) {
configuration.setDefaultEncoding("UTF-8");
configuration.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
try {
// TODO Jar包运行时,是无法读取classpath下资源文件,可以通过 SpringTemplateLoader 读取资源文件目录
final SpringTemplateLoader templateLoader = new SpringTemplateLoader(new DefaultResourceLoader(), "classpath:templates");
configuration.setTemplateLoader(templateLoader);
configuration.setLogTemplateExceptions(false);
configuration.setWrapUncheckedExceptions(true);
Template template = configuration.getTemplate(fileName);
return FreeMarkerTemplateUtils.processTemplateIntoString(template, dataMap);
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 使用 iText 生成 PDF 文档
*
* @param htmlTmpStr html 模板文件字符串
* @param fontFile 所需字体文件(相对路径+文件名)
*/
public static byte[] createHtml2Pdf(String htmlTmpStr, String fontFile) {
ByteArrayOutputStream outputStream = null;
byte[] result = null;
// 生成 字体临时文件
// TODO jar包运行后,是无法直接读取 classpath 下的资源文件,需要通过文件流读取,生成临时文件
final File tempFile = getTempFile(fontFile);
// TODO jar包运行后,是无法直接读取 classpath 下的资源文件,需要通过文件流读取,生成临时文件
try {
outputStream = new ByteArrayOutputStream();
ITextRenderer renderer = new ITextRenderer();
renderer.setDocumentFromString(htmlTmpStr);
ITextFontResolver fontResolver = renderer.getFontResolver();
// 解决中文支持问题,需要所需字体(ttc)文件
fontResolver.addFont(tempFile.getAbsolutePath(), BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
renderer.layout();
renderer.createPDF(outputStream);
// 对Html2Pdf 的文件插入水印图片
final String fileName = System.currentTimeMillis() + (int) (Math.random() * 90000 + 10000) + ".pdf";
addWaterMark(outputStream.toByteArray(), fileName);
final ByteArrayOutputStream fileOutputStream = getFileOutputStream(new File(fileName));
result = fileOutputStream.toByteArray();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (com.lowagie.text.DocumentException e) {
e.printStackTrace();
} finally {
try {
if (outputStream != null) {
outputStream.flush();
outputStream.close();
}
if (tempFile.exists()) {
tempFile.delete();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return result;
}
/**
* freemark生成word----docx格式
*
* @param dataMap 数据源
* @param templateFile document.xml模板的文件名
* @param docxFile docx模板的文件名
* @return 生成的文件路径
*/
public static byte[] createDocx2Pdf(Map<String, Object> dataMap, String templateFile, String docxFile) {
//word输出流
ZipOutputStream zipout = null;
//docx格式的word文件路径
//输出word文件路径和名称
String fileName = "applyWord_" + System.currentTimeMillis() + ".docx";
// 生成docx临时文件
final File tempPath = new File(fileName);
// 获取docx模板
final File docxTempFile = getTempFile(docxFile);
try {
//freeMark根据模板生成内容xml
// 获取 document.ftl 输入流
final String str = freemarkerRender(dataMap, templateFile);
ByteArrayInputStream documentInput = new ByteArrayInputStream(str.getBytes(StandardCharsets.UTF_8));
final ZipFile zipFile = new ZipFile(docxTempFile);
System.out.println(zipFile.getName());
Enumeration<? extends ZipEntry> zipEntrys = zipFile.entries();
// docx文件输出流
zipout = new ZipOutputStream(new FileOutputStream(tempPath));
//循环遍历主模板docx文件,替换掉主内容区,也就是上面获取的document.xml的内容
//------------------覆盖文档------------------
int len = -1;
byte[] buffer = new byte[1024];
while (zipEntrys.hasMoreElements()) {
ZipEntry next = zipEntrys.nextElement();
final InputStream is = zipFile.getInputStream(next);
if (next.toString().indexOf("media") < 0) {
zipout.putNextEntry(new ZipEntry(next.getName()));
if ("word/document.xml".equals(next.getName())) {
//写入填充数据后的主数据信息
if (documentInput != null) {
while ((len = documentInput.read(buffer)) != -1) {
zipout.write(buffer, 0, len);
}
}
} else {//不是主数据区的都用主模板的
while ((len = is.read(buffer)) != -1) {
zipout.write(buffer, 0, len);
}
is.close();
}
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (zipout != null) {
zipout.close();
}
if (docxTempFile.exists()) {
docxTempFile.delete();
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
// word转pdf
final String pdfFile = convertDocx2Pdf(fileName);
return getFileOutputStream(new File(pdfFile)).toByteArray();
}
/**
* word(docx)转pdf
*
* @param wordPath docx文件路径
* @return 生成的带水印的pdf路径
*/
public static String convertDocx2Pdf(String wordPath) {
OutputStream os = null;
InputStream is = null;
//输出pdf文件路径和名称
final String fileName = "pdfNoMark_" + System.currentTimeMillis() + ".pdf";
try {
is = new FileInputStream(wordPath);
WordprocessingMLPackage mlPackage = WordprocessingMLPackage.load(is);
Mapper fontMapper = new IdentityPlusMapper();
fontMapper.put("隶书", PhysicalFonts.get("LiSu"));
fontMapper.put("宋体", PhysicalFonts.get("SimSun"));
fontMapper.put("微软雅黑", PhysicalFonts.get("Microsoft Yahei"));
fontMapper.put("黑体", PhysicalFonts.get("SimHei"));
fontMapper.put("楷体", PhysicalFonts.get("KaiTi"));
fontMapper.put("新宋体", PhysicalFonts.get("NSimSun"));
fontMapper.put("华文行楷", PhysicalFonts.get("STXingkai"));
fontMapper.put("华文仿宋", PhysicalFonts.get("STFangsong"));
fontMapper.put("宋体扩展", PhysicalFonts.get("simsun-extB"));
fontMapper.put("仿宋", PhysicalFonts.get("FangSong"));
fontMapper.put("仿宋_GB2312", PhysicalFonts.get("FangSong_GB2312"));
fontMapper.put("幼圆", PhysicalFonts.get("YouYuan"));
fontMapper.put("华文宋体", PhysicalFonts.get("STSong"));
fontMapper.put("华文中宋", PhysicalFonts.get("STZhongsong"));
//解决宋体(正文)和宋体(标题)的乱码问题
PhysicalFonts.put("PMingLiU", PhysicalFonts.get("SimSun"));
PhysicalFonts.put("新細明體", PhysicalFonts.get("SimSun"));
PhysicalFonts.addPhysicalFonts("SimSun", WordUtils.class.getResource("/simsun1.ttc"));
mlPackage.setFontMapper(fontMapper);
os = new FileOutputStream(fileName);
//docx4j docx转pdf
FOSettings foSettings = Docx4J.createFOSettings();
foSettings.setWmlPackage(mlPackage);
Docx4J.toFO(foSettings, os, Docx4J.FLAG_EXPORT_PREFER_XSL);
is.close();//关闭输入流
os.close();//关闭输出流
} catch (Exception e) {
e.printStackTrace();
} finally {
// 删除docx 临时文件
File file = new File(wordPath);
if (file != null && file.isFile() && file.exists()) {
file.delete();
}
try {
if (is != null) {
is.close();
}
if (os != null) {
os.close();
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
return fileName;
}
/**
* 文件转字节输出流
*
* @param outFile 文件
* @return
*/
public static ByteArrayOutputStream getFileOutputStream(File outFile) {
// 获取生成临时文件的输出流
InputStream input = null;
ByteArrayOutputStream bytestream = null;
try {
input = new FileInputStream(outFile);
bytestream = new ByteArrayOutputStream();
int ch;
while ((ch = input.read()) != -1) {
bytestream.write(ch);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
bytestream.close();
input.close();
log.info("删除临时文件");
if (outFile.exists()) {
outFile.delete();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return bytestream;
}
/**
* 获取资源文件的临时文件
* 资源文件打jar包后,不能直接获取,需要通过流获取生成临时文件
*
* @param fileName 文件路径 temp/temp.ftl
* @return
*/
public static File getTempFile(String fileName) {
final File tempFile = new File(fileName);
InputStream fontTempStream = null;
try {
fontTempStream = FreeMarkUtils.class.getClassLoader().getResourceAsStream(fileName);
FileUtils.copyInputStreamToFile(fontTempStream, tempFile);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (fontTempStream != null) {
fontTempStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return tempFile;
}
/**
* 插入图片水印
* @param srcByte 已生成PDF的字节数组(流转字节)
* @param destFile 生成有水印的临时文件 temp.pdf
* @return
*/
public static FileOutputStream addWaterMark(byte[] srcByte, String destFile) {
// 待加水印的文件
PdfReader reader = null;
// 加完水印的文件
PdfStamper stamper = null;
FileOutputStream fileOutputStream = null;
try {
reader = new PdfReader(srcByte);
fileOutputStream = new FileOutputStream(destFile);
stamper = new PdfStamper(reader, fileOutputStream);
int total = reader.getNumberOfPages() + 1;
PdfContentByte content;
// 设置字体
//BaseFont font = BaseFont.createFont();
// 循环对每页插入水印
for (int i = 1; i < total; i++) {
final PdfGState gs = new PdfGState();
// 水印的起始
content = stamper.getUnderContent(i);
// 开始
content.beginText();
// 设置颜色 默认为蓝色
//content.setColorFill(BaseColor.BLUE);
// content.setColorFill(Color.GRAY);
// 设置字体及字号
//content.setFontAndSize(font, 38);
// 设置起始位置
// content.setTextMatrix(400, 880);
//content.setTextMatrix(textWidth, textHeight);
// 开始写入水印
//content.showTextAligned(Element.ALIGN_LEFT, text, textWidth, textHeight, 45);
// 设置水印透明度
// 设置笔触字体不透明度为0.4f
gs.setStrokeOpacity(0f);
Image image = null;
image = Image.getInstance("url");
// 设置坐标 绝对位置 X Y 这个位置大约在 A4纸 右上角展示LOGO
image.setAbsolutePosition(472, 785);
// 设置旋转弧度
image.setRotation(0);// 旋转 弧度
// 设置旋转角度
image.setRotationDegrees(0);// 旋转 角度
// 设置等比缩放 图片大小
image.scalePercent(4);// 依照比例缩放
// image.scaleAbsolute(200,100);//自定义大小
// 设置透明度
content.setGState(gs);
// 添加水印图片
content.addImage(image);
// 设置透明度
content.setGState(gs);
//结束设置
content.endText();
content.stroke();
}
} catch (IOException e) {
e.printStackTrace();
} catch (DocumentException e) {
e.printStackTrace();
} finally {
try {
stamper.close();
fileOutputStream.close();
reader.close();
} catch (DocumentException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
return fileOutputStream;
}
}
更多推荐
已为社区贡献3条内容
所有评论(0)