为什么 java.awt.Font.getStringBounds 在不同机器上会产生不同的结果?

4

我有一个应用程序生成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报告。不同的尺寸会导致文本略微对齐不同。这只是几个像素,但如果有文本靠近边框,那么可能看起来相当尴尬。 - yankee
错过了。我的错。你从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(); }` - Andrew Thompson
1个回答

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

那些DEFAULT_CLASS的值可以验证你的“显然不是。什么可能导致这种差异?”。

对于某些nix系统,sun.font.fontmanager的值可能为sun.awt.X11FontManager,但对于Windows来说可能为null,因此管理器将会是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)

注意到无法加载 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(字形处理)+ freetype(渲染)本地实现,这确实让人放心。

所以...

• 您可以将生产服务器(简单的方法)作为呈现字体和渲染的参考,并基于它验证结果(而不是开发机器)。
• 或者,使用跨平台和独立的(可能是本地的)字形处理/渲染字体引擎/实现,以确保开发和生产结果将相同。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接