Custom WordWrapper

This article is about a question from chopps on the ASP.NET Forums. He wanted to wordwrap a text to a certain width.

It seemed something simple and easy to answer, but I decided to check on Google if anyone else had a good resource on this. Without any luck, I found remarkably little info on creating your own wordwrap method, so I decided to write it myself in C#. He was happy with the answer, I cleaned the code up a bit, and here's the article about it :)

We begin with creating two projects. One Console App (WordWrapDriver) and one Class Library (WordWrap). Reference the library in the console app.

[csharp] using System; using WordWrapper = WordWrap.WordWrap;

namespace WordWrapDriver { class WordWrapDriver { static void Main(string[] args) { // TODO } / Main / } / WordWrapDriver / } / WordWrapDriver / [/csharp]

Our wordwrapper will be a static method called Wrap. We're going to support a given width and a prefix (for example "> " to prefix like a reply mail).

[csharp] using System; using System.Collections;

namespace WordWrap { public class WordWrap {

        #region Public Methods
        public static string Wrap(string originalText, int maxWidth) {
              return Wrap(originalText, maxWidth, "");
        } /* Wrap */

        public static string Wrap(string originalText, int maxWidth, string preFix) {
              // TODO
        } /* Wrap */
        #endregion
  } /* WordWrap */

} / WordWrap / [/csharp]

Let's begin. In the Wrap method we pass our text as a string to a Wrapper method, which gives us a string array that we loop through and prefix.

[csharp] public static string Wrap(string originalText, int maxWidth, string preFix) { string[] wrappedText = Wrapper(originalText, maxWidth);

  string wrappedBlock = "";
  foreach(string textLine in wrappedText) {
        wrappedBlock = String.Format("{0}\n{1}{2}", wrappedBlock, preFix, textLine);
  }
  wrappedBlock = wrappedBlock.Substring(1);
  return wrappedBlock;

} / Wrap / [/csharp]

So what does Wrapper do? First we filter out \r\n and \r, after we split our text on \n to create 'blocks'.

[csharp] originalText = originalText.Replace("\r\n", "\n"); originalText = originalText.Replace("\r", "\n"); originalText = originalText.Replace("\t", " "); string[] textParagraphs = originalText.Split('\n'); [/csharp]

Now we take each paragraph and check if it's smaller then our maxWidth or not, if it isn't we just add the line. If it is longer we'll have to break it up in separate lines.

[csharp] for (int i = 0; i < textParagraphs.Length; i++) { if (textParagraphs[i].Length <= maxWidth) { // Block of text is smaller then width, add it textLines.Add(textParagraphs[i]); } else { // Block of text is longer, break it up in seperate lines string[] pLines = BreakLines(textParagraphs[i], maxWidth); for (int j = 0; j < pLines.Length; j++) { textLines.Add(pLines[j]); } } } [/csharp]

And lastly we return our lines.

[csharp] string[] wrappedText = new string[textLines.Count]; textLines.CopyTo(wrappedText, 0); return wrappedText; [/csharp]

The only method remaining is the BreakLines method. Here we split up our string into seperate words. With those words we re-create the text, but with the correct width. Therefore we loop through every word and add it to a temporary string, which we check against the given maxWidth. If it's smaller, we continue, if it's not we add the temporary string we had just before adding the word, and we continue, the word causing the 'overflow' will be the first word of the new line. If we have a line that is inside our required length, we check if it's the last word and if that's the case we add our temporary string.

[csharp] while (wordIndex < textWords.Length) { if (textWords[wordIndex] == "") { wordIndex++; } else { string backupLine = tmpLine; if (tmpLine == "") { tmpLine = textWords[wordIndex]; } else { tmpLine = tmpLine + " " + textWords[wordIndex]; }

        if (tmpLine.Length <= maxWidth) {
              wordIndex++;
              // If our line is still small enough, but we don't have anymore words
              // add the line to the collection
              if (wordIndex == textWords.Length) {
                    textLines.Add(tmpLine);
              }
        } else {
              // Our line is too long, add the previous line to the collection
              // and reset the line, the word causing the 'overflow' will be
              // the first word of the new line
              textLines.Add(backupLine);
              tmpLine = "";
        }
  }

} [/csharp]

And here as well, we return our lines.

[csharp] string[] textLinesStr = new string[textLines.Count]; textLines.CopyTo(textLinesStr, 0); return textLinesStr; [/csharp]

In the WordWrapDriver I added some test strings to test out the WordWrapper.

[csharp] string strText = "The\t\tquick brown fox jumped over the lazy dog bla."; Console.WriteLine("20 chars, prefixed example."); Console.WriteLine(strText); Console.WriteLine(); Console.WriteLine("> #################### (20)"); Console.WriteLine(WordWrapper.Wrap(strText, 20, "> ")); Console.ReadLine(); [/csharp]

And here are the sources as usual. Hope you find it usefull.