【图片处理】✈️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 字体了,图片上生成的字符使用的应该就是这种字体了,看着很别扭

  1. 将获取图片方法 封装为一个工具类
java 复制代码
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";
    }
}
  1. 这一个工具类别的就没了,看下入参,一个是 html 的代码字符串(已经处理过变量填充问题),一个是 fileName,因为本地是需要将图片上传到oss的,当然,也可以调整为输出到本地,,只需要调整一下最后的图片上传操作即可

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

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

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

章末

文章到这里就结束了~

往期推荐 > > >

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

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

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

相关推荐
yantuguiguziPGJ4 小时前
WPF 联合 Web 开发调试流程梳理(基于 Microsoft.Web.WebView2)
前端·microsoft·wpf
大飞记Python5 小时前
部门管理|“编辑部门”功能实现(Django5零基础Web平台)
前端·数据库·python·django
tsumikistep6 小时前
【前端】前端运行环境的结构
前端
你的人类朋友6 小时前
【Node】认识multer库
前端·javascript·后端
Aitter6 小时前
PDF和Word文件转换为Markdown的技术实现
前端·ai编程
mapbar_front7 小时前
面试问题—上家公司的离职原因
前端·面试
云知谷7 小时前
【HTML】网络数据是如何渲染成HTML网页页面显示的
开发语言·网络·计算机网络·html
昔人'7 小时前
css使用 :where() 来简化大型 CSS 选择器列表
前端·css
昔人'7 小时前
css `dorp-shadow`
前端·css