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

yan*_*kee 4 java fonts awt

我有一个生成 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",
    0,
    4,
    new FontRenderContext(null, true, true)
));
Run Code Online (Sandbox Code Playgroud)

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

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]
Run Code Online (Sandbox Code Playgroud)

在服务器上打印:

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]
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,我实际上将字体文件与应用程序一起发送,因此我相信两台机器实际上不可能使用不同的字体。

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

小智 6

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

是的,这有点本土化。例如,即使是新的 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);
}
Run Code Online (Sandbox Code Playgroud)

这些DEFAULT_CLASS值可以验证您的显然不是。什么可能导致差异?标记。

val forsun.font.fontmanager可能sun.awt.X11FontManager适用于某些 nix 系统,但也可能null适用于 Windows,因此管理器将为sun.awt.Win32FontManager.

现在,每个管理器可能取决于不同的底层整形/渲染引擎/实现(这可能会有所帮助)。

主要原因可能是字体的性质。因为它们大多是矢量素材。因此,基于平台/环境,呈现的文本可能更大或更小。例如,Windows 可能会在请求的文本呈现上应用桌面 cleartype 和屏幕文本大小 (DPI)。

看起来,即使你正好有两个sun.awt.X11FontManager经理,结果也会有所不同。这也可能有帮助

如果您只是在在线编译器上尝试示例代码,您肯定会遇到不同的结果。

ideaone ( 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)
Run Code Online (Sandbox Code Playgroud)

请注意失败/错过的加载libfreetype是本机/C字体渲染应用程序

编码基础的结果(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]
Run Code Online (Sandbox Code Playgroud)

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]
Run Code Online (Sandbox Code Playgroud)

我的机器

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]
Run Code Online (Sandbox Code Playgroud)

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

几年前我遇到了类似的问题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);
Run Code Online (Sandbox Code Playgroud)

使用 注册字体GraphicsEnvironment,然后使用 实例化它Font解决了我们的问题。所以你也可以试一试。

解决方案

最后,我只是彻底放弃了 jdk 的东西(这真的是让脖子很痛),并提出了harfbuzz(整形)+ freetype(渲染)本机 impl,这确实让我感到安心。

所以...

• 您可以将您的生产服务器(简单方法)作为渲染字体推进和渲染的参考,并根据它(而不是开发机器)验证结果
• 或者,使用交叉和独立(可能是原生)整形/渲染字体engine/impl 以确保开发和生产结果相同。