引入依赖
<!--Freemarker wls-->
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.30</version>
</dependency>
<dependency>
<groupId>com.itextpdf.tool</groupId>
<artifactId>xmlworker</artifactId>
<version>5.5.11</version>
</dependency>
<!-- 支持中文 -->
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itext-asian</artifactId>
<version>5.2.0</version>
</dependency>
<!-- 支持css样式渲染 -->
<dependency>
<groupId>org.xhtmlrenderer</groupId>
<artifactId>flying-saucer-pdf-itext5</artifactId>
<version>9.1.18</version>
</dependency>
<dependency>
<groupId>gui.ava</groupId>
<artifactId>html2image</artifactId>
<version>2.0.1</version>
</dependency>
代码示例
/**
* @author alin
* @date 2024-06-11
*/
@Slf4j
public class TestCreatePdf {
public static void main(String[] args) throws Exception {
generatePdfUrl();
}
/**
* 生成pdf
*
* @return
*/
public static String generatePdfUrl() throws Exception {
// 构造参数
Model model = assembleData();
return createPdfAndUpload(beanToMap(model), UUID.randomUUID() + ".pdf", "testCreatePdf.html", "testCreatePdf.css");
}
/**
* 创建pdf并上传/输出
*
* @param data
* @param fileName 文件名
* @param templateFileName 模板文件名, html模板文件
* @param cssPath css文件路径
* @return
* @throws Exception
*/
private static String createPdfAndUpload(Map<String, Object> data, String fileName, String templateFileName, String cssPath) throws Exception {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try {
// 根据模板生成html字符串
String pdf = createHtmlStr(data, templateFileName);
// 通过html字符串生成pdf文件
generatePdf(pdf, outputStream, cssPath);
} catch (Exception e) {
return null;
}
// 输出/上传至指定位置
FileOutputStream out = new FileOutputStream("d:/testPdf/" + fileName);
out.write(outputStream.toByteArray());
return "d:/testPdf/" + fileName;
}
/**
* bean转map
*
* @param bean
* @return
*/
public static Map<String, Object> beanToMap(Object bean) {
Class<?> clazz = bean.getClass();
return Arrays.stream(clazz.getDeclaredFields())
.collect(Collectors.toMap(
Field::getName,
field -> {
try {
field.setAccessible(true);
return field.get(bean);
} catch (IllegalAccessException e) {
// 处理异常
return null;
}
}
));
}
/**
* 模板生成html字符串
*
* @param data 数据
* @param templateFileName 模板文件名
* @throws Exception 捕获异常
*/
public static String createHtmlStr(Map<String, Object> data, String templateFileName) throws Exception {
// 创建一个FreeMarker实例, 负责管理FreeMarker模板的Configuration实例
Configuration cfg = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS);
// 指定FreeMarker模板文件的位置
cfg.setClassForTemplateLoading(TestCreatePdf.class, "/template");
// 获取模板文件
Template template = cfg.getTemplate(templateFileName, "UTF-8");
StringWriter stringWriter = new StringWriter();
BufferedWriter writer = new BufferedWriter(stringWriter);
template.process(data, writer);
String htmlStr = stringWriter.toString();
writer.flush();
writer.close();
return htmlStr;
}
/**
* 通过html字符串生成pdf文件
*
* @param htmlStr
* @param out
* @param cssPath
* @throws IOException
* @throws DocumentException
*/
public static void generatePdf(String htmlStr, OutputStream out, String cssPath) throws IOException, DocumentException {
Document document = new Document(PageSize.A3);
PdfWriter writer = PdfWriter.getInstance(document, out);
document.open();
// html内容解析
HtmlPipelineContext htmlContext = new HtmlPipelineContext(
new CssAppliersImpl(new XMLWorkerFontProvider() {
@Override
public Font getFont(String fontName, String encoding,
float size, final int style) {
Font font = null;
if (fontName == null) {
//字体
BaseFont bf;
try {
bf = BaseFont.createFont("STSongStd-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
font = new Font(bf, size, style);
} catch (Exception e) {
log.error("getFont", e);
}
}
return font;
}
})) {
@Override
public HtmlPipelineContext clone()
throws CloneNotSupportedException {
HtmlPipelineContext context = super.clone();
try {
ImageProvider imageProvider = this.getImageProvider();
context.setImageProvider(imageProvider);
} catch (Exception e) {
log.error("clone", e);
}
return context;
}
};
// 图片解析
htmlContext.setImageProvider(new AbstractImageProvider() {
@Override
public String getImageRootPath() {
return StringUtils.EMPTY;
}
@Override
public Image retrieve(String src) {
if (StringUtils.isEmpty(src)) {
return null;
}
try {
int pos = src.indexOf("base64,");
try {
if (src.startsWith("data") && pos > 0) {
byte[] img = Base64.decode(src.substring(pos + 7));
return Image.getInstance(img);
} else if (src.startsWith("http")) {
return Image.getInstance(src);
}
} catch (Exception ex) {
log.error("retrieve", ex);
return null;
}
return null;
} catch (Throwable e) {
log.error("retrieve", e);
}
return super.retrieve(src);
}
});
htmlContext.setAcceptUnknown(true).autoBookmark(true).setTagFactory(Tags.getHtmlTagProcessorFactory());
// css解析
CSSResolver cssResolver = new StyleAttrCSSResolver();
InputStream cssInputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(cssPath);
CssFile cssfile = XMLWorkerHelper.getCSS(cssInputStream);
cssResolver.addCss(cssfile);
HtmlPipeline htmlPipeline = new HtmlPipeline(htmlContext, new PdfWriterPipeline(document, writer));
Pipeline<?> pipeline = new CssResolverPipeline(cssResolver, htmlPipeline);
XMLWorker worker = new XMLWorker(pipeline, true);
XMLParser parser = new XMLParser(true, worker, StandardCharsets.UTF_8);
try (InputStream inputStream = new ByteArrayInputStream(
htmlStr.getBytes())) {
parser.parse(inputStream, StandardCharsets.UTF_8);
}
document.close();
}
/**
* 组装数据
*
* @return
* @throws Exception
*/
private static Model assembleData() throws Exception {
TestCreatePdf.Model model = new TestCreatePdf.Model();
model.setCompanyName("公司名称");
model.setField1("字段一");
model.setField2("字段二");
model.setField3("字段三");
model.setField4("字段四");
model.setField5("字段五");
model.setField6("字段六");
model.setField7("字段七");
model.setRemark("备注~~~~~~~~~");
model.setSignUrl1("D:/testPdf/test.png");
model.setSignUrl2("D:/testPdf/test.png");
model.setSignUrl3("D:/testPdf/test.png");
model.setSignTime1("2024-04-28 17:08:52");
model.setSignTime2("2024-04-28 17:08:52");
model.setSignTime3("2024-04-28 17:08:52");
List<Object> modeDetailFieldList = Lists.newArrayList();
modeDetailFieldList.add("表头一");
modeDetailFieldList.add("表头二");
modeDetailFieldList.add("表头三");
modeDetailFieldList.add("表头四");
modeDetailFieldList.add("表头五");
modeDetailFieldList.add("表头六");
model.setModeDetailFieldList(modeDetailFieldList);
List<List<Object>> modeDetailValueList = Lists.newArrayList();
for (int i = 1; i < 6; i++) {
List<Object> valueList = Lists.newArrayList();
valueList.add("表头一值--" + i);
valueList.add("表头二值--" + i);
valueList.add("表头三值--" + i);
valueList.add("表头四值--" + i);
valueList.add("表头五值--" + i);
valueList.add("表头六值--" + i);
modeDetailValueList.add(valueList);
}
model.setModeDetailValueList(modeDetailValueList);
return model;
}
@Data
public static class Model {
/**
* companyName
*/
private String companyName;
/**
* 字段1
*/
private String field1;
/**
* 字段2
*/
private String field2;
/**
* 字段3
*/
private String field3;
/**
* 字段4
*/
private String field4;
/**
* 字段5
*/
private String field5;
/**
* 字段6
*/
private String field6;
/**
* 字段7
*/
private String field7;
/**
* 备注
*/
private String remark;
/**
* 图片地址(base64结构)
*/
private String imgBase64;
/**
* signUrl1
*/
private String signUrl1;
/**
* signUrl2
*/
private String signUrl2;
/**
* signUrl3
*/
private String signUrl3;
/**
* signTime1
*/
private String signTime1;
/**
* signTime2
*/
private String signTime2;
/**
* signTime3
*/
private String signTime3;
/**
* 表格字段名称
*/
private List<Object> modeDetailFieldList;
/**
* 表格字段值
*/
private List<List<Object>> modeDetailValueList;
}
}