如何测量给定字体/大小中数字的像素宽度(C#)

10
我正在尝试计算Excel列的像素宽度,如这篇帖子所述,使用来自OpenXML规范的官方公式。然而,为了应用此公式,我需要知道正常字体的最大数字宽度,即最宽数字字符的像素宽度。 OpenXML规范给出了以下示例以进行说明:

以Calibri字体为例,11点字体大小的最大数字宽度为7个像素(在96 dpi下)。

我已经通过目测Calibri 11点数字并确实为7像素宽来检查了这一点。因此,我正在尝试创建一个方法,用于返回任何字体/大小的最大数字宽度。

我遵循这个问题中提出的建议,但它没有产生我期望的结果。

这是我的测试代码:

var font = new Font("Calibri", 11.0f, FontStyle.Regular);

for (var i = 0; i < 10; i++)
{
    Debug.WriteLine(TextRenderer.MeasureText(i.ToString(), font));
}

这里报告所有数字的宽度为15。

请问有什么建议吗?

谢谢, Tim

5个回答

5

首先,我要说的是,我即将展示的代码看起来像一种可怕的黑客攻击,并且我不会将其作为解决方案提供。 相反,我希望它能揭示更多关于MeasureText行为的信息,这可能会引导您找到最终解决方案。

我对您的代码进行了轻微的改动。 我正在测量两个下划线字符的长度。 然后,在for循环体中,我使用两侧带有下划线的所需字符进行测量,并减去两个下划线字符的长度。 对于测试场景,我得到了7的结果。 这样做的主要目的是确定MeasureText是否按字符或字符串开头/结束处的填充进行填充。 看起来它是后者。 也许这个结论以及您收到的其他输入,会指引您找到更优雅的解决方案。

var font = new Font("Calibri", 11.0f, FontStyle.Regular);
int underscoreWidth = TextRenderer.MeasureText("__", font).Width; 
for (var i = 0; i < 10; i++)
   {
      int charWidth = TextRenderer.MeasureText(String.Concat("_", i.ToString(), "_"), font).Width - underscoreWidth;
      Debug.WriteLine(charWidth);
   }

感谢您提供的解决方案。正如您所说,这可能有点不寻常,但它确实产生了正确的结果,这比我在过去四个小时中取得的进展要多得多 :). 对于我的应用程序来说,一个次要的好处是它无需 Graphics 对象即可实现。再次感谢。 - Tim Coulter

5

MeasureText会添加填充,填充的数量相当不可预测,尽管它大致与字体大小成线性比例。诀窍是通过减去两个测量值来消除填充。以下代码生成了非常满意的结果:

        float size = 5;
        for (int ix = 0; ix < 10; ++ix) {
            var font = new Font("Calibri", size, FontStyle.Regular);
            string txt = new string('0', 100);
            SizeF sz1 = TextRenderer.MeasureText("00", font) - TextRenderer.MeasureText("0", font);
            Console.WriteLine("{0} {1:N3}", size, sz1.Width);
            size += 2;
        }

输出:

5 4.000
7 5.000
9 6.000
11 7.000
13 9.000
15 10.000
17 12.000
19 13.000
21 14.000
23 16.000

这样可以得到您所需要的7个像素。请注意,您需要适应TrueType提示,它可以调整数字的形状,使其主要干线在像素边界上。至少添加一个像素。


5

注意MeasureString方法会添加额外的空间: "MeasureString方法是为单个字符串设计的,并在字符串前后包含少量额外的空间,以允许悬挂字形。" 此外,请注意,在上下文之外测量单个字形可能不会给出准确的结果。 字符串字符宽度的总和并不总是字符串的宽度,这是由于字距调整造成的。 - plinth
@plinth:是的,我使用MeasureString测试了我的代码,它报告说7像素的数字宽度为9.408365像素。 - Tim Coulter

3

测量文本宽度可能会让人崩溃...我不知道为什么他们把它搞得这么混乱...

以下是我发现的最准确的测量文本长度的方法:

    protected int _MeasureDisplayStringWidth ( Graphics graphics, string text, Font font )
    {
        if ( text == "" )
            return 0;

        StringFormat format = new StringFormat ( StringFormat.GenericDefault );
        RectangleF rect = new RectangleF ( 0, 0, 1000, 1000 );
        CharacterRange[] ranges = { new CharacterRange ( 0, text.Length ) };
        Region[] regions = new Region[1];

        format.SetMeasurableCharacterRanges ( ranges );
        format.FormatFlags = StringFormatFlags.MeasureTrailingSpaces;

        regions = graphics.MeasureCharacterRanges ( text, font, rect, format );
        rect = regions[0].GetBounds ( graphics );

        return (int)( rect.Right );
    }

@Fungus:谢谢,你的代码最接近正确答案,将7像素数字报告为8像素宽。不幸的是,1像素的误差会在我的应用程序中放大到更大的误差。此外,似乎Font构造函数的参数会影响结果。有没有一种正确的方法来构造一个表示11pt Calibri的Font对象? - Tim Coulter
嗯...这有可能会返回比实际宽度多1像素,因为它测量的是字符占用的空间,这将考虑到字符周围的边框。 - Mongus Pong

1

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