如何在Linux上配置简单的Java fontconfig.properties文件

8
我正在使用定制的Java 11运行环境在自定义的Linux硬件上,这个Java运行时不是我构建的。但我的应用程序需要访问字体,而运行时没有配置任何字体,因此我会得到以下堆栈跟踪:
Exception in thread "main" java.lang.InternalError: java.lang.reflect.InvocationTargetException
        at java.desktop/sun.font.FontManagerFactory$1.run(FontManagerFactory.java:86)
        at java.base/java.security.AccessController.doPrivileged(Native Method)
        at java.desktop/sun.font.FontManagerFactory.getInstance(FontManagerFactory.java:74)
        at java.desktop/java.awt.Font.getFont2D(Font.java:497)
        at java.desktop/java.awt.Font.getFamily(Font.java:1410)
        at java.desktop/java.awt.Font.getFamily_NoClientCode(Font.java:1384)
        at java.desktop/java.awt.Font.getFamily(Font.java:1376)
        at java.desktop/java.awt.Font.toString(Font.java:1869)
        at java.base/java.lang.String.valueOf(String.java:2951)
        at java.base/java.io.PrintStream.println(PrintStream.java:897)
        at Fonts.main(Fonts.java:7)
Caused by: java.lang.reflect.InvocationTargetException
        at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
        at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
        at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
        at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:490)
        at java.desktop/sun.font.FontManagerFactory$1.run(FontManagerFactory.java:84)
        ... 10 more
Caused by: java.lang.NullPointerException
        at java.desktop/sun.awt.FontConfiguration.getVersion(FontConfiguration.java:1262)
        at java.desktop/sun.awt.FontConfiguration.readFontConfigFile(FontConfiguration.java:225)
        at java.desktop/sun.awt.FontConfiguration.init(FontConfiguration.java:107)
        at java.desktop/sun.awt.X11FontManager.createFontConfiguration(X11FontManager.java:719)
        at java.desktop/sun.font.SunFontManager$2.run(SunFontManager.java:367)
        at java.base/java.security.AccessController.doPrivileged(Native Method)
        at java.desktop/sun.font.SunFontManager.<init>(SunFontManager.java:312)
        at java.desktop/sun.awt.FcFontManager.<init>(FcFontManager.java:35)
        at java.desktop/sun.awt.X11FontManager.<init>(X11FontManager.java:56) 

我可以提供一些字体,我已经确定需要创建一个 fontconfig.properties 文件并将其放入 Java 运行时的 lib 文件夹中,但我难以理解在 fontconfig.properties 中应该放置什么内容。
有人能否给我一个示例,在 Linux 上如何指定一个最小的字体集合,以防止出现异常。
更具体地说,我有一组 TrueType 字体,我将它们放入了 lib 文件夹中的一个 fonts 文件夹中,那么我该如何将此集合用作 Java 可用的字体集合。
  • LucidaBrightDemiItalic.ttf
  • LucidaBrightRegular.ttf
  • LucidaSansRegular.ttf
  • LucidaTypewriterRegular.ttf
  • LucidaBrightDemiBold.ttf
  • LucidaBrightItalic.ttf
  • LucidaSansDemiBold.ttf
  • LucidaTypewriterBold.ttf
如果我创建一个空的 fontconfig.properties 文件,那么第一个异常就会改变为:
Caused by: java.lang.NullPointerException
        at java.desktop/sun.awt.FontConfiguration.getInitELC(FontConfiguration.java:465)
        at java.desktop/sun.awt.FontConfiguration.initFontConfig(FontConfiguration.java:441)
        at java.desktop/sun.awt.FontConfiguration.init(FontConfiguration.java:108)
        at java.desktop/sun.awt.X11FontManager.createFontConfiguration(X11FontManager.java:719)
        at java.desktop/sun.font.SunFontManager$2.run(SunFontManager.java:367)
        at java.base/java.security.AccessController.doPrivileged(Native Method)
        at java.desktop/sun.font.SunFontManager.<init>(SunFontManager.java:312)
        at java.desktop/sun.awt.FcFontManager.<init>(FcFontManager.java:35)
        at java.desktop/sun.awt.X11FontManager.<init>(X11FontManager.java:56)

