1

I'm writing a text box from scratch in order to optimize it for syntax highlighting. However, I need to get the width of characters in pixels, exactly, not the garbage Graphics::MeasureString gives me. I've found a lot of stuff on the web, specifially this, however, none of it seems to work, or does not account for tabs. I need the fastest way to measure the exact dimensions of a character in pixels, and tab spaces. I can't seem to figure this one out...

Should mention I'm using C++, CLR/CLI, and GDI+


Here is my measuring function. In another function the RectangleF it returns is drawn to the screen:

RectangleF TextEditor::MeasureStringWidth(String^ ch, Graphics^ g, int xDistance, int lineYval)
{
    RectangleF measured;
    Font^ currentFont = gcnew Font(m_font, (float)m_fontSize);
    StringFormat^ stringFormat = gcnew StringFormat;
    RectangleF layout = RectangleF(xDistance,lineYval,35,m_fontHeightPix);
    array<CharacterRange>^ charRanges = {CharacterRange(0,1)};
    array<Region^>^ strRegions;

    stringFormat->FormatFlags = StringFormatFlags::DirectionVertical;
    stringFormat->SetMeasurableCharacterRanges(charRanges);

    strRegions = g->MeasureCharacterRanges(ch, currentFont, layout, stringFormat);

    if(strRegions->Length >= 1)
        measured = strRegions[0]->GetBounds(g);
    else
        measured = RectangleF(0,0,0,0);

    return measured;
}

I don't really understand what MeasureCharacterRanges layoutRect parameter does. I modified the code from Microsofts example to only work with, or only measure, one character.

smoth190
  • 438
  • 7
  • 26
  • 3
    Measuring just one character is wrong, that's why you get bad results. Because of kerning, the extent of a string is not even close to the sum of extents of individual characters. If you want the coordinates of each character, pass the entire string, and set up a `CharacterRange` for each character. – Ben Voigt Feb 21 '12 at 19:35

2 Answers2

5

You should not be using Graphics for any text rendering.

Starting with .NET Framework 2.0 use of Graphics.MeasureString and Graphics.DrawString was deprecated in favor of a newly added helper class TextRenderer:

  • TextRenderer.MeasureText
  • TextRenderer.DrawText

The GDI+ text renderer has been abandoned, and hasn't gotten any improvements or fixes for over 10 years; as well as being software rendered.

GDI rendering (which TextRenderer is a simple wrapper of) is hardware accelerated, and continues to get rendering improvements (ligatures, Uniscribe, etc).

Note: GDI+ text rendering is wrapped by Graphics.DrawString and MeasureString

Here's a comparison of the measure results of Graphics and TextRenderer:

enter image description here

The GDI+ measurements aren't "wrong", they are doing exactly what they intend - return the size that the text would be if it were rendered as the original font author intended (which you can achieve using Anti-alias rendering):

enter image description here

But nobody really wants to look at text the way the font designer intended, because that causes stuff to not line-up on pixel boundaries - making the text too fuzzy (i.e. as you see on a Mac). Ideally the text should be snapped to line up on actual pixel boundaries (i.e. Windows' Grid Fitting)

Bonus Reading

Community
  • 1
  • 1
Ian Boyd
  • 246,734
  • 253
  • 869
  • 1,219
  • Good answer; very much agreed about preferring GDI to GDI+. That's something Win32 developers have known for a long time, the WinForms team was just a bit slow to come around. `TextRenderer` has been around now for enough versions to assume it's ubiquitously available. But I think someone stole the second half of your last sentence! – Cody Gray - on strike Feb 22 '12 at 02:58
  • Wow, I had never even heard of TextRenderer! Thank you, I should probably find my answer with this. – smoth190 Feb 22 '12 at 05:07
  • @CodyGray That was me inserting my final bonus chatter link before some half-completed thought; that should have been removed. It has been removed :) – Ian Boyd Feb 22 '12 at 18:35
2

There's MeasureCharacterRanges that is like MeasureString, but more powerful and more accurate (and also slower).

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • I rewrote my measuring function with this, however, it seems to ignore whitespaces and all the measurements come out inaccurate. I'll post my code. – smoth190 Feb 21 '12 at 18:50