方案概览
| 方案 | 核心库 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| 浏览器渲染引擎 | Flying Saucer (xhtmlrenderer) | 高度逼真,能完美呈现现代 CSS (包括 Flexbox, Grid),支持 JavaScript | 依赖无头浏览器 (如 Playwright) 才能执行 JS,配置相对复杂 | 对样式还原度要求极高的场景,如从网页直接生成 PDF |
| 专业商业库 | iText, Aspose.Words | 功能强大,稳定,支持丰富的 PDF 操作(如数字签名、加密、AcroForms) | 商业软件需要付费,iText 5 开源版有 AGPL 许可限制,iText 7 有免费版但功能受限 | 企业级应用,需要复杂 PDF 操作,且预算充足 |
| 纯 Java 渲染 | OpenPDF, Flying Saucer (无 JS) | 轻量级,纯 Java 实现,无外部依赖 | CSS 支持相对较弱,对现代 CSS 布局支持不佳 | 简单的 HTML 转 PDF,样式要求不高,追求轻量 |
- 追求最高还原度(特别是现代 CSS 和 JS):方案一 (Flying Saucer + Playwright) 是最佳选择。
- 企业级应用,需要强大 PDF 功能且能接受付费:方案二 (iText / Aspose) 是行业标准。
- 简单需求,轻量级,无复杂样式:方案三 (OpenPDF) 足够用。
使用 Flying Saucer (xhtmlrenderer) + Playwright (推荐,效果最好)
这个方案的核心思想是:先用一个无头浏览器(如 Playwright)加载 HTML 并执行其中的 JavaScript,将最终渲染好的 DOM 结构截图下来,Flying Saucer 利用这个截图作为背景,精确地在 PDF 上绘制每一个元素,这样既能执行 JS,又能保证像素级的完美还原。

