Introduction
Graphics.MeasureString
can be used to compute the height and width of a text string. Often, however, the dimensions returned do not match the size of what gets drawn on screen when calling Graphics.DrawString
. The red box above shows the dimensions returned by Graphics.MeasureString
, which is about an em too wide...
The differences between what is computed and what is really drawn on the screen are related to how GDI+ computes its widths when using hinting and antialiasing. Here are the gory details. A known work-around is to make GDI+ display its string antialiased, in which case the measured width matches the displayed result. If you want to draw standard strings (to match the GUI appearance, for instance), you are left out.
First, naive solution
The code I present here can be inserted into any class which needs to compute the real width of a string (shown by the yellow background above). The trick I use to compute the real string width is to ask GDI+ to draw the string into a bitmap and then find the position of the last character by reading back the pixels. A few optimisations ensure that this gets done as fast as possible (small bitmap, few pixels).
static public int MeasureDisplayStringWidth(Graphics graphics, string text,
Font font)
{
const int width = 32;
System.Drawing.Bitmap bitmap = new System.Drawing.Bitmap (width, 1,
graphics);
System.Drawing.SizeF size = graphics.MeasureString (text, font);
System.Drawing.Graphics anagra = System.Drawing.Graphics.FromImage(bitmap);
int measured_width = (int) size.Width;
if (anagra != null)
{
anagra.Clear (Color.White);
anagra.DrawString (text+"|", font, Brushes.Black,
width - measured_width, -font.Height / 2);
for (int i = width-1; i >= 0; i--)
{
measured_width--;
if (bitmap.GetPixel (i, 0).R != 255)
break;
}
}
return measured_width;
}
That's all, folks. Right-to-left scripts won't probably work with this piece of code.
Another solution...
It is also possible to get the accurate string geometry by using MeasureCharacterRanges
, which returns a region matching exactly the bounding box of the specified string. This is faster and more elegant than the first solution I posted on CodeProject.
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);
System.Drawing.CharacterRange[] 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);
}
Post Script
Both functions only work with non-empty strings. The second solution will strip the trailing spaces; the first solution will take them in account. Choose the one which best fits your needs