locked
Graphics.MeasureString inconsistency RRS feed

  • Question

  • User1829581882 posted

    I have two strings, in Times New Roman that are being measured by Graphics.MeasureString(). Viewing the strings in Word shows one string longer than the other, but MeasureString returns a smaller number for the longer string.

    Here is the code:


        class Program
        {
            public static System.Windows.Forms.Form form = new System.Windows.Forms.Form();
            public static Graphics GRAPHIX = form.CreateGraphics();
    
            static void Main(string[] args)
            {
                string text;
                float total;
    
                Font TIMES = new Font("Times New Roman", 8);
                Font TIMES_BOLD = new Font("Times New Roman", 8, FontStyle.Bold);
                Font TIMES_SMALL = new Font("Times New Roman", 6);
                Font TIMES_ITALIC = new Font("Times New Roman", 8, FontStyle.Italic);
    
                text = "Bedbugs bite(s) - see Injury, superficial, by site";
                total = 0;
                total += GRAPHIX.MeasureString("Bedbugs bite(s)", TIMES_BOLD).Width;
                total += GRAPHIX.MeasureString(" - ", TIMES).Width;
                total += GRAPHIX.MeasureString("see", TIMES_ITALIC).Width;
                total += GRAPHIX.MeasureString(" ", TIMES).Width;
                total += GRAPHIX.MeasureString("Injury, superficial, by site", TIMES).Width;
                Console.WriteLine("{0}: {1}", text, total);
    
    
                text = "Bloom (-Machacek) (-Torre) syndrome 757.39";
                total = 0;
                total += GRAPHIX.MeasureString("Bloom (-Machacek) (-Torre) syndrome", TIMES_BOLD).Width;
                total += GRAPHIX.MeasureString(" ", TIMES).Width;
                total += GRAPHIX.MeasureString("757.39", TIMES).Width;
                Console.WriteLine("{0}: {1}", text, total);
    
                Console.Read();
            }
        }



    Here are the strings (Copy and paste in Word to confirm that the second is longer in Times New Roman):

    Bedbugs bite(s) - see Injury, superficial, by site

    Bloom (-Machacek) (-Torre) syndrome 757.39

    Thursday, November 11, 2010 9:58 AM

Answers

  • User1633691049 posted

    Hi,

    You may use the overload of MeasureString that accepts StringFormat as parameter. I have here an example:


                StringFormat sFormat = new StringFormat(StringFormat.GenericTypographic);
    
                text = "Bedbugs bite(s) - see Injury, superficial, by site";
                total = 0;
                total += GRAPHIX.MeasureString("Bedbugs bite(s)", TIMES_BOLD, Int32.MaxValue, sFormat).Width;
                total += GRAPHIX.MeasureString(" - ", TIMES, Int32.MaxValue, sFormat).Width;
                total += GRAPHIX.MeasureString("see", TIMES_ITALIC, Int32.MaxValue, sFormat).Width;
                total += GRAPHIX.MeasureString(" ", TIMES, Int32.MaxValue, sFormat).Width;
                total += GRAPHIX.MeasureString("Injury, superficial, by site", TIMES, 500, sFormat).Width;
                Console.WriteLine("{0}: {1}", text, total);
    
    
                text = "Bloom (-Machacek) (-Torre) syndrome 757.39";
                total = 0;
                total += GRAPHIX.MeasureString("Bloom (-Machacek) (-Torre) syndrome", TIMES_BOLD, Int32.MaxValue, sFormat).Width;
                total += GRAPHIX.MeasureString(" ", TIMES, Int32.MaxValue, sFormat).Width;
                total += GRAPHIX.MeasureString("757.39", TIMES, Int32.MaxValue, sFormat).Width;
                Console.WriteLine("{0}: {1}", text, total);


    MSDN Remarks:

    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.

    Cheers,

    Florin


    • Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
    Monday, November 15, 2010 5:59 AM

