为什么java.awt.Font.getStringBounds在不同的机器上给出不同的结果?

问题描述

我有一个生成PDF报告的应用程序(使用JasperReports),但是,如果我在开发笔记本电脑上运行我的应用程序,则文本字段的大小会与在服务器上生成完全相同的报告时略有不同。我最终将问题简化为以下代码

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",4,new FontRenderContext(null,true,true)
));

在我的笔记本电脑上打印:

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]

在服务器上打印:

java.awt.Font[family=Europa-Bold,y=-7.6757812,w=20.06897,h=10.094452]

如您所见,我实际上在应用程序中附带了字体文件,所以我相信两台机器实际上都不可能使用其他字体。

我会猜想在这些条件下getStringBounds输出与系统无关。显然不是。可能导致差异的原因是什么?

解决方法

免责声明! :我不是字体开发专家,只是分享我的经验。

是的,它是本地语言。例如,甚至新的JavaFX Web视图都取决于webkit

如果您深入研究getStringBounds的调试,您将意识到它已经到了一个点,字体管理器应该决定加载具体的字体管理器,而类名应该是系统属性sun.font.fontmanager

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);
}

这些DEFAULT_CLASS值可以验证您的显然不是。可能造成差异的原因是什么?标记。

sun.font.fontmanager的值在某些nix系统中可能是sun.awt.X11FontManager,但对于Windows可能是null,因此管理器将是sun.awt.Win32FontManager

现在,每位经理肯定都会依赖于不同的基础整形/渲染引擎/ Impl(this may help)。

主要原因可能是字体的性质。由于它们大多是矢量材料。因此,基于平台/环境,渲染的文本可能更大或更小。例如,也许Windows会在要求的文字渲染中应用桌面cleartype和屏幕文字大小(DPI)。

看来,即使您只有两个sun.awt.X11FontManager经理,结果也会有所不同。 this may help too

如果只是尝试使用示例代码,则在在线编译器上,肯定会遇到各种各样的结果。

ideatone(https://ideone.com/AuQvMV)的结果无法发生,stderr有一些有趣的信息

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)

请注意{/ {1}}是本地/ libfreetype字体渲染应用程序的失败/丢失负载

编码基础(https://www.tutorialspoint.com/compile_java_online.php)的结果

C

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.282227,w=22.09961,h=11.640625]

我的机器

fnt manager: sun.awt.X11FontManager
java.awt.Font[family=Dialog,y=-9.839991,w=24.0,h=12.569988]

我的故事 (可能有帮助,您可以尝试)

几年前,我遇到过类似的问题,在fnt manager: null java.awt.Font[family=Tahoma,y=-10.004883,w=19.399414,h=12.0703125] 上使用完全相同的嵌入字体进行文本渲染失败,原因是复杂的文本渲染(许多连字)。不仅是大小,而且还会打断连字,字距调整等等。

我可以使用另一种解决方法来解决我的问题(不记得是否修复了大小调整,但肯定没有断字)

macOs/jdk8

使用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); 注册字体,然后使用GraphicsEnvironment实例化字体解决了我们的问题。因此,您也可以尝试一下。

解决方案

最后,我只是永久性地降低了jdk的内容(脖子上的确很痛),并提出了Font(塑形)+ harfbuzz(渲染)本机暗示内心的平静。

所以...

•您可以考虑将生产服务器(简便方法)作为渲染字体前进和渲染的参考,并基于它验证结果(而不是开发机器)
•或者,使用交叉和独立的(可能是本机的)整形/渲染字体引擎/ Impl以确保开发和生产结果相同。