C# Textrenderer - 测量较小的字体大小会导致较大的尺寸

标签 c# winforms fonts textrenderer

我正在尝试使用 TextRenderer 类测量给定特定字体的字符串的大小。尽管我尝试用 3 种不同的方法(Graphics.MeasureCharacterRanges、Graphics.MeasureString、TextRenderer.MeasureText)测量它,但它们都给我不同的结果而不准确,但我还是偶然发现了其他东西。
使用字体大小 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);

结果是 s1width 为 41,height 为 13,而 s2 code>width 为 40,height 为 14。

为什么较小的字体会导致较大的宽度?

最佳答案

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

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

对于每种字体大小,它输出每个字符的宽度,包括字距调整。在 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)
Total width: 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)
Total width: 33

虽然我显然没有捕捉到 TextRenderer 进行测量的确切公式,但它确实说明了相同的宽度差异。在字体大小为 7 时,所有字符的宽度均为 7。然而,在字体大小为 8 时,字符宽度开始变化,有些变大,有些变小,最终加起来变小。

关于C# Textrenderer - 测量较小的字体大小会导致较大的尺寸,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36624106/

相关文章:

c# - Visual Studio 命名空间约定

c# - 如何防止用户在 ComboBox 中写入内容?

c++ - 二进制 '=' : no operator found which takes a right-hand operand of type 'std::unique_ptr<char [],std::default_delete<_Ty>>'

css - wordpress新主题自定义otf字体

Android:设置自定义字体时出现异常

c# - 如何对类(class)的其他人隐藏支持字段

c# - 如果这也是一个接口(interface),则提取接口(interface)会隐藏成员的细节

C# 代码样式 : Switching from "this." prefix to "underscore"

c# - 在 Windows 窗体应用程序中调整标签大小

javascript - 如何从javascript指定字体?