Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Fonts slightly wider in OpenJDK vs OracleJDK

Tags:

java

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.

OpenJDK vs OracleJDK fonts

e.g. the String "Dialog - plain" at 12pt is

  • 125px wide in OpenJDK - my build of 8u131-b11
  • 125px wide in OpenJDK - stock RPM from redhat disk - 1.8u45-b13
  • 120px wide in OracleJDK - 8u131-b11 release from Oracle website

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
like image 817
Adam Avatar asked Dec 19 '17 10:12

Adam


People also ask

Is OpenJDK better?

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.

What is the difference between OpenJDK and Java version?

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.

Is OpenJDK slower than Oracle?

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.


1 Answers

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.

like image 122
Michael Berry Avatar answered Oct 04 '22 15:10

Michael Berry