JDK-8264703 : Numbers and latin letters rendering in different fonts
  • Type: Bug
  • Component: client-libs
  • Sub-Component: 2d
  • Affected Version: 13,14,15,16
  • Priority: P4
  • Status: Open
  • Resolution: Unresolved
  • OS: linux
  • CPU: x86_64
  • Submitted: 2021-04-01
  • Updated: 2022-11-16
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
Related Reports
Duplicate :  
Duplicate :  
Relates :  
Tested using the Java 11 build distributed with ArchLinux, and using a Oracle OpenJDK 16
build (though this also appeared in ArchLinux's default OpenJDK 15 build):

IMPLEMENTOR="Oracle Corporation"
MODULES="java.base java.compiler java.datatransfer java.xml java.prefs java.desktop java.instrument java.logging java.management java.security.sasl java.naming java.rmi java.management.rmi java.net.http java.scripting java.security.jgss java.transaction.xa java.sql java.sql.rowset java.xml.crypto java.se java.smartcardio jdk.accessibility jdk.internal.jvmstat jdk.attach jdk.charsets jdk.compiler jdk.crypto.ec jdk.crypto.cryptoki jdk.dynalink jdk.internal.ed jdk.editpad jdk.hotspot.agent jdk.httpserver jdk.incubator.foreign jdk.incubator.vector jdk.internal.le jdk.internal.opt jdk.internal.vm.ci jdk.jartool jdk.javadoc jdk.jcmd jdk.management jdk.management.agent jdk.jconsole jdk.jdeps jdk.jdwp.agent jdk.jdi jdk.jfr jdk.jlink jdk.jpackage jdk.jshell jdk.jsobject jdk.jstatd jdk.localedata jdk.management.jfr jdk.naming.dns jdk.naming.rmi jdk.net jdk.nio.mapmode jdk.sctp jdk.security.auth jdk.security.jgss jdk.unsupported jdk.unsupported.desktop jdk.xml.dom jdk.zipfs"

Text in Swing UIs using the Dialog.plain font are rendered using a mixture of two fonts - one for latin text and
one for numbers. Unfortunately as this appears to be the default font, the effects are quite widespread in the
tested applications that don't set the font themselves.

An example of this rendering can be found here (I coundn't see any way to post images in the bug
report form): <CAPTURE1.PNG><ATTACHED> - while the example is in a table, this appears everywhere and not
just tables.

I've gone through this in a debugger and think I've found the cause:

The system default font as reported by fc-match is: DejaVuSans.ttf: "DejaVu Sans" "Book"

This font has a subfamily of 'Book' but the full name is still only "DejaVu Sans" (for comparison, the
bold variant has a subfamily of 'Bold' and it's included in the full name: "DejaVu Sans Bold"). Apparently
as a result of the subfamily font-config is producing the full name "DejaVu Sans Book".

Running fc-cat | grep 'DejaVu Sans Book' thus presents:

"DejaVuSans.ttf" 0 "DejaVu Sans:familylang=en:style=Book:stylelang=en:fullname=DejaVu Sans Book:<other font rendering information>

This then appears to make it's way into Java's font cache: ~/.java/fonts/16-ea/fcinfo-1-gearbox-Arch-rolling-en-NZ.properties
sansserif.3.3.fullName=DejaVu Sans Book

This causes a problem in CompositeFont.java:296 which checks the full name loaded from the TrueType file matches
the corresponding name in the array passed to it's constructor:

	} else if (!componentNames[slot].equalsIgnoreCase(name)) {
		/* If a component specifies the file with a bad font,
		* the corresponding slot will be initialized by
		* default physical font. In such case findFont2D may
		* return composite font which cannot be casted to
		* physical font.

This makes it's way to getDefaultPhysicalFont. Unfortunately the font in question *is* the default physical
font, and that causes SunFontManager.java:1042 to run:

	if (defaultPhysicalFont == null) {
		/* Because of the findFont2D call above, if we reach here, we
			* know all fonts have already been loaded, just accept any
			* match at this point. If this fails we are in real trouble
			* and I don't know how to recover from there being absolutely
			* no fonts anywhere on the system.
		defaultPhysicalFont = physicalFonts.values().stream().findFirst()
			.orElseThrow(()->new Error("Probable fatal error: No physical fonts found."));

This ends up using "Noto Sans Devanagari UI Thin" as the default font, which contains arabic numerals
but no latin characters. Since this is inside a CompositeFont those characters will fall through to the next
font, which is "DejaVu Sans Bold" - the combination of thin and bold text leads to the visually jarring text
shown above.

Running "otfinfo /usr/share/fonts/TTF/DejaVuSans.ttf --info" shows that the 'Book' subfamily is not part
of the full name:

Family:              DejaVu Sans
Subfamily:           Book
Full name:           DejaVu Sans
PostScript name:     DejaVuSans
Preferred family:    DejaVu Sans
Preferred subfamily: Book
Version:             Version 2.37
Unique ID:           DejaVu Sans
Manufacturer:        DejaVu fonts team
Vendor URL:          http://dejavu.sourceforge.net
Copyright:           Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved.
Copyright (c) 2006 by Tavmjong Bah. All Rights Reserved.
DejaVu changes are in public domain

The SHA256 hash of this file is 2fb91d8d18b8312659070e66001a863b82fb6758a21e37df2c94f1cae83683ed.

Testing shows the following values are showing up to the CompositeFont constructor:

Java 11:
CompositeFont.component{File}Names start withs "/usr/share/fonts/TTF/DejaVuSans.ttf" and "DejaVu Sans"

Java 16:
CompositeFont.component{File}Names start withs "/usr/share/fonts/TTF/DejaVuSans.ttf" and "DejaVu Sans Book"
CompositeFont.java:296 - componentNames[slot]="DejaVu Sans Book" while name="DejaVu Sans"

This appears to be caused by JDK-8219901, which uses the full names rather than the family names of fonts.

REGRESSION : Last worked in version 11

Set DejaVu as the default system font on Linux - this is the default with ArchLinux and KDE.

Text should render using the selected default system font.
The characters are rendered using two different versions of the system fault: <CAPTURE2.PNG><ATTACHED>
---------- BEGIN SOURCE ----------
import javax.swing.*;

class Scratch {
    public static void main(String[] args) {
        JFrame frame = new JFrame("Hello World");
        frame.setContentPane(new JTextArea("Testing 1234 test abc123"));
---------- END SOURCE ----------

FREQUENCY : always

My recommendation to anyone encountering this is to take it up with the maintainer of fontconfig

Not getting very far with that fontconfig bug submission. It should be a simple reversion but for reasons that make no sense I am getting push back. And on top of that now this fontconfig bug is in the wild. It seems that for any font that is of "regular" style / weight we can't trust the name returned by fontconfig We stash that name in the fontconfig.properties file and we also use it to enumerate fonts on the system. Putting in checks "after the fact" could be quite a whack-a-mole and probably too late .. if we already reported to the app there's a font called "Deja Vu Sans Book" but there isn't really. We could try to perpetuate the lie .. Or we add new code so that every case that we think *might* be affected, we up-front open the font file and check what the real name is and change it before it propagates.

I submitted fontconfig bug https://gitlab.freedesktop.org/fontconfig/fontconfig/-/issues/277

> as a result of the subfamily font-config is producing the full name "DejaVu Sans Book". That seems like it is a bug in fontconfig. I've downloaded this font (the latest version) and the full name is "DejaVu Sans". Nowhere in the font can find the string "DejaVu Sans Book". "book" appears solely on its own as the SubFamily name. The OpenType spec agrees it is common for the full name to be the concatenation of family + sub-family but nothing says that it must be or provides for an API to mislead the client. I found this https://gitlab.freedesktop.org/fontconfig/fontconfig/-/merge_requests/82 And this code in fcfopentype.c if (FcStrCmpIgnoreBlanksAndCase(style, (const FcChar8 *) "Regular") != 0) { FcStrBufChar (&sbuf, ' '); FcStrBufString (&sbuf, style); } and originally I think pushed here https://gitlab.freedesktop.org/fontconfig/fontconfig/-/merge_requests/82/diffs?commit_id=49ee062f8df74cb134224c903a25cc7594da3ceb So the irony is somewhat that I think the assumption is that the "Regular" font will always have "Regular" as the style string and by (1) not using the fullname which might have it and (2) skipping the case where the font style is regular, that the full name of the regular font ends up as the same as the family.