C# Textrenderer - 测量较小的字体大小会导致较大的尺寸
C# Textrenderer - Measuring smaller fontsize results in larger size
我正在尝试使用 TextRenderer class 测量给定特定字体的字符串的大小。尽管我尝试用 3 种不同的方法(Graphics.MeasureCharacterRanges、Graphics.MeasureString、TextRenderer.MeasureText)测量它,但它们都给我不同的结果而不准确,但我还是偶然发现了其他东西。
使用字体大小 7 和 8 测量具有相同字体的相同字符串 START
,字体大小 7 的测量结果比字体大小 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
的 width
为 41,height
为 13,而 s2
的 width
为 40,height
共 14 个。
为什么字体越小宽度越大?
似乎 TextRenderer.MeasureText
给出了正确的值,但是较小的字体对于某些字形具有较大的字母间距。
您可以在下面看到它如何查找 "TTTTTTTTT"
文本。上一个是Arial 7,下一个是Arial 8。
对于更大的字体,字母之间没有 space。
为了具体解决为什么较大的字体可能会产生较小的宽度,我整理了这个示例控制台应用程序。值得注意的是,我将 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 时,字符宽度开始变化,有的变大,有的变小,最终加起来变小了。
我正在尝试使用 TextRenderer class 测量给定特定字体的字符串的大小。尽管我尝试用 3 种不同的方法(Graphics.MeasureCharacterRanges、Graphics.MeasureString、TextRenderer.MeasureText)测量它,但它们都给我不同的结果而不准确,但我还是偶然发现了其他东西。
使用字体大小 7 和 8 测量具有相同字体的相同字符串 START
,字体大小 7 的测量结果比字体大小 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
的 width
为 41,height
为 13,而 s2
的 width
为 40,height
共 14 个。
为什么字体越小宽度越大?
似乎 TextRenderer.MeasureText
给出了正确的值,但是较小的字体对于某些字形具有较大的字母间距。
您可以在下面看到它如何查找 "TTTTTTTTT"
文本。上一个是Arial 7,下一个是Arial 8。
对于更大的字体,字母之间没有 space。
为了具体解决为什么较大的字体可能会产生较小的宽度,我整理了这个示例控制台应用程序。值得注意的是,我将 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 时,字符宽度开始变化,有的变大,有的变小,最终加起来变小了。