javafontsopenjdk-11

How do I configure simple Java fontconfig.properties file for use on Linux


I am using a custom Java 11 runtime on custom linux hardware, the Java runtime was not built my me. But I have a problem my application requires access to a font and the runtime is not configured with any so I get this stacktrace

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) 

I can provide some fonts and I have worked out I need to create a fontconfig.properties and put i into the Java runtimes lib folder, but I am struggling to understand what I need to put into fontconfig.properties.

Can someone give me an example of how to specify a minimal set of fonts in fontconfig.properties on linux to prevent the exception occurring.

More specificially, I have a set of truetype fonts that I have put into a fonts folder within the lib folder so how do I use this set as a set of fonts available to Java

If I create an empty fontconfig.properties file then the first exception changes to

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)

so this shows Java runtime is at least finding the (empty) fontconfig.properties file so if I can configure it correctly this should work.

I tried to create a very minimal fontconfig.properties file with one file but it didnt work.

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

Solution

  • Jdk releases do not come with any fonts, Oracle releases generally do, so I had an idea to download an Oracle release and see what they do but the fontconfig.properties file differs significantly between Windows and UNIX releases so I needed a UNIX release.

    I first downloaded jdk-11.0.6_linux-x64_bin.tar.gz but this had no fontconfig.properties file probably because it was just a generic linux buiild rather than tied to any in particular. Since my main dev machine is Windows I wasn't keen to try out the .deb or .rpm builds because no easy route for me to install them. So instead I download the Solaris jdk-11.0.6_solaris-sparcv9_bin.tar.gz and unzipped that.

    That did contain a font.properties.src file following this structure

    Version =1
    # Component Font Mappings
    # Search Sequences
    # Font Filenames
    # AWT X11 font paths
    

    My understanding is the Component Font Mappings map from a Java component font to a logical font name, Search Sequence specifies an order to search for fonts based on the Java component fonts. Font Filenames map from logical font name to actual filename where the font is located on machine. AWT X11 font paths specifies from the component font name to the actual folder containing the actual font on machine.

    So I did a search and replace on the file replacing actual filename with the location on my font on the server, and replacing actual folder with the location on the folder containing the actual font.

    I then renamed this modified fontconfig.proprties.src to fontconfig.properties and stored it in the jre/lib folder

    A simple test program that previously failed now works

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

    However I have only specified one font (ipag.ttf) to be used for different scripts and for different styles (plain, bold etc).

    When I run the program the fonts are required for use with jakarta.poi (for creating an excel spreadsheet file) and this now give the following exception:

    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)
    

    I think the problem here is that a Physical font was expected but because I had not specified bold, italic fonts ectera Java try to create a font based on modifications to a physical font, creating Composite font. But Java always expects Physical fonts to be provided for some basic styles.

    So I then copied the Lucida fonts to the fonts dir, modified the fontconfig. properties file to use these these font variations as follows

    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
    

    Just kept the ipag.ttf for South East Asian scripts and restarted application and that worked. Whether or not this will work in all circumstances I am not sure.