I'm noticing differences in font spacing using OpenJDK compared to OracleJDK. I've narrowed this down to the fonts. They are rendered by OpenJDK ever so slightly wider... Careful visual inspection of the screenshot above shows the character widths are identical, the only difference is the spacing. I also confirmed this with programmatic check of font metrics for all characters A-Za-z0-9.
e.g. the String "Dialog - plain" at 12pt is
I've searched extensively for information on this and found various options including -Dawt.useSystemAAFontSettings
, -Dswing.useSystemFontSettings
, -Dswing.defaultlaf=com.sun.java.swing.plaf.gtk.GTKLookAndFeel
from Java_Runtime_Environment_fonts. I've tried altering all of these but the results remain the same.
Further investigation has found sun.font.FontScaler
, this uses different underlying fontscaler. This appears partially configurable in sun.font.FontUtilities
which checks the system property for -Dsun.java2d.font.scaler=t2k
, however setting this makes no difference.
My question: can FreetypeFontScaler
be configured to behave in a similar or closer way to T2KFontScaler?
if (FontUtilities.isOpenJDK) {
scalerClass = Class.forName("sun.font.FreetypeFontScaler");
} else {
scalerClass = Class.forName("sun.font.T2KFontScaler");
}
This is the test program I've been using
public class FontTester {
public static void main(String[] args) throws Exception {
System.out.println(String.format("java.home=%s", System.getProperty("java.home")));
String family = Font.DIALOG;
int style = Font.PLAIN;
describeFont(new Font(family, style, 12));
JFrame frame = new JFrame();
frame.setSize(800, 600);
frame.add(new DemoPanel());
frame.setVisible(true);
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
}
private static class DemoPanel extends JPanel {
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
String family = Font.DIALOG;
int style = Font.PLAIN;
Font font = new Font(family, style, 20);
g.setFont(font);
String str = family + " - " + name(font) + " ";
Rectangle2D bounds = g.getFontMetrics().getStringBounds(str, g);
str += String.format("%f x %f", bounds.getWidth(), bounds.getHeight());
g.drawString(str, 10, 50);
}
private String name(Font font) {
List<String> attrs = new ArrayList<>();
if (font.isBold()) {
attrs.add("bold");
}
if (font.isItalic()) {
attrs.add("italic");
}
if (font.isPlain()) {
attrs.add("plain");
}
return String.join(",", attrs);
}
}
private static void describeFont(Font font) throws Exception {
Method method = Font.class.getDeclaredMethod("getFont2D");
method.setAccessible(true);
Font2D font2d = (Font2D) method.invoke(font);
System.out.print(String.format("%s: ", font));
describeFont2D(font2d);
}
private static void describeFont2D(Font2D font) {
if (font instanceof CompositeFont) {
CompositeFont cf = (CompositeFont) font;
for (int i = 0; i < cf.getNumSlots(); i++) {
PhysicalFont pf = cf.getSlotFont(i);
describeFont2D(pf);
break;
}
} else {
System.out.print(String.format("-> %s \n", font));
}
}
}
Yet more investigation has traced this though to sun.font.FontStrike.getGlyphMetrics(int)
returning different results. For a glyph-id 39 ("D") the advance X value is returned as 14.0px using Oracle JDK (via T2KFontScaler) but 15.0px using OpenJDK (via FreetypeFontScaler)
To figure which is "correct" I used the fontbox Java parser to extract the advance X value for Glyph-ID 39 from the HMTX table within the TTF file LiberationSans-Regular.ttf. The value is 1479 font design units - which maps to 14.44px at 20pt font size.
There is no difference for the next character "i" - glyph-id 76. This is 4.44 according to fontbox, and returned as 4 by both T2KScaler and FreetypeScaler
I've further narrowed this down to the rounding / calculation used when standard "non-fractional" metrics are used. If fractional metrics are enabled then both Oracle and Open JDKs behave identically, although the widths are still slighly smaller than the Oracle non-fractional case.
g2.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
Update - Jan 2020 * In Java 11 there is new option which may be related - see https://bugs.openjdk.java.net/browse/JDK-8217731
FREETYPE_PROPERTIES=truetype:interpreter-version=35
Performance. There's no real technical difference between the two, since the build process for Oracle JDK is based on that of OpenJDK. When it comes to performance, Oracle's is much better regarding responsiveness and JVM performance.
The biggest difference between OpenJDK and Oracle JDK is licensing. OpenJDK is completely open source Java with a GNU General Public License. Oracle JDK requires a commercial license under Oracle Binary Code License Agreement. But there are many other differences within support and cost, too.
Oracle JDK provides much better performance compared to the OpenJDK in terms of responsiveness and JVM performance. Oracle JDK has less open source community compared to the OpenJDK where OpenJDK community users outperform the features released by Oracle JDK to improve the performance.
Further investigation has found sun.font.FontScaler, this uses different underlying fontscaler. This appears partially configurable in sun.font.FontUtilities which checks the system property for -Dsun.java2d.font.scaler=t2k, however setting this makes no difference.
You're correct in that the underlying font scaler is different between Oracle and OpenJDK - unfortunately however, this is hard coded and not configurable.
The relevant code is in FontScaler:97:
if (FontUtilities.isOpenJDK) {
scalerClass = Class.forName("sun.font.FreetypeFontScaler");
} else {
scalerClass = Class.forName("sun.font.T2KFontScaler");
}
And the isOpenJDK
flag? It's set by FontUtilities:125:
File lucidaFile = new File(jreFontDirName + File.separator + LUCIDA_FILE_NAME);
isOpenJDK = !lucidaFile.exists();
And that constant:
static final String LUCIDA_FILE_NAME = "LucidaSansRegular.ttf";
Unless I've missed something in those source files, there's no other configuration flag or any other condition that will change that scaler class being used.
So the only vaguely sensible way of doing this (I'm excluding horrible classloader / reflection hacks here) is to add that Lucida file in place. Unfortunately though in most scenarios I can think of (assuming you're not distributing the JRE along with the package) this isn't really going to be a viable solution either.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With