C#Textrenderer-测量较小的字体大小会产生较大的大小

C#Textrenderer-测量较小的字体大小会产生较大的大小,c#,winforms,fonts,textrenderer,C#,Winforms,Fonts,Textrenderer,我试图使用TextRenderer类测量给定字体的字符串大小。尽管我尝试用3种不同的方法(Graphics.MeasureCharacterRanges、Graphics.MeasureString、textrender.MeasureText)测量它,但它们都给了我不同的结果,但并不精确,我还是偶然发现了其他一些东西。 使用fontsize 7和8测量相同字体的同一字符串START,fontsize 7测量值比fontsize 8测量值宽 以下是我使用的代码: Font f1 = new Fo

我试图使用TextRenderer类测量给定字体的字符串大小。尽管我尝试用3种不同的方法(Graphics.MeasureCharacterRanges、Graphics.MeasureString、textrender.MeasureText)测量它,但它们都给了我不同的结果,但并不精确,我还是偶然发现了其他一些东西。
使用fontsize 7和8测量相同字体的同一字符串
START
,fontsize 7测量值比fontsize 8测量值宽

以下是我使用的代码:

Font f1 = new Font("Arial", 7, FontStyle.Regular);
Font f2 = new Font("Arial", 8, FontStyle.Regular);
Size s1 = TextRenderer.MeasureText("START", f1);
Size s2 = TextRenderer.MeasureText("START", f2);
结果是
s1
宽度为41,高度为13,而
s2
宽度为40,高度为14


为什么字体越小,宽度越大

看起来像是
TextRenderer.MeasureText
给出了正确的值,但是较小的字体对于某些字形具有较大的字母间距

下面您可以看到它如何查找
“ttttttt”
文本。上一个是Arial 7,下一个是Arial 8

对于较大字体,字母之间没有空格

为了明确说明为什么较大的字体可以产生较小的宽度,我制作了这个示例控制台应用程序。值得注意的是,我将7和8字体大小分别调整为7.5和8.25,因为这是大小
textrender
对它们进行内部评估的大小

using System;
using System.Drawing;
using System.Linq;
using System.Runtime.InteropServices;

namespace FontSizeDifference
{
    static class Program
    {
        [StructLayout(LayoutKind.Sequential)]
        struct ABCFLOAT
        {
            public float abcfA;
            public float abcfB;
            public float abcfC;
        }

        [DllImport("gdi32.dll")]
        static extern bool GetCharABCWidthsFloat(IntPtr hdc, int iFirstChar, int iLastChar, [Out] ABCFLOAT[] lpABCF);

        [DllImport("gdi32.dll", CharSet = CharSet.Auto, EntryPoint = "SelectObject", SetLastError = true)]
        static extern IntPtr SelectObject(IntPtr hdc, IntPtr obj);

        [DllImport("gdi32.dll", EntryPoint = "DeleteObject")]
        static extern bool DeleteObject([In] IntPtr hObject);

        [StructLayout(LayoutKind.Sequential)]
        struct KERNINGPAIR
        {
            public ushort wFirst;
            public ushort wSecond;
            public int iKernAmount;
        }

        [DllImport("gdi32.dll")]
        static extern int GetKerningPairs(IntPtr hdc, int nNumPairs, [Out] KERNINGPAIR[] lpkrnpair);

        [STAThread]
        static void Main()
        {
            var fonts = new[] {
                new Font("Arial", 7.5f, FontStyle.Regular),
                new Font("Arial", 8.25f, FontStyle.Regular)
            };
            string textToMeasure = "START";

            using (Graphics g = Graphics.FromHwnd(IntPtr.Zero))
            {
                IntPtr hDC = g.GetHdc();

                foreach (Font font in fonts)
                {
                    float totalWidth = 0F;
                    IntPtr hFont = font.ToHfont();

                    // Apply the font to dc
                    SelectObject(hDC, hFont);

                    int pairCount = GetKerningPairs(hDC, short.MaxValue, null);
                    var lpkrnpair = new KERNINGPAIR[pairCount];
                    GetKerningPairs(hDC, pairCount, lpkrnpair);

                    Console.WriteLine("\r\n" + font.ToString());

                    for (int ubound = textToMeasure.Length - 1, i = 0; i <= ubound; ++i)
                    {
                        char c = textToMeasure[i];
                        ABCFLOAT characterWidths = GetCharacterWidths(hDC, c);
                        float charWidth = (characterWidths.abcfA + characterWidths.abcfB + characterWidths.abcfC);
                        totalWidth += charWidth;

                        int kerning = 0;
                        if (i < ubound)
                        {
                            kerning = GetKerningBetweenCharacters(lpkrnpair, c, textToMeasure[i + 1]).iKernAmount;
                            totalWidth += kerning;
                        }

                        Console.WriteLine(c + ": " + (charWidth + kerning) + " (" + charWidth + " + " + kerning + ")");
                    }

                    Console.WriteLine("Total width: " + totalWidth);

                    DeleteObject(hFont);
                }

                g.ReleaseHdc(hDC);
            }
        }

