C# Textrenderer - Измерение меньшего размера шрифта приводит к большему размеру
Я пытаюсь измерить размер строки с заданным шрифтом с помощью класса TextRenderer. Несмотря на то, что я попытался измерить его тремя различными подходами (Graphics.MeasureCharacterRanges, Graphics.MeasureString, TextRenderer.MeasureText), и все они дают мне разные результаты, не будучи точными, я наткнулся на кое-что еще.
Измерение одной и той же строки START
с тем же шрифтом, использующим размер шрифта 7 и 8, измерение размера шрифта 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
Почему меньший шрифт приводит к большей ширине?
2 ответа
Чтобы конкретно указать, почему шрифт большего размера может иметь меньшую ширину, я собрал этот пример консольного приложения. Стоит отметить, что я настраиваю размеры шрифтов 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 для меня это приводит к:
[Шрифт: Имя =Arial, Размер = 7,5, Единицы = 3, GdiCharSet = 1, GdiVerticalFont = False]
S: 7 (7 + 0)
Т: 6 (7 + -1)
A: 7 (7 + 0)
R: 7 (7 + 0)
Т: 7 (7 + 0)
Общая ширина: 34[Шрифт: Имя =Arial, Размер =8,25, Единицы = 3, GdiCharSet = 1, GdiVerticalFont = False]
S: 7 (7 + 0)
Т: 5 (6 + -1)
A: 8 (8 + 0)
R: 7 (7 + 0)
Т: 6 (6 + 0)
Общая ширина: 33
Хотя я явно не уловил точную формулу для измерений, сделанных TextRenderer
это иллюстрирует то же несоответствие ширины. При размере шрифта 7 все символы имеют ширину 7. Тем не менее, при размере шрифта 8 ширина символов начинает меняться, некоторые больше, другие меньше, что в итоге приводит к уменьшению ширины.
Это похоже на TextRenderer.MeasureText
дает правильное значение, однако меньший шрифт имеет больший межбуквенный интервал для некоторых глифов.
Ниже вы можете увидеть, как это выглядит "TTTTTTTTT"
текст. Верхний - Arial 7, нижний - Arial 8.
Для более крупного шрифта между буквами нет места.