这表明Java运行时至少找到了(空的)fontconfig.properties文件,因此如果我可以正确配置它,这应该可以工作。

我尝试创建一个非常简单的fontconfig.properties文件,只有一个文件,但它没有起作用。

version=1

allfonts.plain.latin-1=-monotype-times new roman-medium-r-normal--*-%d-*-*-p-*-iso10646-1

filename.-monotype-times new roman-medium-r-normal--*-%d-*-*-p-*-iso10646-1=/mnt/app/opt/SongKong/songkong/jre/lib/fonts/ipag.ttf

awtfontpath.latin-1=/mnt/app/opt/SongKong/songkong/jre/lib/fonts

我相信你找到了这个页面。https://docs.oracle.com/javase/8/docs/technotes/guides/intl/fontconfig.html和这个示例页面https://alvinalexander.com/java/jwarehouse/openjdk-8/jdk/src/windows/classes/sun/awt/windows/fontconfig.properties.shtml以及https://github.com/srisatish/openjdk/blob/master/jdk/src/solaris/classes/sun/awt/fontconfigs/linux.fontconfig.properties。 - Gilbert Le Blanc
看过一些例子,但它们不是很清晰。我只需要在我的应用程序中为一个任务使用字体,所以我想知道的是在Linux服务器上一个最小有效的fontconfig.properties文件是什么。也就是说,如果在位置x有一个单独的ttf字体,那么我需要在fontconfig.properties中放置什么,以便Java应用程序在需要字体时调用该字体。 - Paul Taylor
我能否只使用一种字体,还是需要像问题中列出的Lucida字体一样提供一组字体? - Paul Taylor
令人惊讶的是,他们把将字体文件添加到Java系统这样简单的任务变得如此困难。当然,所有这些配置灵活性可能都有其目的。但是拜托,你还需要一个基本的配置指令和适当的默认值,这样你就不必成为整个Java字体生态系统的专家来添加单个字体文件了。Windows允许您安装字体文件而无需指定组件字体、搜索序列、字符排除等等。 - deltamind106
1个回答

3
Jdk发布版本不会附带任何字体,Oracle发布版本通常会附带字体。因此,我想下载一个Oracle发布版本并查看它们的做法,但是Windows和UNIX版本之间的fontconfig.properties文件有很大的区别,所以我需要一个UNIX版本。
我首先下载了jdk-11.0.6_linux-x64_bin.tar.gz,但是这个版本没有fontconfig.properties文件,可能是因为它只是一个通用的Linux构建版本,而不是针对特定版本的。由于我的主要开发机器是Windows,我不想尝试.deb或.rpm版本,因为没有简单的方法可以安装它们。因此,我下载了Solaris jdk-11.0.6_solaris-sparcv9_bin.tar.gz并解压缩它。
这个版本包含一个按照以下结构命名的font.properties.src文件。
Version =1
# Component Font Mappings
# Search Sequences
# Font Filenames
# AWT X11 font paths

我的理解是,组件字体映射Java组件字体映射到一个逻辑字体名称搜索序列根据Java组件字体指定搜索字体的顺序。 字体文件名将逻辑字体名称映射到字体在机器上的实际文件名AWT X11字体路径指定了从组件字体名称到包含机器上实际字体的实际文件夹的路径。

因此,我对该文件进行了搜索和替换,用我的服务器上的字体位置替换了实际文件名,并用包含实际字体的文件夹位置替换了实际文件夹

然后,我将这个修改后的fontconfig.proprties.src重命名为fontconfig.properties并存储在jre/lib文件夹中。

一个之前失败的简单测试程序现在可以工作了。

import java.awt.*;
public class Fonts
{
     public static void main(String[] args) throws Exception
     {
         Font defaultFont = Font.decode(null);
         System.out.println(defaultFont);
     }
}

然而,我只指定了一个字体(ipag.ttf)用于不同的脚本和不同的样式(普通、粗体等)。

当我运行程序时,需要将字体用于 jakarta.poi(用于创建Excel电子表格文件),此时会出现以下异常:

