找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

3364

积分

0

好友

448

主题
发表于 1 小时前 | 查看: 4| 回复: 0

最近接到一个需求:做一个简单的打印功能,总共就三个小点:

  1. HTML 要能显示在浏览器上
  2. 要能在电脑上打印
  3. 要生成 PDF 给小程序用

重点是,这三个地方的显示效果要求完全一致。

最开始的想法很简单:以浏览器显示的效果为标准调整 HTML,电脑打印直接用浏览器自带的打印功能。既然都是浏览器渲染,样式不会有多大出入,即使有细微差别也容易调整。

麻烦出在小程序的 PDF 上。小程序本身限制较多且面向用户,整体思路还是要先把 PDF 保存到服务器,小程序只负责下载。那能不能在 PC 浏览器打印时调用“另存为 PDF”,后台静默把文件上传到服务器?这样就不需要额外写生成 PDF 的接口了。

可是仔细调研后发现,浏览器为了用户隐私与安全,禁止后台静默调用打印接口——大概是防止某些网页未经允许擅自打印、浪费纸张吧。唯一可行的办法是让用户自己另存为 PDF,再自行上传,这种方案显然不可能通过。

于是,只能自己生成 PDF。

常规 PDF 生成方案

使用 java 生成 PDF 的常规方法有以下几种:

Apache PDFBox
需要完全用 Java 代码去构造样式,对于要求样式高度一致、且工作量较大的场景,并不适用。下面是一段使用 PDFBox 的示例代码:

try (PDPageContentStream contentStream = new PDPageContentStream(document, page)) {
    contentStream.beginText();
    contentStream.setFont(PDType1Font.HELVETICA_BOLD, 12);
    contentStream.newLineAtOffset(100, 700);
    contentStream.showText("这是一个使用Apache PDFBox生成的PDF文件。");
    contentStream.endText();
} catch (IOException e) {
    e.printStackTrace();
}

iText (pdfHTML)
iText 支持从 HTML 直接转换为 PDF,看上去完美契合需求。于是立刻上手试用,代码很简洁,传入 HTML 就能输出 PDF 文件:

public void createPdf(String html, HttpServletResponse response) throws Exception {
    // 转换 HTML to PDF
    PdfWriter writer = new PdfWriter(response.getOutputStream());
    PdfDocument pdfDocument = new PdfDocument(writer);
    // 设置PDF大小
    pdfDocument.setDefaultPageSize(PageSize.A4);
    // 设置中文
    ConverterProperties converterProperties = new ConverterProperties();
    FontProvider fontProvider = new DefaultFontProvider(false, true);
    FontProgram fontProgram = FontProgramFactory.createFont("font/msyh.ttf");
    fontProvider.addFont(fontProgram);
    converterProperties.setFontProvider(fontProvider);
    // html转换PDF
    HtmlConverter.convertToPdf(html, pdfDocument, converterProperties);
    // 关闭
    pdfDocument.close();
}

测试几个简单例子都正常,中文乱码也很快解决。可一旦换上正式数据,打印出来的 PDF 样式就乱得一塌糊涂。深入研究才发现,iText 只完整支持 CSS2.1,对 CSS3 的支持非常有限,Flexbox/Grid 这类现代布局完全不被识别。而项目的 HTML 早已写好并上线使用,现在再按照它的标准去改动根本不现实,强行改还会导致数据样式前后不一致的风险。

把浏览器塞进项目里

问题又绕回浏览器打印——要想样式和 HTML 显示一模一样,终究还得依赖浏览器渲染引擎。但浏览器只允许用户手动调用打印,怎么破?

这时发现了一个好东西:Playwright,微软出品的自动化测试框架。把它引入项目:

<dependency>
    <groupId>com.microsoft.playwright</groupId>
    <artifactId>playwright</artifactId>
    <version>1.40.0</version>
</dependency>

它的原理就是直接把一个真实的浏览器装进了服务器:启动一个无头 Chromium 实例,这样就能在服务端调用打印功能,渲染出来的效果和用户浏览器看到的完全一致。示例代码如下:

public void generatePdfFromUrl() {
    try (Playwright playwright = Playwright.create()) {
        Browser browser = playwright.chromium().launch(new BrowserType.LaunchOptions());
        Page page = browser.newPage();
        // 直接加载 HTML 字符串
        page.setContent("<div class='html' style='page-break-after: always;'>...</div>");
        // 等待静态资源(如图片、字体)加载完成
        page.waitForLoadState(LoadState.NETWORKIDLE);
        // 生成 PDF 文件流或保存到本地
        page.pdf(new Page.PdfOptions()
            .setPath(Paths.get("output.pdf"))
            .setFormat("A4")
            .setPrintBackground(true)  // 保留 CSS 背景颜色和图片
            .setMargin(new Margin().setTop("10mm").setBottom("10mm")));
        browser.close();
    }
}

这种方式并非没有缺点,最大的问题就是“重”:首次运行时,Playwright 会自动下载 Chromium 内核到临时目录(也可以提前在打包时下载好);每次运行时都会占用相当多的 CPU 和内存,相当于在服务器上开了一个浏览器,启动速度也慢,达到秒级。在我电脑上实测,初始化大约 5 秒,实际打印 2 秒左右。因此,在生产环境中,这种方案只适用于低频、可接受一定延迟的场景,并且最好控制单线程运行,以免内存或 CPU 溢出。

但它重归重,最终打印效果无可挑剔,CSS 各种样式、图片、甚至 JavaScript 执行后的动态内容都能完美呈现——浏览器支持的,它都支持。刚好我的业务场景访问频率不高,简直完美契合。

两种技术对比

最后对比一下 iText 与浏览器方案(Playwright)的特性差异:

特性 iText (pdfHTML) Playwright (Browser-based)
技术本质 纯 Java 库,解析 HTML 并映射到 PDF 坐标 驱动真实浏览器 (Chromium) 进行渲染
CSS 支持 有限(CSS2.1,部分 CSS3,不支持 Flexbox/Grid) 完美支持,浏览器能看到的就能渲染
JS 执行 不支持(无法运行 Vue/React 或图表脚本) 支持,可等待数据加载或动画完成后生成
生成速度 极快(毫秒级) 较慢(需要启动浏览器进程,秒级)
资源消耗 低(纯内存操作) 高,需大量 CPU 和内存
部署成本 低,只需引入 Jar 包 高,需安装 Chromium 或依赖其驱动

两者各有优势,选择哪个方案,取决于实际场景对样式一致性、性能和部署复杂度的取舍。

如果你也在寻找 HTML 转 PDF 的解决方案,或者想探讨更多 Java 生成 PDF 的实战经验,欢迎到 云栈社区 与开发者们一起交流。
今日收获:HTML 转 PDF 解决方案再 +1。




上一篇:HTTP 协议不再基于 TCP?详解 HTTP3.0 与 QUIC 协议的演进
下一篇:字节面试题:vLLM与Ollama性能对比及PagedAttention深度解析
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-5-3 20:26 , Processed in 0.751631 second(s), 41 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表