杰瑞科技汇

java html转化为pdf

方案概览

方案 核心库 优点 缺点 适用场景
浏览器渲染引擎 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,又能保证像素级的完美还原。

java html转化为pdf-图1
(图片来源网络,侵删)

准备工作

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
成本 免费 商业收费 免费
推荐度 ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐ (付费) ⭐ (仅限极简场景)

最终建议:

  1. 首选方案:如果你需要生成包含复杂样式、图片、甚至需要执行 JavaScript 的 PDF,请使用 Playwright,它能给你最接近浏览器打印的效果,是目前最现代、最可靠的解决方案。
  2. 次选方案:如果你的 PDF 生成需求是纯后台的,样式不复杂,且你的项目需要强大的 PDF 操作能力(如加密、表单、签名等),并且预算允许,可以考虑 iTextAspose
  3. 最后选择:如果你的 HTML 内容极其简单,只是一些纯文本和基本标签,且不希望引入任何外部依赖,可以考虑 OpenPDF,但要对其局限性有清醒的认识。
分享:
扫描分享到社交APP
上一篇
下一篇