Why does java.awt.Font.getStringBounds give different result on different machines?
Asked Answered
T

1

5

I have an application that generates PDF reports (using JasperReports), however if I run my application on my development laptop the text fields have a slightly different size than when I generate the exact same report on the server. I eventually reduced the issue to the following code:

final Font font = Font.createFont(
    Font.TRUETYPE_FONT,
    MyTest.class.getResourceAsStream("/fonts/europa/Europa-Bold.otf")
).deriveFont(10f);
System.out.println(font);
System.out.println(font.getStringBounds(
    "Text",
    0,
    4,
    new FontRenderContext(null, true, true)
));

On my Laptop this prints:

java.awt.Font[family=Europa-Bold,name=Europa-Bold,style=plain,size=10]
java.awt.geom.Rectangle2D$Float[x=0.0,y=-9.90999,w=20.080002,h=12.669988]

On the server this prints:

java.awt.Font[family=Europa-Bold,name=Europa-Bold,style=plain,size=10]
java.awt.geom.Rectangle2D$Float[x=0.0,y=-7.6757812,w=20.06897,h=10.094452]

As you can see I actually ship the font file with the application, so I believe that there is no chance that both machines actually work with a different font.

I would have guessed that under these conditions the output of getStringBounds is system-independent. Obviously it is not. What could possibly cause the difference?

Tympan answered 11/9, 2020 at 14:54 Comment(2)
@AndrewThompson: As written in the first sentence of the question I generate PDF reports. The different sizes cause the text to align slightly differently. It is just a few pixels, however if there is text close to a border, than that may already look rather awkward.Tympan
Missed that. My bad. Do you get a consistent result from the GlyphVector? public static Shape getShapeOfText(Font font, String msg) { BufferedImage bi = new BufferedImage( 1, 1, BufferedImage.TYPE_INT_RGB); Graphics2D g = bi.createGraphics(); FontRenderContext frc = g.getFontRenderContext(); GlyphVector gv = font.createGlyphVector(frc, msg); return gv.getOutline(); }Mancunian
C
6

Disclaimer! : I'm not a font dev expert, just sharing my experience.

Yes, it's kind of native. Even new JavaFX web view for example, is depends on webkit.

If you dive into debugging for getStringBounds, you will realize it reaches a point, where font manager should decide to load a concreted font manager, where the class name is supposed to be system property sun.font.fontmanager.

Source code of sun.font.FontManagerFactory

...
private static final String DEFAULT_CLASS;
    static {
        if (FontUtilities.isWindows) {
            DEFAULT_CLASS = "sun.awt.Win32FontManager";
        } else if (FontUtilities.isMacOSX) {
            DEFAULT_CLASS = "sun.font.CFontManager";
            } else {
            DEFAULT_CLASS = "sun.awt.X11FontManager";
            }
    }
...
public static synchronized FontManager getInstance() {
...
String fmClassName = System.getProperty("sun.font.fontmanager", DEFAULT_CLASS);
}

Those DEFAULT_CLASS values could validate your Obviously it is not. What could possibly cause the difference? mark.

The val for sun.font.fontmanager may be sun.awt.X11FontManager for some nix systems, but could be null for windows for instance, so the manager will be sun.awt.Win32FontManager.

Now each manager, may depends on vary underlying shaping/rendering engine/impl for sure(this may help).

Main reason could be the nature of fonts. As they are mostly vector stuffs. So based on platform/env, a rendered text could be bigger, or smaller. e.g., maybe windows apply desktop cleartype, and screen text size(DPI) on requested text rendering.

It seems, even if you have exactly two sun.awt.X11FontManager manager, the results will be vary. this may help too

If you just try out the sample code, on online compilers, you will face with vary results for sure.