准备工作
在 pom.xml 中添加依赖:
<!-- 1. Flying Saucer 核心库 -->
<dependency>
<groupId>org.xhtmlrenderer</groupId>
<artifactId>flying-saucer-pdf</artifactId>
<version>9.1.22</version> <!-- 请使用最新版本 -->
</dependency>
<!-- 2. Playwright Java 依赖,用于驱动浏览器 -->
<dependency>
<groupId>com.microsoft.playwright</groupId>
<artifactId>playwright</artifactId>
<version>1.40.0</version> <!-- 请使用最新版本 -->
</dependency>
<!-- 3. 添加 Bouncy Castle 提供者,这是 Flying Saucer 依赖的 -->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.70</version>
</dependency>
代码实现
import com.microsoft.playwright.*;
import org.xhtmlrenderer.pdf.ITextRenderer;
import org.w3c.dom.Document;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
public class HtmlToPdfWithPlaywright {
public static void main(String[] args) {
// 1. 定义 HTML 内容和输出路径
String htmlContent = """
<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: sans-serif; }
.container { display: flex; justify-content: space-between; }
.left { width: 60%; }
.right { width: 35%; text-align: right; }
h1 { color: #333; }
.highlight { background-color: yellow; }
</style>
<script>
// 模拟一个 JS 操作
document.addEventListener('DOMContentLoaded', function() {
document.getElementById('date').innerText = new Date().toLocaleDateString();
document.querySelector('.highlight').style.color = 'blue';
});
</script>
</head>
<body>
<h1>这是一个动态生成的 PDF</h1>
<div class="container">
<div class="left">
<p>这是页面的主要内容区域。</p>
<p>JavaScript 已经执行,修改了日期和样式。</p>
</div>
<div class="right">
<p>生成日期: <span id="date">加载中...</span></p>
<p class="highlight">这段文字会变成蓝色背景。</p>
</div>
</div>
</body>
</html>
""";
File outputFile = new File("output_with_playwright.pdf");
try {
// 2. 使用 Playwright 渲染 HTML 并截图
byte[] screenshotBytes;
try (Playwright playwright = Playwright.create()) {
Browser browser = playwright.chromium().launch();
Page page = browser.newPage();
page.setContent(htmlContent); // 将 HTML 内容注入页面
// 设置视口大小,确保截图包含所有内容
page.setViewportSize(800, 600);
// 截图整个页面
screenshotBytes = page.screenshot();
browser.close();
}
// 3. 使用 Flying Saucer 将 HTML 和截图结合生成 PDF
ITextRenderer renderer = new ITextRenderer();
// 设置 HTML 内容
renderer.setDocumentFromString(htmlContent);
renderer.layout();
// 获取 PDF 输出流
try (OutputStream os = new FileOutputStream(outputFile)) {
// 关键步骤:设置背景图片为 Playwright 截的图
renderer.getSharedContext().setUserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36");
renderer.getCanvasProvider().setPainting(false); // 禁用默认绘制
renderer.getCanvasProvider().startRenderer();
renderer.getCanvasProvider().getGraphics().drawImage(
javax.imageio.ImageIO.read(new ByteArrayInputStream(screenshotBytes)),
0, 0,
renderer.getPrintableWidth(),
renderer.getPrintableHeight()
);
renderer.getCanvasProvider().endRenderer();
renderer.createPDF(os);
}
System.out.println("PDF 生成成功: " + outputFile.getAbsolutePath());
} catch (Exception e) {
e.printStackTrace();
}
}
}
注意: 上述代码是概念性的,Flying Saucer 直接使用截图作为背景并精确绘制元素的技术细节比较复杂,更常见的做法是让 Playwright 将 HTML 渲染到一个虚拟的 A4 纸张尺寸的页面,然后直接打印为 PDF。Playwright 本身就支持直接生成 PDF,这通常是更简单、更可靠的选择。
简化版:仅使用 Playwright 生成 PDF
Playwright 的 page.pdf() 方法可以直接将页面保存为 PDF,这是目前最推荐的方式,因为它简单、高效且效果极佳。
import com.microsoft.playwright.*;
public class SimpleHtmlToPdfWithPlaywright {
public static void main(String[] args) {
String htmlContent = """
<html>
<head><style>body { font-family: sans-serif; }</style></head>
<body><h1>Hello, PDF from Playwright!</h1></body>
</html>
""";
File outputFile = new File("output_simple_playwright.pdf");
try (Playwright playwright = Playwright.create()) {
Browser browser = playwright.chromium().launch();
Page page = browser.newPage();
// 设置 HTML 内容
page.setContent(htmlContent);
// 设置 PDF 显示尺寸,模拟 A4 纸
page.setDefaultViewportSize(794, 1123); // A4 尺寸 (96dpi)
// 直接生成 PDF
page.pdf(new Page.PdfOptions()
.setPath(outputFile)
.setPrintBackground(true) // 打印背景
.setFormat("A4")
);
browser.close();
System.out.println("PDF 生成成功: " + outputFile.getAbsolutePath());
} catch (Exception e) {
e.printStackTrace();
}
}
}
这个简化版是当前处理 HTML 转 PDF 的最佳实践。
使用 iText (商业库)
iText 是一个非常强大的 Java PDF 库,但它有严格的许可协议,iText 5 的 AGPLv3 许可要求如果你的应用是私有的,你也必须开源你的应用代码,iText 7 提供了更友好的 AGPL 许可,但商业使用需要购买许可证。
准备工作 (iText 7)
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itext7-core</artifactId>
<version>7.2.5</version> <!-- 请使用最新版本 -->
<type>pom</type>
</dependency>
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>html2pdf</artifactId>
<version>4.0.3</version> <!-- HTML to PDF 模块 -->
</dependency>
代码实现
iText 7 的 html2pdf 模块使得转换非常简单。
import com.itextpdf.html2pdf.HtmlConverter;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
public class HtmlToPdfWithIText {
public static void main(String[] args) {
// 输入可以是 HTML 字符串或文件
String htmlContent = "<h1>iText HTML to PDF</h1><p>This is a paragraph.</p>";
File outputFile = new File("output_itext.pdf");
try {
// 直接转换 HTML 字符串到 PDF 文件
HtmlConverter.convertToPdf(htmlContent, outputFile);
System.out.println("PDF 生成成功: " + outputFile.getAbsolutePath());
} catch (IOException e) {
e.printStackTrace();
}
}
}
注意: iText 对 CSS 的支持虽然一直在改进,但可能不如 Flying Saucer 或浏览器引擎那么完美,特别是对于非常复杂的 CSS 布局。
使用 OpenPDF (纯 Java,轻量级)
OpenPDF 是 iText 5 的一个分支,采用 Mozilla Public License 1.1,允许在商业软件中免费使用,但它本身不直接支持 HTML 转 PDF,你需要结合其他工具,Apache FOP,或者使用它的模板功能。
这里展示一个更“原生”的方式,即用 OpenPDF 的 HTMLWorker 来解析简单的 HTML 标签。注意:HTMLWorker 已被废弃,且功能非常有限,仅支持非常基础的 HTML。
准备工作
<dependency>
<groupId>com.github.librepdf</groupId>
<artifactId>openpdf</artifactId>
<version>1.3.30</version> <!-- 请使用最新版本 -->
</dependency>
代码示例 (不推荐,仅作了解)
import com.lowagie.text.Document;
import com.lowagie.text.DocumentException;
import com.lowagie.text.html.simpleparser.HTMLWorker;
import com.lowagie.text.html.simpleparser.StyleSheet;
import com.lowagie.text.pdf.PdfWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.StringReader;
public class HtmlToPdfWithOpenPdf {
public static void main(String[] args) {
String simpleHtml = "<h1>OpenPDF Example</h1><p>This is a <b>bold</b> text.</p>";
File outputFile = new File("output_openpdf.pdf");
Document document = new Document();
try {
PdfWriter.getInstance(document, new FileOutputStream(outputFile));
document.open();
// 创建一个简单的样式表
StyleSheet styles = new StyleSheet();
styles.loadTagStyle("h1", "font-size", "20pt");
styles.loadTagStyle("p", "leading", "16, 0");
// 使用已废弃的 HTMLWorker
HTMLWorker htmlWorker = new HTMLWorker(document);
htmlWorker.parse(new StringReader(simpleHtml));
document.close();
System.out.println("PDF 生成成功: " + outputFile.getAbsolutePath());
} catch (DocumentException | IOException e) {
e.printStackTrace();
}
}
}
再次强调: 这种方法仅适用于极简的 HTML,对于任何包含 <div>, <span>, CSS 样式的现代 HTML 都会失败。现代项目中应避免使用此方案。
总结与建议
| 特性 | Playwright (推荐) | iText / Aspose | OpenPDF |
|---|---|---|---|
| 易用性 | 高 (一行代码) | 高 (一行代码) | 低 (功能有限,需额外处理) |
| CSS/JS 支持 | 完美 (浏览器级) | 良好 (但非100%) | 几乎没有 |
| 依赖 | 需要下载浏览器驱动 | 纯 Java | 纯 Java |
| 成本 | 免费 | 商业收费 | 免费 |
| 推荐度 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ (付费) | ⭐ (仅限极简场景) |
最终建议:
- 首选方案:如果你需要生成包含复杂样式、图片、甚至需要执行 JavaScript 的 PDF,请使用 Playwright,它能给你最接近浏览器打印的效果,是目前最现代、最可靠的解决方案。
- 次选方案:如果你的 PDF 生成需求是纯后台的,样式不复杂,且你的项目需要强大的 PDF 操作能力(如加密、表单、签名等),并且预算允许,可以考虑 iText 或 Aspose。
- 最后选择:如果你的 HTML 内容极其简单,只是一些纯文本和基本标签,且不希望引入任何外部依赖,可以考虑 OpenPDF,但要对其局限性有清醒的认识。