java.lang.ClassCastException: class sun.font.CompositeFont cannot be cast to class sun.font.PhysicalFont (sun.font.CompositeFont and sun.font.PhysicalFont are in module java.desktop of loader 'bootstrap')
    at java.desktop/sun.font.SunFontManager.getDefaultPhysicalFont(SunFontManager.java:1086)
    at java.desktop/sun.font.SunFontManager.initialiseDeferredFont(SunFontManager.java:965)
    at java.desktop/sun.font.SunFontManager.findOtherDeferredFont(SunFontManager.java:903)
    at java.desktop/sun.font.SunFontManager.findDeferredFont(SunFontManager.java:919)
    at java.desktop/sun.font.SunFontManager.findFont2D(SunFontManager.java:2120)
    at java.desktop/java.awt.Font.getFont2D(Font.java:506)
    at java.desktop/java.awt.Font.canDisplayUpTo(Font.java:2246)
    at java.desktop/java.awt.font.TextLayout.singleFont(TextLayout.java:469)
    at java.desktop/java.awt.font.TextLayout.<init>(TextLayout.java:530)
    at org.apache.poi.ss.util.SheetUtil.getDefaultCharWidth(SheetUtil.java:275)

我认为这里的问题在于期望使用实体字体,但由于我未指定粗体、斜体等样式,Java尝试基于对实体字体的修改创建字体,从而创建了组合字体。但是Java始终期望提供某些基本样式的实体字体。
因此,我将Lucida字体复制到字体目录中,并修改了fontconfig.properties文件以使用这些字体变体,如下所示:
filename.-monotype-arial-medium-r-normal--*-%d-*-*-p-*-iso10646-1=/mnt/app/opt/jre/lib/fonts/LucidaSansRegular.ttf
filename.-monotype-arial-medium-i-normal--*-%d-*-*-p-*-iso10646-1=/mnt/app/opt/jre/lib/fonts/LucidaSansRegular.ttf
filename.-monotype-arial-bold-r-normal--*-%d-*-*-p-*-iso10646-1=/mnt/app/opt/jre/lib/fonts/LucidaSansDemiBold.ttf
filename.-monotype-arial-bold-i-normal--*-%d-*-*-p-*-iso10646-1=/mnt/app/opt/jre/lib/fonts/LucidaSansDemiBold.ttf
filename.-monotype-courier_new-medium-r-normal--*-%d-*-*-p-*-iso10646-1=/mnt/app/opt/jre/lib/fonts/LucidaTypewriterRegular.ttf
filename.-monotype-courier_new-medium-i-normal--*-%d-*-*-p-*-iso10646-1=/mnt/app/opt/jre/lib/fonts/LucidaTypewriterRegular.ttf
filename.-monotype-courier_new-bold-r-normal--*-%d-*-*-p-*-iso10646-1=/mnt/app/opt/jre/lib/fonts/LucidaTypewriterBold.ttf
filename.-monotype-courier_new-bold-i-normal--*-%d-*-*-p-*-iso10646-1=/mnt/app/opt/jre/lib/fonts/LucidaTypewriterBold.ttf
filename.-monotype-times_new_roman-medium-r-normal--*-%d-*-*-p-*-iso10646-1=/mnt/app/opt/jre/lib/fonts/LucidaBrightRegular.ttf
filename.-monotype-times_new_roman-medium-i-normal--*-%d-*-*-p-*-iso10646-1=/mnt/app/opt/jre/lib/fonts/LucidaBrightItalic.ttf
filename.-monotype-times_new_roman-bold-r-normal--*-%d-*-*-p-*-iso10646-1=/mnt/app/opt/jre/lib/fonts/LucidaBrightDemiBold.ttf
filename.-monotype-times_new_roman-bold-i-normal--*-%d-*-*-p-*-iso10646-1=/mnt/app/opt/jre/lib/fonts/LucidaBrightDemiItalic.ttf

只需保留ipag.ttf字体文件以支持东南亚文字,然后重新启动应用程序即可解决问题。但我不确定这种方法是否在所有情况下都有效。


请问,我该如何找到 xlfd name (-monotype-arial-medium-r-normal--*-%d-*-*-p-*-iso10646-1),仅知道字体名称(Lucida[SansRegular])的情况下,请您解释一下。 - I.G.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接