        static KERNINGPAIR GetKerningBetweenCharacters(KERNINGPAIR[] lpkrnpair, char first, char second)
        {
            return lpkrnpair.Where(x => (x.wFirst == first) && (x.wSecond == second)).FirstOrDefault();
        }

        static ABCFLOAT GetCharacterWidths(IntPtr hDC, char character)
        {
            ABCFLOAT[] values = new ABCFLOAT[1];
            GetCharABCWidthsFloat(hDC, character, character, values);
            return values[0];
        }
    }
}
使用系统;
使用系统图;
使用System.Linq;
使用System.Runtime.InteropServices;
名称空间大小差异
{
静态类程序
{
[StructLayout(LayoutKind.Sequential)]
结构ABCFLOAT
{
公开发行abcfA;
公共浮动abcfB;
公开发行abcfC;
}
[DllImport(“gdi32.dll”)]
静态外部bool getcharabchwidthsfloat(IntPtr hdc、intifirstchar、intilastchar、[Out]ABCFLOAT[]lpABCF);
[DllImport(“gdi32.dll”,CharSet=CharSet.Auto,EntryPoint=“SelectObject”,SetLastError=true)]
静态外部IntPtr SelectObject(IntPtr hdc、IntPtr obj);
[DllImport(“gdi32.dll”,EntryPoint=“DeleteObject”)]
静态外部bool DeleteObject([In]IntPtr hObject);
[StructLayout(LayoutKind.Sequential)]
结构核对
{
公共卫生服务;
公共卫生服务;
国际iKernAmount公共酒店;
}
[DllImport(“gdi32.dll”)]
静态外部int GetKerningPairs(IntPtr hdc,int nNumPairs,[Out]KERNINGPAIR[]lpkrnpair);
[状态线程]
静态void Main()
{
var fonts=new[]{
新字体(“Arial”,7.5f,FontStyle.Regular),
新字体(“Arial”,8.25f,FontStyle.Regular)
};
字符串textToMeasure=“开始”;
使用(Graphics g=Graphics.FromHwnd(IntPtr.Zero))
{
IntPtr hDC=g.GetHdc();
foreach(字体中的字体)
{
浮动总宽度=0F;
IntPtr hFont=font.ToHfont();
//将字体应用于dc
选择对象(hDC、hFont);
int pairCount=GetKerningPairs(hDC,short.MaxValue,null);
var lpkrnpair=新的内核对[pairCount];
GetKerningPairs(hDC、pairCount、lpkrnpair);
Console.WriteLine(“\r\n”+font.ToString());
对于(int-ubound=textToMeasure.Length-1,i=0;i(x.wFirst==first)和&(x.wssecond==second)).FirstOrDefault();
}
静态ABCFLOAT GetCharacterWidths(IntPtr hDC,char character)
{
ABCFLOAT[]值=新的ABCFLOAT[1];
GetCharABCWidthsFloat(hDC、字符、字符、值);
返回值[0];
}
}
}
对于每个字体大小,它输出每个字符的宽度,包括字距。对于我来说,在96 DPI时,这将导致:

[Font:Name=Arial,Size=7.5,Units=3,GdiCharSet=1,GdiVerticalFont=False]
S:7(7+0)
T:6(7+-1)
A:7(7+0)
R:7(7+0)
T:7(7+0)
总宽度:34

[Font:Name=Arial,Size=8.25,Units=3,GdiCharSet=1,GdiVerticalFont=False]
S:7(7+0)
T:5(6+-1)
A:8(8+0)
R:7(7+0)
T:6(6+0)
总宽:33


虽然我显然没有捕捉到
textrender
所做测量的精确公式,但它确实说明了相同的宽度差异。在字体大小为7时,所有字符的宽度都是7。然而,在字体大小为8时,字符宽度开始变化,一些更大,一些更小,最终加起来就是更小的宽度。

Hmeproduce。这里我得到44,15和53,16。请改用SizeF!我的显示器有120dpi。请注意,除了Graphics.MeasureString with StringFormat.TypographicGenic之外,其他所有显示器都添加了一些空格,以允许您将输出串在一起,而不会重叠单词。@TaW我的显示器是在默认的96dpi上运行的。TextRenderer不提供任何有助于d测量一个字符串,得到一个SizeF。我不介意由于额外的填充而使大小变大,我只是想知道更小的字体大小如何导致更大的宽度。这是一个96 dpi的测量值。正确的是,在96 dpi时,8点文本实际上比7点文本宽。你必须习惯测量文本的神秘和不精确性,Tru字型暗示、像素网格拟合和字体特征,如字形悬垂、连字和字距,使其成为一种不精确的艺术。