All replies

  • User1633691049 posted

    Hi,

    You may use the overload of MeasureString that accepts StringFormat as parameter. I have here an example:


                StringFormat sFormat = new StringFormat(StringFormat.GenericTypographic);
    
                text = "Bedbugs bite(s) - see Injury, superficial, by site";
                total = 0;
                total += GRAPHIX.MeasureString("Bedbugs bite(s)", TIMES_BOLD, Int32.MaxValue, sFormat).Width;
                total += GRAPHIX.MeasureString(" - ", TIMES, Int32.MaxValue, sFormat).Width;
                total += GRAPHIX.MeasureString("see", TIMES_ITALIC, Int32.MaxValue, sFormat).Width;
                total += GRAPHIX.MeasureString(" ", TIMES, Int32.MaxValue, sFormat).Width;
                total += GRAPHIX.MeasureString("Injury, superficial, by site", TIMES, 500, sFormat).Width;
                Console.WriteLine("{0}: {1}", text, total);
    
    
                text = "Bloom (-Machacek) (-Torre) syndrome 757.39";
                total = 0;
                total += GRAPHIX.MeasureString("Bloom (-Machacek) (-Torre) syndrome", TIMES_BOLD, Int32.MaxValue, sFormat).Width;
                total += GRAPHIX.MeasureString(" ", TIMES, Int32.MaxValue, sFormat).Width;
                total += GRAPHIX.MeasureString("757.39", TIMES, Int32.MaxValue, sFormat).Width;
                Console.WriteLine("{0}: {1}", text, total);


    MSDN Remarks:

    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.

    Cheers,

    Florin


    • Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
    Monday, November 15, 2010 5:59 AM
  • User1829581882 posted

    Thanks for the example. using GenericTypographic has given me the expected result for those strings. I have noticed another problem, though. Here are the two strings, shown in Word in Times 8pt:


    and here is the code which produces the wrong result:

        class Program
        {
            public static System.Windows.Forms.Form form = new System.Windows.Forms.Form();
            public static Graphics GRAPHIX = form.CreateGraphics();
    
            static void Main(string[] args)
            {
                string text;
                float total;
    
                Font TIMES = new Font("Times New Roman", 8);
                Font TIMES_BOLD = new Font("Times New Roman", 8, FontStyle.Bold);
                Font TIMES_SMALL = new Font("Times New Roman", 6);
                Font TIMES_ITALIC = new Font("Times New Roman", 8, FontStyle.Italic);
    
                StringFormat sFormat = new StringFormat(StringFormat.GenericTypographic);
    
                text = "MCLS (mucocutaneous lymph node syndrome)";
                total = 0;
                total += GRAPHIX.MeasureString("MCLS", TIMES_BOLD, Int32.MaxValue, sFormat).Width;
                total += GRAPHIX.MeasureString(" (mucocutaneous lymph node syndrome)", TIMES, Int32.MaxValue, sFormat).Width;
                Console.WriteLine("{0}: {1}", text, total);
    
                text = "Posttraumatic brain syndrome, nonpsychotic";
                total = 0;
                total += GRAPHIX.MeasureString("Posttraumatic brain syndrome, nonpsychotic", TIMES_BOLD, Int32.MaxValue, sFormat).Width;
                Console.WriteLine("{0}: {1}", text, total);
    
                Console.Read();
                return;
            }
        }



    Monday, November 15, 2010 9:23 AM
  • User1633691049 posted

    I did add a second method mentioned in the MSDN: MeasureCharacterRanges, I did also follow the recommendation to set the TextRenderingHint to AntiAlias.

    Here is the code to compare the results:

            public static System.Windows.Forms.Form form = new System.Windows.Forms.Form();
            public static Graphics GRAPHIX = form.CreateGraphics();
    
            static void Main(string[] args)
            {
                string text;
                float total;
    
                Font TIMES = new Font("Times New Roman", 8);
                Font TIMES_BOLD = new Font("Times New Roman", 8, FontStyle.Bold);
                Font TIMES_SMALL = new Font("Times New Roman", 6);
                Font TIMES_ITALIC = new Font("Times New Roman", 8, FontStyle.Italic);
    
                GRAPHIX.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
    
                Console.WriteLine();
    
                StringFormat sFormat = new StringFormat(StringFormat.GenericTypographic);
                //sFormat.FormatFlags = StringFormatFlags.NoClip | StringFormatFlags.LineLimit | StringFormatFlags.FitBlackBox | StringFormatFlags.MeasureTrailingSpaces;
    
                text = "Bedbugs bite(s) - see Injury, superficial, by site";
                total = 0;
                total += GRAPHIX.MeasureString("Bedbugs bite(s)", TIMES_BOLD, Int32.MaxValue, sFormat).Width;
                total += GRAPHIX.MeasureString(" - ", TIMES, Int32.MaxValue, sFormat).Width;
                total += GRAPHIX.MeasureString("see", TIMES_ITALIC, Int32.MaxValue, sFormat).Width;
                total += GRAPHIX.MeasureString(" ", TIMES, Int32.MaxValue, sFormat).Width;
                total += GRAPHIX.MeasureString("Injury, superficial, by site", TIMES, 500, sFormat).Width;
                Console.WriteLine("{0}: {1}", text, total);
    
                total = 0;
                total += MeasureDisplayStringWidth(GRAPHIX, "Bedbugs bite(s)", TIMES_BOLD, sFormat);
                total += MeasureDisplayStringWidth(GRAPHIX, " - ", TIMES, sFormat);
                total += MeasureDisplayStringWidth(GRAPHIX, "see", TIMES_ITALIC, sFormat);
                total += MeasureDisplayStringWidth(GRAPHIX, " ", TIMES, sFormat);
                total += MeasureDisplayStringWidth(GRAPHIX, "Injury, superficial, by site", TIMES, sFormat);
                Console.WriteLine("{0}: {1}*", text, total);
    
                text = "Bloom (-Machacek) (-Torre) syndrome 757.39";
                total = 0;
                total += GRAPHIX.MeasureString("Bloom (-Machacek) (-Torre) syndrome", TIMES_BOLD, Int32.MaxValue, sFormat).Width;
                total += GRAPHIX.MeasureString(" ", TIMES, Int32.MaxValue, sFormat).Width;
                total += GRAPHIX.MeasureString("757.39", TIMES, Int32.MaxValue, sFormat).Width;
                Console.WriteLine("{0}: {1}", text, total);
    
                total = 0;
                total += MeasureDisplayStringWidth(GRAPHIX, "Bloom (-Machacek) (-Torre) syndrome", TIMES_BOLD, sFormat);
                total += MeasureDisplayStringWidth(GRAPHIX, " ", TIMES, sFormat);
                total += MeasureDisplayStringWidth(GRAPHIX, "757.39", TIMES, sFormat);
                Console.WriteLine("{0}: {1}*", text, total);
    
    
                text = "MCLS (mucocutaneous lymph node syndrome)";
                total = 0;
                total += GRAPHIX.MeasureString("MCLS", TIMES_BOLD, Int32.MaxValue, sFormat).Width;
                total += GRAPHIX.MeasureString(" (mucocutaneous lymph node syndrome)", TIMES, Int32.MaxValue, sFormat).Width;
                Console.WriteLine("{0}: {1}", text, total);
    
                total = 0;
                total += MeasureDisplayStringWidth(GRAPHIX, "MCLS", TIMES_BOLD, sFormat);
                total += MeasureDisplayStringWidth(GRAPHIX, " (mucocutaneous lymph node syndrome)", TIMES, sFormat);
                Console.WriteLine("{0}: {1}*", text, total);
    
                text = "Posttraumatic brain syndrome, nonpsychotic";
                total = 0;
                total += GRAPHIX.MeasureString("Posttraumatic brain syndrome, nonpsychotic", TIMES_BOLD, Int32.MaxValue, sFormat).Width;
                Console.WriteLine("{0}: {1}", text, total);
    
                total = 0;
                total += MeasureDisplayStringWidth(GRAPHIX, "Posttraumatic brain syndrome, nonpsychotic", TIMES_BOLD, sFormat);
                Console.WriteLine("{0}: {1}*", text, total);
    
    
                Console.ReadLine();
            }
    
    
            static public float MeasureDisplayStringWidth(Graphics graphics, string text,
                                                        Font font, StringFormat sFormat)
            {
                StringFormat strFormat = new StringFormat(sFormat);
                 
                CharacterRange[] ranges;
                Region[] charRegions;
    
                float width = 0F;
                for (int index = 0, i; index < text.Length; index += 32)
                {
                    if (text.Length - index < 1) //if index becomes bigger then the text length
                        break;
    
                    else if (text.Length - index > 32) //if we're guarenteed 32 characters of bliss
                        ranges = new CharacterRange[32];
    
                    else //we got less then 32 characters, but atleast 1
                        ranges = new CharacterRange[text.Length - index];
    
                    //get the character ranges for the 32 char-length or less segment of text we're at
                    for (i = 0; i < ranges.Length; i++)
                        ranges[i] = new CharacterRange(index + i, 1);
    
                    //setup for measure
                    strFormat.SetMeasurableCharacterRanges(ranges);
    
                    System.Drawing.RectangleF rect = new System.Drawing.RectangleF(0, 0,
                                                                                  1000, 1000);
    
                    //measure it
                    charRegions = graphics.MeasureCharacterRanges(text, font, rect, strFormat);
    
                    //draw text one character at a time
                    for (i = 0; i < charRegions.Length; i++)
                    {
                        //draw the text
                        RectangleF bounds = charRegions[i].GetBounds(graphics);
                        width += bounds.Width;
                    }
                }
                return width;
            }


    I'm not sure how Word is drawing the texts. This is what you can do with GDI+. Most of the MeasureDisplayStringWidth code credit goes here:

    http://www.codeproject.com/KB/GDI-plus/drawing_text_strings.aspx


    Florin


    Monday, November 15, 2010 10:53 AM