How to Call a SQL Stored Procedure Using Entityframework 7 and ASP.NET 5

Accuracy of TextRenderer.MeasureText results

Is there any way to improve the accuracy of the MeasureText method? Should I be calling one of the overrides that accepts a device context and/or format flags?

You have answered your question by yourself. Actually MeasureText based on Win32 DrawTextEx, and this function cannot work without valid device context. So when you call MeasureText override without hdc, it internally create desktop compatible hdc to do measurement.

Of course measurement depends on additional TextFormatFlags. Also keep in mind that Label painting (and measurement) depends on UseCompatibleTextRendering.

So general conclusion you should use MeasureText for your own code, for example when you then call DrawText with exactly same parameters, in all other cases size returned by MeasureText cannot be treated as precise.

If you need to get expected Label size, you should use GetPreferredSize method.

C# Textrenderer - Measuring smaller fontsize results in larger size

To address specifically why it would be possible for a larger font to produce a smaller width, I put together this sample console app. It's worth noting that I adjust the 7 & 8 font sizes to 7.5 & 8.25, respectively, as this is what size TextRenderer evaluates them as internally.

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

For each font size, it outputs the width of each character, including kerning. At 96 DPI, for me, this results in:

[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

Though I've obviously not captured the exact formula for measurements made by TextRenderer, it does illustrate the same width-discrepancy. At font size 7, all characters are 7 in width. However, at font size 8, the character widths begin to vary, some larger, some smaller, ultimately adding up to a smaller width.

Why doesn't TextRenderer.MeasureText work properly?

Please, use the TextFormatFlags measure parameter as shown below:

Size size = TextRenderer.MeasureText(text, font, canvas, TextFormatFlags.WordBreak);

TextRenderer.MeasureText and Graphics.MeasureString mismatch in size

TextRenderer uses GDI to render the text, whereas Graphics uses GDI+. The two use a slightly different method for laying out text so the sizes are different.

Which one you should use depends on what will eventually be used to actually draw the text. If you are drawing it with GDI+ Graphics.DrawString, measure using Graphics.MeasureString. If you are drawing using GDI TextRenderer.DrawText, measure using TextRenderer.MeasureText.

If the text will be displayed inside a Windows Forms control, it uses TextRenderer if UseCompatibleTextRendering is set to false (which is the default).

Reading between the lines of your question, you seem to be using TextRenderer because you don't have a Graphics instance outside the Paint event. If that's the case, you can create one yourself to do the measuring:

using( Graphics g = someControl.CreateGraphics() )
{
SizeF size = g.MeasureString("some text", SystemFonts.DefaultFont);
}

If you don't have access to a control to create the graphics instance you can use this to create one for the screen, which works fine for measurement purposes.

using( Graphics g = Graphics.FromHwnd(IntPtr.Zero) )
{
SizeF size = g.MeasureString("some text", SystemFonts.DefaultFont);
}

TextRenderer.MeasureText' giving distinct size than 'Graphics.MeasureString'

TextRenderer.MeasureText returns a Size so the values are integer. Graphics.MeasureString returns a SizeF containing floats.

Both are "raw values" though. When testing to see if some text will fit somewhere, you may also need to take into account Control.Padding which each Type may implement differently.

275 vs 300 seems like a large variance though. I have a checkbox thing that returns 82, 13 vs `82.85806, 13.8251925).

problem with TextRenderer.MeasureText

MeasureText is not known to be accurate.

Heres a better way :

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


Related Topics



Leave a reply



Submit