Result of ideaone (https://ideone.com/AuQvMV), could not be happened, stderr has some interesting info

java.lang.UnsatisfiedLinkError: /opt/jdk/lib/libfontmanager.so: libfreetype.so.6: cannot open shared object file: No such file or directory
    at java.base/java.lang.ClassLoader$NativeLibrary.load0(Native Method)
    at java.base/java.lang.ClassLoader$NativeLibrary.load(ClassLoader.java:2430)
    at java.base/java.lang.ClassLoader$NativeLibrary.loadLibrary(ClassLoader.java:2487)
    at java.base/java.lang.ClassLoader.loadLibrary0(ClassLoader.java:2684)
    at java.base/java.lang.ClassLoader.loadLibrary(ClassLoader.java:2638)
    at java.base/java.lang.Runtime.loadLibrary0(Runtime.java:827)
    at java.base/java.lang.System.loadLibrary(System.java:1902)
    at java.desktop/sun.font.FontManagerNativeLibrary$1.run(FontManagerNativeLibrary.java:57)
    at java.base/java.security.AccessController.doPrivileged(AccessController.java:310)
    at java.desktop/sun.font.FontManagerNativeLibrary.<clinit>(FontManagerNativeLibrary.java:32)
    at java.desktop/sun.font.SunFontManager$1.run(SunFontManager.java:270)
    at java.base/java.security.AccessController.doPrivileged(AccessController.java:310)
    at java.desktop/sun.font.SunFontManager.<clinit>(SunFontManager.java:266)
    at java.base/java.lang.Class.forName0(Native Method)
    at java.base/java.lang.Class.forName(Class.java:415)
    at java.desktop/sun.font.FontManagerFactory$1.run(FontManagerFactory.java:82)
    at java.base/java.security.AccessController.doPrivileged(AccessController.java:310)
    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:3042)
    at java.base/java.io.PrintStream.println(PrintStream.java:897)
    at Ideone.main(Main.java:19)

Note the failed/missed load of libfreetype which is a native/C font rendering app

Result of coding ground (https://www.tutorialspoint.com/compile_java_online.php)

fnt manager: sun.awt.X11FontManager
java.awt.Font[family=Dialog,name=tahoma,style=plain,size=10]
java.awt.geom.Rectangle2D$Float[x=0.0,y=-9.282227,w=22.09961,h=11.640625]

Result of jdoodle (https://www.jdoodle.com/online-java-compiler/)

fnt manager: sun.awt.X11FontManager
java.awt.Font[family=Dialog,name=tahoma,style=plain,size=10]
java.awt.geom.Rectangle2D$Float[x=0.0,y=-9.839991,w=24.0,h=12.569988]

My machine

fnt manager: null
java.awt.Font[family=Tahoma,name=tahoma,style=plain,size=10]
java.awt.geom.Rectangle2D$Float[x=0.0,y=-10.004883,w=19.399414,h=12.0703125]

My Story (may help, you may try)

I had similar issue back in some years ago, where text rendering using exactly same embedded font failed on macOs/jdk8, for complex text rendering(lots of ligatures). Not just sizes, but also broken ligatures, kerning, etc...

I could fix my issue(cannot remember if fixed the sizing, but no broken ligatures for sure), using another workground, as following

InputStream is = Main.class.getResourceAsStream(fontFile);
Font newFont = Font.createFont(Font.TRUETYPE_FONT, is);
GraphicsEnvironment.getLocalGraphicsEnvironment().registerFont(newFont);
//later load the font by constructing a Font ins
Font f = new Font(name/*name of the embedded font*/, style, size);

Registering font using GraphicsEnvironment, and then instancing it using Font fixed our issue. So you may also give it a try.

Solution

Finally, I just step-down the jdk stuffs(it's really great pain in neck) for good, and came up with harfbuzz(shaping) + freetype(rendering) native impl that indeed was a peace in mind.

So...

• You may consider your production server(easy way) as reference for rendered font advance and rendering, and validate the result based on it(rather than dev machine)
• Or, use a cross and standalone (and probably native) shaping/rendering font engine/impl to make sure dev and production results will be the same.

Cervicitis answered 17/9, 2020 at 0:39 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.