【图片处理】✈️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);}//转换为 Base64byte[] 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 日志链路追踪!