MeasureString and DrawString difference
why do i have to increase MeasureString()
result width by 21%
size.Width = size.Width * 1.21f;
to evade Word Wrap in DrawString()
?
I need a solution to get the exact result.
Same font, same stringformat, same text used in both functions.
From answer by OP:
SizeF size = graphics.MeasureString(element.Currency, Currencyfont, new PointF(0, 0), strFormatLeft);
size.Width = size.Width * 1.21f;
int freespace = rect.Width - (int)size.Width;
if (freespace < ImageSize) { if (freespace > 0) ImageSize = freespace; el开发者_StackOverflowse ImageSize = 0; }
int FlagY = y + (CurrencySize - ImageSize) / 2;
int FlagX = (freespace - ImageSize) / 2;
graphics.DrawImage(GetResourseImage(@"Flags." + element.Flag.ToUpper() + ".png"),
new Rectangle(FlagX, FlagY, ImageSize, ImageSize));
graphics.DrawString(element.Currency, Currencyfont, Brushes.Black,
new Rectangle(FlagX + ImageSize, rect.Y, (int)(size.Width), CurrencySize), strFormatLeft);
My code.
MeasureString() method had some issues, especially when drawing non-ASCII characters. Please try TextRenderer.MeasureText() instead.
Graphics.MeasureString, TextRenderer.MeasureText and Graphics.MeasureCharacterRanges all return a size that includes blank pixels around the glyph to accomodate ascenders and descenders.
In other words, they return the height of "a" as the same as the height of "d" (ascender) or "y" (descender). If you need the true size of the glyph, the only way is to draw the string and count the pixels:
Public Shared Function MeasureStringSize(ByVal graphics As Graphics, ByVal text As String, ByVal font As Font) As SizeF
' Get initial estimate with MeasureText
Dim flags As TextFormatFlags = TextFormatFlags.Left + TextFormatFlags.NoClipping
Dim proposedSize As Size = New Size(Integer.MaxValue, Integer.MaxValue)
Dim size As Size = TextRenderer.MeasureText(graphics, text, font, proposedSize, flags)
' Create a bitmap
Dim image As New Bitmap(size.Width, size.Height)
image.SetResolution(graphics.DpiX, graphics.DpiY)
Dim strFormat As New StringFormat
strFormat.Alignment = StringAlignment.Near
strFormat.LineAlignment = StringAlignment.Near
' Draw the actual text
Dim g As Graphics = graphics.FromImage(image)
g.SmoothingMode = SmoothingMode.HighQuality
g.TextRenderingHint = Drawing.Text.TextRenderingHint.AntiAliasGridFit
g.Clear(Color.White)
g.DrawString(text, font, Brushes.Black, New PointF(0, 0), strFormat)
' Find the true boundaries of the glyph
Dim xs As Integer = 0
Dim xf As Integer = size.Width - 1
Dim ys As Integer = 0
Dim yf As Integer = size.Height - 1
' Find left margin
Do While xs < xf
For y As Integer = ys To yf
If image.GetPixel(xs, y).ToArgb <> Color.White.ToArgb Then
Exit Do
End If
Next
xs += 1
Loop
' Find right margin
Do While xf > xs
For y As Integer = ys To yf
If image.GetPixel(xf, y).ToArgb <> Color.White.ToArgb Then
Exit Do
End If
Next
xf -= 1
Loop
' Find top margin
Do While ys < yf
For x As Integer = xs To xf
If image.GetPixel(x, ys).ToArgb <> Color.White.ToArgb Then
Exit Do
End If
Next
ys += 1
Loop
' Find bottom margin
Do While yf > ys
For x As Integer = xs To xf
If image.GetPixel(x, yf).ToArgb <> Color.White.ToArgb Then
Exit Do
End If
Next
yf -= 1
Loop
Return New SizeF(xf - xs + 1, yf - ys + 1)
End Function
If it helps anyone, I transformed answer from smirkingman to C#, fixing memory bugs (using - Dispose) and outer loop breaks (no TODOs). I also used scaling on graphics (and fonts), so I added that, too (didn't work otherwise). And it returns RectangleF, because I wanted to position the text precisely (with Graphics.DrawText).
The not-perfect but good enough for my purpose source code:
static class StringMeasurer
{
private static SizeF GetScaleTransform(Matrix m)
{
/*
3x3 matrix, affine transformation (skew - used by rotation)
[ X scale, Y skew, 0 ]
[ X skew, Y scale, 0 ]
[ X translate, Y translate, 1 ]
indices (0, ...): X scale, Y skew, Y skew, X scale, X translate, Y translate
*/
return new SizeF(m.Elements[0], m.Elements[3]);
}
public static RectangleF MeasureString(Graphics graphics, Font f, string s)
{
//copy only scale, not rotate or transform
var scale = GetScaleTransform(graphics.Transform);
// Get initial estimate with MeasureText
//TextFormatFlags flags = TextFormatFlags.Left | TextFormatFlags.NoClipping;
//Size proposedSize = new Size(int.MaxValue, int.MaxValue);
//Size size = TextRenderer.MeasureText(graphics, s, f, proposedSize, flags);
SizeF sizef = graphics.MeasureString(s, f);
sizef.Width *= scale.Width;
sizef.Height *= scale.Height;
Size size = sizef.ToSize();
int xLeft = 0;
int xRight = size.Width - 1;
int yTop = 0;
int yBottom = size.Height - 1;
// Create a bitmap
using (Bitmap image = new Bitmap(size.Width, size.Height))
{
image.SetResolution(graphics.DpiX, graphics.DpiY);
StringFormat strFormat = new StringFormat();
strFormat.Alignment = StringAlignment.Near;
strFormat.LineAlignment = StringAlignment.Near;
// Draw the actual text
using (Graphics g = Graphics.FromImage(image))
{
g.SmoothingMode = graphics.SmoothingMode;
g.TextRenderingHint = graphics.TextRenderingHint;
g.Clear(Color.White);
g.ScaleTransform(scale.Width, scale.Height);
g.DrawString(s, f, Brushes.Black, new PointF(0, 0), strFormat);
}
// Find the true boundaries of the glyph
// Find left margin
for (; xLeft < xRight; xLeft++)
for (int y = yTop; y <= yBottom; y++)
if (image.GetPixel(xLeft, y).ToArgb() != Color.White.ToArgb())
goto OUTER_BREAK_LEFT;
OUTER_BREAK_LEFT: ;
// Find right margin
for (; xRight > xLeft; xRight--)
for (int y = yTop; y <= yBottom; y++)
if (image.GetPixel(xRight, y).ToArgb() != Color.White.ToArgb())
goto OUTER_BREAK_RIGHT;
OUTER_BREAK_RIGHT: ;
// Find top margin
for (; yTop < yBottom; yTop++)
for (int x = xLeft; x <= xRight; x++)
if (image.GetPixel(x, yTop).ToArgb() != Color.White.ToArgb())
goto OUTER_BREAK_TOP;
OUTER_BREAK_TOP: ;
// Find bottom margin
for (; yBottom > yTop; yBottom-- )
for (int x = xLeft; x <= xRight; x++)
if (image.GetPixel(x, yBottom).ToArgb() != Color.White.ToArgb())
goto OUTER_BREAK_BOTTOM;
OUTER_BREAK_BOTTOM: ;
}
var pt = new PointF(xLeft, yTop);
var sz = new SizeF(xRight - xLeft + 1, yBottom - yTop + 1);
return new RectangleF(pt.X / scale.Width, pt.Y / scale.Height,
sz.Width / scale.Width, sz.Height / scale.Height);
}
}
This article on codeproject gives two ways to get the exact size of characters as they are rendered by DrawString.
Personally, the most efficient way and what I recommend, has always been:
const TextFormatFlags _textFormatFlags = TextFormatFlags.NoPadding | TextFormatFlags.NoPrefix | TextFormatFlags.PreserveGraphicsClipping;
// Retrieve width
int width = TextRenderer.MeasureText(element.Currency, Currencyfont, new Size(short.MaxValue, short.MaxValue), _textFormatFlags).Width + 1;
// Retrieve height
int _tempHeight1 = TextRenderer.MeasureText("_", Currencyfont).Height;
int _tempHeight2 = (int)Math.Ceiling(Currencyfont.GetHeight());
int height = Math.Max(_tempHeight1, _tempHeight2) + 1;
You likely need to add the following the the StringFormat
flags:
StringFormatFlags.FitBlackBox
try this solution: http://www.codeproject.com/Articles/2118/Bypass-Graphics-MeasureString-limitation (found it at https://stackoverflow.com/a/11708952/908936)
code:
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 (new[] {ranges});
regions = graphics.MeasureCharacterRanges (text, font, rect, format);
rect = regions[0].GetBounds (graphics);
return (int)(rect.Right + 1.0f);
}
精彩评论