7
votes

First of all, question How to measure width of character precisely? which is answered, doesn't really help for this case, so this isn't a duplicate of that.

I have a string. I draw using graphics.DrawString, however when I need to put another one after it, I need to know the precise width of previous string.

For this I use graphics.MeasureString with:

StringFormat format = new StringFormat(StringFormat.GenericTypographic);
format.Alignment = StringAlignment.Center;
format.Trimming = StringTrimming.None;
format.FormatFlags = StringFormatFlags.MeasureTrailingSpaces;

I have tried many other functions, just as TextRendered.MeasureText however all of them fail, with all possible combinations of parameters.

the mentioned combination of MeasureString is most close to what I need (it works in most cases, except for special characters), however using characters like # break it. The width is either shorter or longer.

Is there a way to get a precise size of text produced by DrawString function? How does the DrawString calculate the size of drawing area? It must be clearly some other function because the size always differ.

The source code of whole application is here https://gitorious.org/pidgeon/pidgeon-main/ (File where I work with this, is https://gitorious.org/pidgeon/pidgeon-main/blobs/master/scrollback/SBABox.cs)

4
if question clearly didn't say MeasureString doesn't work, it would be duplicatePetr
Without a better description of how and when "it doesn't work", and code that includes MeasureString and DrawString together, this just isn't a real question. Come up with a small testcase.Henk Holterman

4 Answers

9
votes

You just need to eliminate extra width. You can do this by using string format:

GdipStringFormatGetGenericTypographic()

You could also use:

float doubleWidth = g.MeasureString(text+text,...).Width;
float singleWidth = g.MeasureString(text).Width;
float textWidth = doubleWidth-singleWidth;

This will allow you to work with other languages such as Japanese.

On codeproject, Pierre Anaud's solution was to use MeasureCharacterRanges, which returns a region matching exactly the bounding box of the specified string:

static public int MeasureDisplayStringWidth(Graphics graphics, string text, Font font)
{
    System.Drawing.StringFormat format  = new System.Drawing.StringFormat ();
    System.Drawing.RectangleF   rect    = new System.Drawing.RectangleF(0, 0, 1000, 1000);
    var ranges  = new System.Drawing.CharacterRange(0, text.Length);
    System.Drawing.Region[] regions = new System.Drawing.Region[1];

    format.SetMeasurableCharacterRanges (ranges);

    regions = graphics.MeasureCharacterRanges (text, font, rect, format);
    rect    = regions[0].GetBounds (graphics);

    return (int)(rect.Right + 1.0f);
}
6
votes

I'm a little late to the party here, but I was trying to do something similar and stumbled on this question. You've probably already seen the following remark from the documentation for the Graphics.MeasureString method on MSDN:

The MeasureString method is designed for use with individual strings and includes a small amount of extra space before and after the string to allow for overhanging glyphs. Also, the DrawString method adjusts glyph points to optimize display quality and might display a string narrower than reported by MeasureString. To obtain metrics suitable for adjacent strings in layout (for example, when implementing formatted text), use the MeasureCharacterRanges method or one of the MeasureString methods that takes a StringFormat, and pass GenericTypographic. Also, ensure the TextRenderingHint for the Graphics is AntiAlias.

It seems that you were trying to follow this advice because you're using StringFormat.GenericTypographic as a starting point for your custom StringFormat object. However, the line

format.FormatFlags = StringFormatFlags.MeasureTrailingSpaces;

effectively negates the fact that you started with StringFormat.GenericTypographic because it clears any previously set flags. What you probably meant to do is set the StringFormatFlags.MeasureTrailingSpaces flag while preserving the other flags, like so:

format.FormatFlags |= StringFormatFlags.MeasureTrailingSpaces;
1
votes

Try to use this methods:

GDI+ (graphics.MeasureString and graphics.DrawString) >> System.Drawing.Graphics
GDI (TextRenderer.MeasureText and TextRenderer.DrawText)

It also may help you:

Write a custom measure method:

  • Split entry string on special characters
  • Use above .net methods
  • Calculate width of special characters and sum ...

Read Ian Boyd answer

0
votes

A method using Graphics.MeasureCharacterRanges to return all the rectangles enclosing each individual letter in a string and their positions is given here: Measure character positions when drawing long strings in C#

I have used the MeasureCharactersInWord and MeasureCharacters methods from that post, then in order to find the exact width without the spaces added to each side of the string, I use this code:

var w = 0F;
var rects = MeasureCharacters(Graphics.FromHwnd(IntPtr.Zero), font, text);
if (rects.Count>0)
{
    if (rects.Count == 1)
    {
        w = rects.First().Width;
    }
    else
    {
        var r0 = rects.First();
        var rN = rects.Last();
        w = rN.X - r0.X + rN.Width;
    }
}

Note that the height of the rectangle is the height of the font and not of the character itself. If you need the height check this post: Determining exact glyph height in specified font

A final note: the reason why I used MeasureCharacterRanges is because all the other methods I tried were failing at giving me a bounding box without space to the left and right of the text. This post The wonders of text rendering and GDI gives a method to get the string width and remove this space using TextRenderer so the whole thing can be done in about two lines of code. I haven't checked the result though.