Proper Measurement of Characters in Pixels

2019-04-16 14:38发布

问题:

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.

回答1:

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:

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):

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

  • See my answer over here, with more information.


回答2:

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