Tutorial :What is the best way to get a string that fits the provided width in pixels?


I am now facing a problem with getting a string that fits the provided width in pixels.

For example, if I have a sentence like this (in JavaScript):

var mystring = "This is my sentence, I need to get just 50 pixels from it."  

If I use the MeasureString method in C# I can get the width:

Font font = new Font("Segoe UI", 11, FontStyle.Regular, GraphicsUnit.Point);  SizeF size = graphics.MeasureString(mystring, font);  

Let's say the width of this string is 400px but the max width I can display on the site is 50px.

If I shorten the string and measure it until it's less than 50px width it works, but it will require many iterations which is not a good solution at all.

Does anyone have a good solution for this?



Using a binary search for the optimal length it shouldn't take many iterations. Given the intricacies of text rendering, I believe that's the best you can do. You can't just take a width for each char and add them together.


I would like to add to this section - "If I shorten the string and measure it until it's less than 50px width ..."

Why don't you do a binary search starting from the middle of the string. Here's the location for starters. No matter how long the string is - it will take much lesser time to find out what's your ideal length. The complexity reduces to log(n) from n.

For better results, you can truncate your string if it's really long, like 500 characters, for example, before starting your binary search.


string myString = "This is my sentence, I need to get just 50 pixels from it."  Font font = new Font("Segoe UI", 11, FontStyle.Regular, GraphicsUnit.Point);     int desiredWidth = 50;  int myStringWidth = TextRenderer.MeasureText(myString , font).Width;  string result = mystring.Substring(0, myString.Length * desiredWidth / myStringWidth);  

This solution does not consider line breaks.


You can approximate the width of a string as being the sum of the width of sub-strings. If your substrings are bounded by whitespace† it may even be a pretty good approximation. But you can't know until you ask exactly how wide any particular string will be, because the text rendering engine does things like kerning (altering the spacing between characters).

† and you probably want them to be, at least in a European language, because it is much easier to read text broken only on whitespace than text broken mid-word, even if it results in the text looking slightly more ragged.


GetBoxedString method of the StringBoxer class "estimates" the amount of string (Space-separated words, Enter-separated words or even long characters) you can put in a rectangle and returns it (Be careful when copying/pasting it because I couldn't fit whole my code in the gray box below):

public sealed class StringBoxer {

public string GetBoxedString(string s, Size size, Font font)  {      int longestStringLengthInWidth = 0;    var result = string.Empty;    if (size.Height < font.Height)    {      return string.Empty;    }      using (var bmp = new Bitmap(size.Width, size.Height))    {      int index = 0;      var words = this.SplitString(s);          var measuredSizeBeforeAddingWord = new SizeF(0, 0);        using (var graphic = Graphics.FromImage(bmp))      {        longestStringLengthInWidth = CalculateLongestStringLength(size, font);          do        {          if (words[index].Length > longestStringLengthInWidth)          {            //// If a word is longer than the maximum string length for the specified size then break it into characters and add char 0 at the begining of each of those characters            var brokenCharacters = words[index].Select(c => ((char)0) + c.ToString()).ToList();            brokenCharacters.Add(" ");            words.RemoveAt(index);            words.InsertRange(index, brokenCharacters);          }            var measuredSizeAfterAddingWord = graphic.MeasureString(result + (!words[index].EndsWith("\n") ? words[index] + " " : words[index]), font, size);          if ((words[index].Contains('\n') || measuredSizeAfterAddingWord == measuredSizeBeforeAddingWord) && measuredSizeAfterAddingWord.Height >= size.Height-font.Height)          {            return result.TrimEnd();          }            measuredSizeBeforeAddingWord = measuredSizeAfterAddingWord;            if (words[index].Contains((char)0))          {            result += words[index].Replace(((char)0).ToString(), string.Empty);          }          else          {            result += (!words[index].EndsWith("\n") ? words[index] + " " : words[index]);          }            index++;        }        while (index < words.Count);      }    }      return result.TrimEnd();  }    private List<string> SplitString(string s)  {    var words = s.Split(' ').ToList();    var index = 0;    do    {      // If a word contains Enter key(s) then break it into more words and replace them with the original word.      if (!words[index].Contains("\n"))      {        index++;        continue;      }        var enterSplitWords = (words[index] + " ").Split('\n');      var brokenWords = enterSplitWords.Select(str => (enterSplitWords.LastOrDefault() != str ? str + "\n" : str).Replace(" ", string.Empty)).ToList();      words.RemoveAt(index);      words.InsertRange(index, brokenWords);      index += brokenWords.Count;    }    while (index < words.Count);      return words;  }    private static int CalculateLongestStringLength(Size size, Font font)  {    var tempString = string.Empty;    var longestStringLengthInWidth = 0;    using (var bmp = new Bitmap(size.Width, size.Height))    {      using (var graphic = Graphics.FromImage(bmp))      {        do        {          if (Math.Floor(graphic.MeasureString(tempString, font, size).Height) <= font.Height)          {            longestStringLengthInWidth++;          }          else          {            break;          }            tempString += "x";        } while (true);      }    }      return longestStringLengthInWidth;  }  


Note:If u also have question or solution just comment us below or mail us on toontricks1994@gmail.com
Next Post »