JavaFX 使用 JPackage 打包后,在 Linux 中运行时简体中文将无法正常显示,你看见的全是方框。网上有各种各样的解决办法,可以说都有用,但终究没有找到根本原因。
网上查了一下,有大概几种解决办法:
1 Linux 缺中文字体(较新的发行版都有中文支持了,确实没有需要安装);
2 JavaFX 程序中代码加载字体;
3 JavaFx 程序中代码设置字体;
4 JavaFx 程序中样式表设置字体;
5 配置JAVA_HOME/jre/lib/fonts/fontconfig.properties(JavaFX打包后没有这个了);
6 复制Windows的字体到Linux系统(吐血)。
要解决问题就要找到根本原因
搜罗大量国内外文章之后,我们发现了端倪,首先有一篇比较全面的文章 OpenJFX/Font+Setup 可以指导我们排查问题,过程就不描述了,结论就是:JavaFX 默认字体名称(CSS -fx-font-family:)为 "System Regular" 字体族 "System",通过Linux系统命令 “ $ fc-match sans.regular ” 可输出其映射的实际字体文件 NotoSansCJK-Regular.ttc: "Noto Sans CJK SC" "Regular",其中的SC表示简体中文( Simplified Chinese ),如此说来,更本不是 Linux 系统缺简体中文字体(哪个Linux系统没有中文桌面呢,那不是也正确显示中文了吗),其实是 JavaFX 自身的问题。
网上有一篇文章跟踪了JavaFx的字体加载过程,解释了这个问题的原因,很遗憾我们丢失了链接(这里应该链接到那一篇非常不错的文章);根本原因就是 JavaFx 在 Windows 、MacOS 和 Linux 分别采用了不同的默认字体加载机制,而 Linux 中的加载机制,恰好没有将 “System” 默认为支持中文的字体。
问题的根源已经找到,接下来想办法解决。
解决JavaFx在Linux中文显示问题
我必须要吐槽,解决这个问题的过程,真的非常痛苦。
1.通过 JavaFX 的样式表像前端那样配置多个字体名称来兼容多个平台(不可行)。
.root {
/* JavaFx 中这不会生效 */
-fx-font-family: 'Noto Sans CJK SC','Source Han Sans CN' ...;
}
JavaFx 明确不支持这种配置方式,我们只能指定一个字体名称;指定多个是错误的语法,等同于无效的样式项。
2.通过 @font-face 指定多个本地名称来兼容多个平台(不可行)。
@font-face {
/* JavaFx 中此方法不可行 */
-fx-font-family: 'FontDown';
src: local('Noto Sans CJK SC'),
local('PingFang SC'),
local('Hiragino Sans GB'),
local('Source Han Sans CN'),
local('WenQuanYi Micro Hei'),
url("AlibabaPuHuiTi-3-45-Light.ttf");
}
.root {
-fx-font-family: 'FontDown';
}
这个思路是不是完美无缺,但是实际上 JavaFx 的 CSS 文档中明确告知,这也不支持。
Although the parser will parse the syntax, all @font‑face descriptors are ignored except for the src descriptor. The src descriptor is expected to be a <url>. The format hint is ignored.
翻译过来就是: 尽管解析器会解析该语法,但除 src 描述符外,所有 @font-face 描述符均会被忽略。src 描述符应为 <url> 类型,而 format 格式提示将被忽略。
也就是说 @font-face 中只有 src:url() 被支持,其余的你爱咋写咋写,我全忽略。以上引用自JavaFX CSS 参考指南 。
3.通过@font-face加载指定字体文件并作为指定显示字体(可行)
但这里有个大坑我们要注意:在 JavaFx 的 @font-face 中 font-family:'name' 也无效,因此只能使用字体文件默认的 font-family 名称,听起来也可以接受,那就用默认的名称把;
用默认的名称也有个大坑,如果你用的是具有中文名称的字体,例如:阿里巴巴普惠体;我们还要吐血一次,在 JavaFx 中字体的 font-family 名称随操作系统而不同,我们以阿里巴巴普惠体为例,详细说明。
如果在Windows系统中安装阿里巴巴普惠体,在JavaFx中可以用以下方式指定字体,测试有效。
-fx-font-family: '阿里巴巴普惠体 3.0 45 Light';
如果在 JavaFx 的 @font-face 中加载阿里巴巴普惠体,使用上面的字体名称无效,实际的字体名称变成了另外的名称,有效名称如下所示。
-fx-font-family: 'Alibaba PuHuiTi 3.0 45 Light';
我们是通过以下代码,找出加载的字体在 JavaFx 中是以什么名称映射的。
final Font[] fonts = Font.loadFonts(getClass().getResourceAsStream("AlibabaPuHuiTi-3-45-Light.ttf"), 13);
for (Font font : fonts) {
System.out.println(font.getFamily());
}
总结一下:JavaFX 通过 CSS @font-face 加载的字体,font-family 名称可能不是我们预期的,并且也无法指定名称。
最终解决办法
1. 如果我们是特定的 Linux 系统可以直接通过JavaFX 的 CSS指定操作系统默认支持中文的字体,如果指定的字体不存在,将自动回退为 "System Regular",这将导致中文显示为方框,我们得提醒用户安装指定的字体。
.root {
-fx-font-family: 'Noto Sans CJK SC';
}
2.通过程序判断有哪些支持的中文字体,并设置到 JavaFx 窗口加载的根 Node ,此方法的好处是可以自动适配多个 Linux 平台以及其它平台,但是麻烦的是,我们需要为每个加载的窗口,添加代码;JavaFx 没有全局窗口的样式配置方式。
public static void defaultFont(Parent parent) {
final List<String> families = Font.getFamilies();
if (families.contains("Noto Sans CJK SC")) {
parent.setStyle("-fx-font-family: 'Noto Sans CJK SC';");
} else
if (families.contains("Source Han Sans CN")) {
parent.setStyle("-fx-font-family: 'Source Han Sans CN';");
} else
if (families.contains("WenQuanYi Micro Hei")) {
parent.setStyle("-fx-font-family: 'WenQuanYi Micro Hei';");
} else
if (families.contains("Hiragino Sans GB")) {
parent.setStyle("-fx-font-family: 'Hiragino Sans GB';");
} else
if (families.contains("PingFang SC")) {
parent.setStyle("-fx-font-family: 'PingFang SC';");
}
}
至此 JavaFx 中文字体问题,彻底解决;期待 JavaFx 在后续的版本中修复此问题,我们当前测试的环境是 Debian 12 + JavaFx 17。
以上是我们在开发 r11 程序时,为了通过 jpackage 将 javaFx 桌面程序打包为最终镜像时遇到的问题;r11 是一款用于将软件源代码导出为软件著作权代码文档的工具,推荐去官网 r11.joyzl.cn 下载试试。