使用Playwright把带有echarts图表的html优雅转成pdf

这是一个工作中遇到的需求:自定义HTML页面里有大量的ECharts图表,需要程序填充数据之后再转成PDF发送给客户端。

我是这么做的(以下只做简单示例,并不贴出实际项目中的应用代码):

Maven依赖配置

首先添加Playwright的Maven依赖,这里我使用的是比较新的1.57.0版本:

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

环境变量配置

由于国内网络原因,需要配置Playwright下载镜像源:

PLAYWRIGHT_DOWNLOAD_HOST=https://npmmirror.com/mirrors/playwright/

可以是项目变量也可以是系统变量。

核心转换代码

package org.at930;

import com.microsoft.playwright.*;
import com.microsoft.playwright.options.Margin;

import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

/**
 * HTML转PDF转换器 - 使用Playwright for Java
 * 将HTML文件(包含ECharts图表、图片等资源)转换为PDF格式
 */
public class HtmlToPdfConverter {

    public static void convertHtmlToPdf(String htmlFilePath, String outputPdfPath) throws Exception {
        Browser browser = null;
        try {
            BrowserType.LaunchOptions launchOptions = new BrowserType.LaunchOptions()
                    .setChannel("msedge")
                    .setHeadless(true)
                    .setArgs(java.util.Arrays.asList(
                            "--no-sandbox",
                            "--disable-dev-shm-usage"
                    ));
            
            browser = Playwright.create().chromium().launch(launchOptions);

            Page page = browser.newPage();

            page.navigate("file://" + Paths.get(htmlFilePath).toAbsolutePath());

            page.waitForFunction("() => document.readyState === 'complete'");

            byte[] pdfBytes = page.pdf(new Page.PdfOptions()
                    .setFormat("A4")
                    .setPrintBackground(true)
                    .setMargin(new Margin()
                            .setTop("13mm")
                            .setRight("11mm")
                            .setBottom("13mm")
                            .setLeft("11mm")
                    )
            );

            Path outputPath = Paths.get(outputPdfPath);
            Path outputDir = outputPath.getParent();
            if (outputDir != null && !Files.exists(outputDir)) {
                Files.createDirectories(outputDir);
            }

            // 保存PDF文件
            Files.write(outputPath, pdfBytes);
            
            // 关闭页面
            page.close();

        } catch (Exception e) {
            System.err.println("PDF转换失败: " + e.getMessage());
            e.printStackTrace();
            throw e;
        } finally {
            // 关闭浏览器
            if (browser != null) {
                browser.close();
            }
        }
    }

    public static void main(String[] args) {
        String htmlFilePath = "D:\\java_playwrightHtmlToPDF\\html\\健康监测报告-无病例用药简约版本.html";
        String outputPdfPath = "D:\\java_playwrightHtmlToPDF\\output\\text2.pdf";
        
        if (args.length >= 2) {
            htmlFilePath = args[0];
            outputPdfPath = args[1];
        }

        System.out.println("HTML文件: " + htmlFilePath);
        System.out.println("输出PDF:  " + outputPdfPath);
        System.out.println();

        try {
            convertHtmlToPdf(htmlFilePath, outputPdfPath);
            System.out.println("========================================");
            System.out.println("转换完成!PDF已保存至: " + outputPdfPath);
            System.out.println("========================================");
        } catch (Exception e) {
            System.err.println("========================================");
            System.err.println("转换失败: " + e.getMessage());
            System.err.println("========================================");
            e.printStackTrace();
            System.exit(1);
        }
    }
}

服务器环境配置

在Linux服务器上配置环境变量:

echo 'export PLAYWRIGHT_DOWNLOAD_HOST=https://npmmirror.com/mirrors/playwright/' >> /etc/profile.d/playwright.sh

验证配置是否生效:

[root@iZ2vc8cxg4knyehdo5xmzsZ ~]# echo $PLAYWRIGHT_DOWNLOAD_HOST
https://npmmirror.com/mirrors/playwright/

Docker容器配置

如果是在Docker容器中运行,需要在Dockerfile中配置环境变量:

FROM openjdk:17
RUN mkdir -p /zhyl-ylhealth-server
WORKDIR /zhyl-ylhealth-server
COPY zhyl-ylhealth-server.jar app.jar

ENV PLAYWRIGHT_DOWNLOAD_HOST=https://npmmirror.com/mirrors/playwright/

ENV TZ=Asia/Shanghai JAVA_OPTS="-Xms512m -Xmx512m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/zhyl-ylhealth-server/heapdump.hprof"

EXPOSE 48101
CMD java ${JAVA_OPTS} -Djava.security.egd=file:/dev/./urandom -jar app.jar

关键配置说明

  • setChannel("msedge"):使用Microsoft Edge浏览器内核,对ECharts等Canvas渲染支持更好
  • setHeadless(true):无头模式运行,适合服务器环境
  • --no-sandbox:禁用沙箱模式,解决Linux权限问题
  • --disable-dev-shm-usage:禁用/dev/shm共享内存,避免Docker容器内存不足
  • waitForFunction:等待页面完全加载,确保ECharts图表渲染完成
  • setPrintBackground(true):打印背景色和背景图,保证PDF视觉效果
  • Margin设置:上下13mm,左右11mm,可根据实际需求调整边距

优势分析

  1. 完美支持ECharts:Playwright基于Chromium内核,能够完整渲染Canvas绘制的ECharts图表
  2. 资源自动加载:自动处理HTML中的图片、CSS、JS等外部资源
  3. 异步等待机制:通过waitForFunction确保动态内容完全渲染后再转换
  4. 高质量输出:生成的PDF清晰度高,文字可选中,图表不失真
  5. 跨平台支持:Windows、Linux、macOS均可运行
  6. Docker友好:配合无头模式和适当参数,可在容器中稳定运行

注意事项

  • 首次运行下载:首次运行会自动下载浏览器内核,建议提前配置镜像源加速下载
  • ECharts异步加载:如果ECharts是异步加载数据,需要增加等待时间或自定义等待条件
  • 内存管理:及时关闭Browser和Page对象,避免内存泄漏
  • 字体支持:Linux服务器可能需要安装中文字体,否则PDF中文可能显示异常
  • 超时控制:可以设置page.setDefaultTimeout()防止页面加载超时

相关资源

相比传统的iText、Flying Saucer等方案,Playwright的优势在于它是真正的浏览器引擎,能够完整渲染现代Web页面的所有特性,特别是对于ECharts这类基于Canvas的动态图表,能够实现像素级的完美转换。