MeasureString and DrawString difference

2019-02-17 01:11发布

问题:

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; else 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.

回答1:

MeasureString() method had some issues, especially when drawing non-ASCII characters. Please try TextRenderer.MeasureText() instead.



回答2:

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


回答3:

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);
    }
}


回答4:

This article on codeproject gives two ways to get the exact size of characters as they are rendered by DrawString.



回答5:

You likely need to add the following the the StringFormat flags:

StringFormatFlags.FitBlackBox


回答6:

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);
}