【图片处理】✈️HTML转图片字体异常处理

💥💥✈️✈️欢迎阅读本文章❤️❤️💥💥

🏆本篇文章阅读大约耗时5分钟。

⛳️motto:不积跬步、无以千里

📋📋📋本文目录如下:🎁🎁🎁

目录

前言

问题排查

寻找方案

解决方案

实现步骤

章末

前言

        小伙伴们大家好,最近开发过程中遇到一个比较折磨的小问题,正如文章的标题所述,是在java项目中将 html 文件转换为图片中遇到的,具体的异常和排查后文会提及,也可以直接跳到最后查看解决方案(不一定能百分百解决各位的问题,但是可以尝试下)

功能概要

        需要返回给前端一个图片的下载链接,图片的要求是按照特殊格式排版的,并且图中会有很多自定义变量,基本每张图片的变量填充都不一样

        原始方案:使用 html 搭建出基本的图片结构,再将 html 转换为 图片

        1. 使用 Java2DRenderer 工具实现 html 转换为图片

        2. 使用 Themeleaf 工具实现 html 中待填充的变量灵活替换

问题乍现:按照开发流程在本地开发完成之后,测试没什么问题,接着发布到测试环境,出现了以下问题(测试环境服务器属于 Centos linux)

        1.服务器上生成的图片大小与本地生成的图片相差很多,本地 50kb的,服务器生成的只有 7kb左右

        2.服务器生成的图片质量较差,并且字体明显不对,大概如下(左边本地,右边服务器)

问题排查

        1.排除代码问题,代码都是同一套

        2.环境问题:服务器环境和本地差异很大

                2.1 代码中尝试指定渲染图片时使用指定的字体,经测试不生效

                2.2 服务器安装指定字体,服务器新增字体后,经测试还是没解决

                2.3 html 源码中指定使用字体,经测试不生效

                2.4 代码中生成图片的工具,切换为别的sdk后本地测试没问题,服务器上测试问题依然存在

寻找方案

        1.市面上各种 ai 查询解决方案和替代方案,一开始都徒劳无果

        2.各大博客寻找相似问题未果,可能这种问题遇到的人比较少

        3.摆烂,就这样了

解决方案

        多亏别人提醒,回想之前的方案都是将html使用代码转换成一张图片,这里面会经过服务器配置去渲染,问题出现在服务器渲染的时候,那么何不直接换种方式,已经有html了,那直接通过代码调用服务器的应用程序打开html文件,然后直接截图一张不就跳过上面那种问题了,实测一下,问题解决

        大概就是使用 Headless Chrome 将 HTML 打开,然后代码控制直接截图

实现步骤

        1.服务器安装浏览器和想要使用的字体

                1.1服务器更新安装工具

        sudo yum update  (更新成功就不用执行这条下载命令了sudo yum install epel-release -y)

                1.2 安装 Chromium 浏览器

        sudo yum install chromium -y

                1.3 安装指定字体

                目标使用用 Arial 字体,需要将该字体的文件上传到服务器(可自行查询服务器安装字体教程),也可以看一下之前服务器的字体都是什么,除自行安装的 Arial 就是 DejaVu 字体了,图片上生成的字符使用的应该就是这种字体了,看着很别扭

        2. 将获取图片方法 封装为一个工具类

import lombok.extern.slf4j.Slf4j;
import org.hibernate.service.spi.ServiceException;
import org.springframework.stereotype.Component;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Base64;

/**
 * @author benbenhuang
 * @date 2025年10月16日 22:03
 */
@Slf4j
@Component
public class HtmlUtil {

    /**
     * 使用 Headless Chrome 将 HTML 渲染为 PNG 并上传
     */
    public void convertHtmlToImageAndUpload(String htmlContent, String fileName) {
        Path htmlPath = null;
        Path outputPath = null;
        try {
            //生成临时 HTML 文件
            htmlPath = Files.createTempFile("page_", ".html");
            Files.write(htmlPath, htmlContent.getBytes(StandardCharsets.UTF_8));

            //生成临时输出图片路径
            outputPath = Files.createTempFile("screenshot_", ".png");

            //组装 Chrome 命令
            String chromePath = detectChromeBinary();
            ProcessBuilder pb = new ProcessBuilder(
                    chromePath,
                    "--headless",
                    "--disable-gpu",
                    "--no-sandbox",
                    "--hide-scrollbars",
                    "--window-size=350,550",
                    "--screenshot=" + outputPath.toAbsolutePath(),
                    htmlPath.toAbsolutePath().toString()
            );

            pb.redirectErrorStream(true);
            Process process = pb.start();

            //打印 Chrome 输出日志(方便排错)
            try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
                reader.lines().forEach(line -> log.debug("[chrome] {}", line));
            }

            int exitCode = process.waitFor();
            if (exitCode != 0) {
                throw new RuntimeException("Chrome render failed, exit code = " + exitCode);
            }

            //转换为 Base64
            byte[] imageBytes = Files.readAllBytes(outputPath);
            String base64Image = Base64.getEncoder().encodeToString(imageBytes);

//            //上传到 OSS
//            ossclient.uploadPic(base64Image, fileName);

        } catch (Exception e) {
            throw new ServiceException("Render HTML to image failed");
        } finally {
            // 清理临时文件
            try {
                if (htmlPath != null) Files.deleteIfExists(htmlPath);
                if (outputPath != null) Files.deleteIfExists(outputPath);
            } catch (IOException ignored) {}
        }
    }

    /**
     * 检测 Chrome 可执行路径
     */
    private String detectChromeBinary() {
        String[] candidates = {
                "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome", // macOS 主要路径
                "/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary", // macOS Canary版本
                "/Applications/Chromium.app/Contents/MacOS/Chromium", // macOS Chromium路径
                "/usr/bin/google-chrome", // Linux 路径
                "/usr/bin/chromium-browser", // Linux 路径
                "/usr/local/bin/chrome", // 可能的自定义路径
                "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe" // Windows 路径
        };
        for (String path : candidates) {
            if (Files.exists(Paths.get(path))) {
                return path;
            }
        }
        return "google-chrome";
    }
}

        3. 这一个工具类别的就没了,看下入参,一个是 html 的代码字符串(已经处理过变量填充问题),一个是 fileName,因为本地是需要将图片上传到oss的,当然,也可以调整为输出到本地,,只需要调整一下最后的图片上传操作即可

        检测可执行路径方法就是找到该浏览器到的可执行路径,里面添加了不同系统该软件默认安装后的可执行路径

        整体就是通过浏览器打开临时html文件,截图保存即可

        经测试生成的图片没有问题了,图片大小和本地大差不差,字体也是目标字体

章末

        文章到这里就结束了~

往期推荐 > > > 

 【服务器搭建】✈️用自己电脑搭建一个服务器!

 【IDEA】✈️自定义模板,自动生成类和方法注释

 【日志链路】⭐️SpringBoot 整合 TraceId 日志链路追踪!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

先锋 Coder

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值