Before I pass a string to Graphics.DrawString(), I'd like to know exactly where each character will be - not just it's offset and width, but its exact bound box with ascent/descent figured in so that I can do collision detection at the character level rather than using the entire line-height that Graphics.MeasureString() is giving me.
I can't seem to find any examples for this that accurately return the bounding box for each character. How can this be done in c#/GDI+ ?
Ideally, I'd like to get each of the gray Rectangles as in this image:
I don't think it's possible to accurately measure characters within a string in GDI+. Even if you forget subpixel positioning and kerning, etc, which mean that characters in the drawn string will be different sizes depending on their context in the string and position on screen and ClearType settings, MeasureString has a habit of adding padding and fudge factors so you can't easily get the desired info out of it.
One approach that sortof works is to Measure the first character, then the first two characters, then the first three characters, and so on, so that you get a rough idea where they will all end up when drawn in a single hit. Then you'll probably need to Measure each char again to determine its height. If MeasureString doesn't give the char height but gives the font height instead, you might have to resort to rendering each character to an offscreen bitmap and then scanning the pixels to determine the true height of the character.
At that point you may find it's better to p/invoke off to a lower level font API that just gives you the information you need directly from the font. But then you'll probably need to use something other than GDO+ to render it as the chances of it lining up with what you've worked out may not be good.
(can you tell that I have a trust issue with the quality off the GDI+ font renderer yet?)
EDIT: This is the solution I used, building on @Sayka's example and a trick to get actual char width:
If you're drawing a string at X, Y using Font in Graphics g1, you can draw each char using these Rectangles:
public IEnumerable<Rectangle> GetRectangles(Graphics g1)
{
int left = X;
foreach (char ch in word)
{
//actual width is the (width of XX) - (width of X) to ignore padding
var size = g1.MeasureString("" + ch, Font);
var size2 = g1.MeasureString("" + ch + ch, Font);
using (Bitmap b = new Bitmap((int)size.Width + 2, (int)size.Height + 2))
using (Graphics g = Graphics.FromImage(b))
{
g.FillRectangle(Brushes.White, 0, 0, size.Width, size.Height);
g.TextRenderingHint = g1.TextRenderingHint;
g.DrawString("" + ch, Font, Brushes.Black, 0, 0);
int top = -1;
int bottom = -1;
//find the top row
for (int y = 0; top < 0 && y < (int)size.Height - 1; y++)
{
for (int x = 0; x < (int)size.Width; x++)
{
if (b.GetPixel(x, y).B < 2)
{
top = y;
}
}
}
//find the bottom row
for (int y = (int)(size.Height - 1); bottom < 0 && y > 1; y--)
{
for (int x = 0; x < (int)size.Width - 1; x++)
{
if (b.GetPixel(x, y).B < 2)
{
bottom = y;
}
}
}
yield return new Rectangle(left, Y + top, (int)(size2.Width - size.Width), bottom - top);
}
left += (int)(size2.Width - size.Width);
}
}
Looks like this:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With