给定字体pt大小和dpi屏幕分辨率的Java2d / AWT文本呈现奇怪的大小/边界?

Posted

技术标签:

【中文标题】给定字体pt大小和dpi屏幕分辨率的Java2d / AWT文本呈现奇怪的大小/边界?【英文标题】:Weird sizes / bounds on Java2d / AWT text rendering with given font pt size and dpi screen resolution? 【发布时间】:2016-03-24 04:48:29 【问题描述】:

我刚刚开始使用 Java2D / AWT 在屏幕上直接渲染文本。基本上它可以工作,但我无法理解 AWT 报告并呈现在屏幕上的结果的像素大小/尺寸。

参数:

文本是“这是一个字形测试” 字体为 java.awt。Font[family=Dialog,name=sans,style=plain,size=72] (pt) 屏幕分辨率为 96 dpi 平台是 x86-64 Linux X11Java 8

已使用 Toolkit.getDefaultToolkit().getScreenResolution() 以编程方式确定屏幕分辨率,并对应于 xdpyinfo

fontMetrics.get...()graphics.drawString()drawGlyphVector()的结果都与这些一致恕我直言,结果值有些奇怪:

边界为 java.awt.geom.Rectangle2D$Float[x=0.0,y=-71.55469,w=448.0,h=55.875] 字体上升:45 px 字体下降:12 px 字体高度:57 像素

如果我将字体大小从 72 更改为 192 pt,结果是一致的:java.awt.geom.Rectangle2D$Float[x=0.0,y=-190.8125,w=1208.0,h=149.0],上升:119,下降:31,高度:150。如果我改变字体,只有宽度改变,而高度保持不变。 (这是我的预期。)

我假设 AWT 使用 DTP 指标,其中 1 pt总是 1/72 英寸。在 96 dpi 的屏幕上,96 像素将构成一英寸(物理上)。 (提供的屏幕 dpi 值不一定准确反映实际物理设备的值,但这并不重要。)因此,72 pt == 1 英寸应转换为 96。我希望找到接近 96 的值 在结果大小值(或它们的组合)中的某个位置,可能是高度值,但它们似乎都没有接近它...

问题

任何人都可以像我一样复制类似的价值观还是非常不典型? 如何计算边界高度/字体高度? (我预计字体高度为 96 px 和 72 pt) 或者:如果不是类似DTP的点,Java中字体大小参数的解释(单位)是什么? 是否只是 AWT 中的舍入或其他不准确错误导致 bounds.hmetrics.heightmetrics.ascent + metrics.descent 还是这种差异有价值的信息? 我是否遗漏了其他相关参数? (此处不涉及仿射变换。) 是否有一种安全的(跨平台)方法可以在 96 dpi 设备和 72 pt 字体大小上可靠地使字体高度为 96 px? (将字体大小乘以一个常数值是否可以接受?)

【问题讨论】:

发布MCVE。 另外,不要在你的问题中使用太多的粗体,它会让人难以阅读。在它们之间放置技术术语:` 这是很多问题,所以我假设您已经看过 this *** help article,它首先告诉您首先自己搜索答案,使用网络搜索等。您有没有找到任何解释 Java2D 如何处理字体度量的页面?似乎不太可能没有,特别是因为它已经存在了很长时间,而且 javadocs 可以非常详尽。 【参考方案1】:

问题已解决:这是我的错!

更准确地说:我实际上不是通过简单的new Font (Font.DIALOG, Font.PLAIN, 72)) 或类似方法初始化 Font 对象,而是使用new Font(Map<? extends AttributedCharacterIterator.Attribute,?> attributes) 构造函数。那个有一张带有各种字体属性的地图,从我的配置中预设。因为一个变量预设了一个错误的值,它实际上包含了一个map.put (SUPERSCRIPT, SUPERSCRIPT_SUPER);(在我的实际代码中并不像这里显示的那样清晰)。

因此,这些奇怪的边界实际上是字体一直被创建为较小的 superscript 变体的结果。没有它,边界/创建的文本大小会更接近我的预期:

Dialog,style=plain,size=72],高度:84,上升:67,下降:17,边界:java.awt.geom.Rectangle2D$Float[x=0.0,y=-66.83203,w=681.0 ,h=83.8125]

虽然它们的尺寸并不完全准确,但它们更接近。感谢 user1803551 的 MCVE 建议让我走上了正轨。为了完整起见,这里是(省略导入):

public final class TextRenderTest extends WindowAdapter 

int fontSource;
static private final String TESTTEXT = "This is a glyph test";
static public void  main (String [] args)  new TextRenderTest ().main (); 

void main () 
 Frame frame = new Frame ("Text render test") 
    @Override public void paint (Graphics g) 
     FontMetrics    fm;
     Font           font;
        g.setFont (fontSource++ % 2 == 0 ? createFont () : new Font (Font.DIALOG, Font.PLAIN, 72));
        g.drawString (TESTTEXT, 16, 96);
        fm = g.getFontMetrics ();
        System.out.println (
            "DPI: "      + Toolkit.getDefaultToolkit ().getScreenResolution () +
            ", font: "   + g.getFont ()    + ", font height: " + fm.getHeight () +
            ", ascent: " + fm.getAscent () + ", descent: "     + fm.getDescent () +
            ", bounds: " + fm.getStringBounds (TESTTEXT, g));  ;
    frame.setBounds (64, 32, 800, 280);
    frame.setVisible (true);
    frame.addWindowListener (this);

static private Font createFont () 
 HashMap <TextAttribute,Object> map = new HashMap<TextAttribute,Object> (9, 1.0f);
    map.put (FAMILY, "sans");
    map.put (WEIGHT, 500);
    map.put (WIDTH, 1);
    map.put (SIZE, 72);
    map.put (SUPERSCRIPT, SUPERSCRIPT_SUPER);   // <-- Something like this was the fault
    return new Font (map);

@Override public void windowClosing (WindowEvent event)  event.getWindow ().dispose (); 

此示例以触发器方式演示了这两种变体:预期边界和意外边界(使用上标)。重绘过程很脏,只看控制台输出。

所以我可以回答我所有的问题:

    不,其他人不太可能重现这些奇怪的结果 ;-) 我的预期基本上是正确的,尽管在细节上仍有细微的差异。 字体大小的解释(单位)似乎基本上是Point,确实。 这些微小的变化是我唯一无法解释的,尽管它们对我来说可以忽略不计。 错过了 SUPERSCRIPT 参数 可能不再需要进一步的补偿

【讨论】:

以上是关于给定字体pt大小和dpi屏幕分辨率的Java2d / AWT文本呈现奇怪的大小/边界?的主要内容,如果未能解决你的问题,请参考以下文章

字体大小

HTML中字体单位px pt em之间的转换

flutter 屏幕尺寸适配字体大小适配

字体大小对照换算表

iOS 字体规范和多屏适配

Android中常见的单位ppi,dp,dpi,sp,px