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

Posted

技术标签:

【中文标题】为啥 java.awt.Font.getStringBounds 在不同的机器上给出不同的结果?【英文标题】:Why does java.awt.Font.getStringBounds give different result on different machines?为什么 java.awt.Font.getStringBounds 在不同的机器上给出不同的结果? 【发布时间】:2020-12-30 02:56:10 【问题描述】:

我有一个生成 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)
));

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

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,name=Europa-Bold,style=plain,size=10]
java.awt.geom.Rectangle2D$Float[x=0.0,y=-7.6757812,w=20.06897,h=10.094452]

如您所见,我实际上是随应用程序一起提供了字体文件,所以我相信两台机器实际上不可能使用不同的字体。

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

【问题讨论】:

@AndrewThompson:正如问题的第一句话所写,我生成 PDF 报告。不同的大小会导致文本的对齐方式略有不同。它只是几个像素,但是如果有靠近边框的文本,那可能看起来已经很尴尬了。 错过了。我的错。您是否从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(); 【参考方案1】:

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

是的,它是本地的。例如,即使是新的 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 值可以验证您的显然不是。什么可能导致差异?标记。

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

现在每个经理,肯定会依赖于不同的底层整形/渲染引擎/impl (this may help)。

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

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

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

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)

注意 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]

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]

我的机器

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]

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

几年前我遇到过类似的问题,在 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 注册字体,然后使用Font 实例化它解决了我们的问题。所以你也可以试试看。

解决方案

最后,我把 jdk 的东西(脖子上真的很痛)降下来了,然后想出了harfbuzz(shaping) + freetype(rendering) native impl,这确实是一种安心.

所以...

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

【讨论】:

以上是关于为啥 java.awt.Font.getStringBounds 在不同的机器上给出不同的结果?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 DataGridView 上的 DoubleBuffered 属性默认为 false,为啥它受到保护?

为啥需要softmax函数?为啥不简单归一化?

为啥 g++ 需要 libstdc++.a?为啥不是默认值?

为啥或为啥不在 C++ 中使用 memset? [关闭]

为啥临时变量需要更改数组元素以及为啥需要在最后取消设置?

为啥 CAP 定理中的 RDBMS 分区不能容忍,为啥它可用?