Java 动态模板转 PDF 并输出高清晰度图片
企业级方案:Java 动态模板转 PDF 并输出高清晰度图片
(Freemarker + iText7 + PDFBox 深度实战)
1. 为什么选择这个组合?
在生产环境中,我们需要解决三大痛点:
-
样式还原度:iText7 配合 html2pdf 对 CSS3 的支持远超 iText5。
-
中文乱码:通过自定义 FontProvider 完美解决。
-
动态图片:支持将 Base64 或网络图片直接注入模板。
2. 依赖管理 (Maven)
引入最新的稳定版本,确保功能最全。
code Xml
downloadcontent_copy
expand_less
<properties>
<itext.version>4.0.5</itext.version>
<pdfbox.version>2.0.29</pdfbox.version>
</properties>
<dependencies>
<!-- 模板引擎 -->
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.31</version>
</dependency>
<!-- iText7 HTML转PDF核心 -->
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>html2pdf</artifactId>
<version>${itext.version}</version>
</dependency>
<!-- PDFBox 用于PDF转图片 -->
<dependency>
<groupId>org.apache.pdfbox</groupId>
<artifactId>pdfbox</artifactId>
<version>${pdfbox.version}</version>
</dependency>
<!-- 常用工具类 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
<optional>true</optional>
</dependency>
</dependencies>
3. 高级 HTML 模板设计 (report.ftl)
为了体现专业性,我们加入表格、Base64图片、分页符和页眉页脚区域。
code Html
play_circledownloadcontent_copy
expand_less
<!DOCTYPE html>
<html>
<head>
<style>
@page {
size: A4;
margin: 50pt;
/* 定义页眉 */
@top-center { content: "XX集团业务报告"; font-family: 'SimSun'; font-size: 9pt; color: #999; }
/* 定义页脚显示页码 */
@bottom-right { content: "第 " counter(page) " 页,共 " counter(pages) " 页"; font-family: 'SimSun'; font-size: 9pt; }
}
body { font-family: 'SimSun'; line-height: 1.6; }
.header { border-bottom: 2px solid #333; padding-bottom: 10px; text-align: center; }
.logo { width: 100px; height: auto; }
table { width: 100%; border-collapse: collapse; margin-top: 20px; }
th, td { border: 1px solid #ccc; padding: 8px; text-align: left; }
th { background-color: #f2f2f2; }
.page-break { page-break-after: always; } /* 强制分页 */
.highlight { color: #e74c3c; font-weight: bold; }
</style>
</head>
<body>
<div class="header">
<img src="${logoBase64}" class="logo" />
<h1>${title}</h1>
</div>
<div class="content">
<p>尊敬的 <span class="highlight">${userName}</span>:</p>
<p>以下是您的季度消费明细:</p>
<table>
<thead>
<tr>
<th>日期</th>
<th>项目</th>
<th>金额</th>
</tr>
</thead>
<tbody>
<#list items as item>
<tr>
<td>${item.date}</td>
<td>${item.name}</td>
<td>¥${item.price}</td>
</tr>
</#list>
</tbody>
</table>
</div>
<!-- 分页示例 -->
<div class="page-break"></div>
<div class="content">
<h2>附件:详细条款</h2>
<p>此处为第二页内容...</p>
</div>
</body>
</html>
4. 核心工具类封装
4.1 资源路径与字体处理
在生成PDF时,最麻烦的是字体。建议将 .ttf 字体文件放在 resources/fonts 下。
code Java
downloadcontent_copy
expand_less
@Slf4j
public class PdfGeneratorUtil {
/**
* 执行HTML转PDF
* @param htmlContent 填充后的HTML字符串
* @param destFile 目标文件
*/
public static void createPdf(String htmlContent, File destFile) throws IOException {
ConverterProperties props = new ConverterProperties();
FontProvider fontProvider = new FontProvider();
// 1. 自动注册系统字体(Windows/Linux下存在的字体)
fontProvider.addSystemFonts();
// 2. 显式注册自定义字体(解决Linux环境无宋体的问题)
// String fontPath = "src/main/resources/fonts/simsun.ttf";
// fontProvider.addFont(fontPath);
props.setFontProvider(fontProvider);
// 3. 设置基础路径,方便读取相对路径的图片
// props.setBaseUri("src/main/resources/static/");
try (FileOutputStream os = new FileOutputStream(destFile)) {
HtmlConverter.convertToPdf(htmlContent, os, props);
}
}
}
4.2 PDF 转多张高清图片
如果 PDF 有多页,我们需要循环渲染并拼接或单独保存。
code Java
downloadcontent_copy
expand_less
public class ImageGeneratorUtil {
/**
* PDF转图片
* @param pdfFile 源文件
* @param outputDir 输出目录
* @param dpi 渲染精度,建议300
*/
public static List<String> pdfToImages(File pdfFile, String outputDir, int dpi) throws IOException {
List<String> imagePaths = new ArrayList<>();
try (PDDocument document = PDDocument.load(pdfFile)) {
PDFRenderer renderer = new PDFRenderer(document);
for (int i = 0; i < document.getNumberOfPages(); i++) {
BufferedImage image = renderer.renderImageWithDPI(i, dpi);
String fileName = "page_" + (i + 1) + ".png";
File outFile = new File(outputDir, fileName);
ImageIO.write(image, "PNG", outFile);
imagePaths.add(outFile.getAbsolutePath());
}
}
return imagePaths;
}
}
5. 业务逻辑整合
code Java
downloadcontent_copy
expand_less
@Service
public class DocumentService {
public void generateReport(String userId) throws Exception {
// 1. 模拟数据准备
Map<String, Object> data = new HashMap<>();
data.put("title", "2023年度消费报告");
data.put("userName", "王小明");
data.put("logoBase64", "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA..."); // 实际使用时替换为真实Base64
List<Map<String, String>> items = new ArrayList<>();
items.add(Map.of("date", "2023-01-01", "name", "云服务器续费", "price", "1200.00"));
items.add(Map.of("date", "2023-02-15", "name", "域名注册", "price", "55.00"));
data.put("items", items);
// 2. Freemarker 渲染 HTML
String html = TemplateUtil.processTemplate("report.ftl", data);
// 3. 生成 PDF
File pdfFile = new File("D:/temp/report.pdf");
PdfGeneratorUtil.createPdf(html, pdfFile);
// 4. PDF 转 高清图片 (第一页)
ImageGeneratorUtil.pdfToImages(pdfFile, "D:/temp/images/", 300);
log.info("全流程处理完成!");
}
}
6. 避坑指南(进阶必看)
-
Linux 环境部署:
-
现象:本地 Windows 正常,发到服务器中文全乱码。
-
根因:Linux 默认没有 SimSun (宋体)。
-
对策:必须在 Java 代码中通过 fontProvider.addFont("path/to/simsun.ttf") 手动加载字体文件。
-
-
图片加载失败:
-
现象:<img> 标签找不到图片。
-
对策:推荐将图片转为 Base64 字符串通过模板注入,这样不需要处理复杂的文件路径问题,也不会受限于内网防火墙。
-
-
内存溢出:
-
现象:生成超大型 PDF(几百页)时 OOM。
-
对策:iText7 支持 PdfDocument 的流式写入。对于 PDF 转图片,避免一次加载过大的 PDF 文件,或者分批处理页面。
-
-
CSS 支持限制:
-
iText7 虽然强大,但不支持 flex 和 grid 布局。请回归到 float 布局或 table 布局。
-
7. 总结
本文展示了从模板引擎渲染到PDF高保真转换,再到高清图像输出的完整闭环。这套方案不仅适用于简单的证书生成,也能胜任复杂的金融报表导出。
源码获取/讨论:欢迎在评论区交流,点赞收藏不迷路!
更多推荐




所有评论(0)