41

I am trying to word wrap a string into multiple lines. Every line will have a defined width.

For example, I would get this result if I word wrap it to an area of 120 pixels in width:

Lorem ipsum dolor sit amet,
consectetur adipiscing elit. Sed augue
velit, tempor non vulputate sit amet,
dictum vitae lacus. In vitae ante
justo, ut accumsan sem. Donec
pulvinar, nisi nec sagittis consequat,
sem orci luctus velit, sed elementum
ligula ante nec neque. Pellentesque
habitant morbi tristique senectus et
netus et malesuada fames ac turpis
egestas. Etiam erat est, pellentesque
eget tincidunt ut, egestas in ante.
Nulla vitae vulputate velit. Proin in
congue neque. Cras rutrum sodales
sapien, ut convallis erat auctor vel.
Duis ultricies pharetra dui, sagittis
varius mauris tristique a. Nam ut
neque id risus tempor hendrerit.
Maecenas ut lacus nunc. Nulla
fermentum ornare rhoncus. Nulla
gravida vestibulum odio, vel commodo
magna condimentum quis. Quisque
sollicitudin blandit mi, non varius
libero lobortis eu. Vestibulum eu
turpis massa, id tincidunt orci.
Curabitur pellentesque urna non risus
adipiscing facilisis. Mauris vel
accumsan purus. Proin quis enim nec
sem tempor vestibulum ac vitae augue.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
ForrestWhy
  • 486
  • 1
  • 4
  • 9

7 Answers7

41
static void Main(string[] args)
{
    List<string> lines = WrapText("Add some text", 300, "Calibri", 11);

    foreach (var item in lines)
    {
        Console.WriteLine(item);
    }

    Console.ReadLine();
}

static List<string> WrapText(string text, double pixels, string fontFamily, 
    float emSize)
{
    string[] originalLines = text.Split(new string[] { " " }, 
        StringSplitOptions.None);

    List<string> wrappedLines = new List<string>();

    StringBuilder actualLine = new StringBuilder();
    double actualWidth = 0;

    foreach (var item in originalLines)
    {
        FormattedText formatted = new FormattedText(item, 
            CultureInfo.CurrentCulture, 
            System.Windows.FlowDirection.LeftToRight,
            new Typeface(fontFamily), emSize, Brushes.Black);

        actualLine.Append(item + " ");
        actualWidth += formatted.Width;

        if (actualWidth > pixels)
        {
            wrappedLines.Add(actualLine.ToString());
            actualLine.Clear();
            actualWidth = 0;
        }
    }

    if(actualLine.Length > 0)
        wrappedLines.Add(actualLine.ToString());

    return wrappedLines;
}

Add WindowsBase and PresentationCore libraries.

gunr2171
  • 16,104
  • 25
  • 61
  • 88
as-cii
  • 12,819
  • 4
  • 41
  • 43
  • Any experiences yet how much the measurement of `FormattedText` matches the rendering of WinForms text (using `Graphics.DrawString`or `TextRenderer.DrawText`)? – floele Apr 09 '14 at 11:14
  • This is the best. Hope it can also handle when the Formatted(item).Width exceeds the max pixel provided. – Jin Ginusuke Apr 28 '22 at 07:36
4

Here's a version I came up with for my XNA game...

(Note that it's a snippet, not a proper class definition. Enjoy!)

using System;
using System.Text;
using Microsoft.Xna.Framework.Graphics;

public static float StringWidth(SpriteFont font, string text)
{
    return font.MeasureString(text).X;
}

public static string WrapText(SpriteFont font, string text, float lineWidth)
{
    const string space = " ";
    string[] words = text.Split(new string[] { space }, StringSplitOptions.None);
    float spaceWidth = StringWidth(font, space),
        spaceLeft = lineWidth,
        wordWidth;
    StringBuilder result = new StringBuilder();

    foreach (string word in words)
    {
        wordWidth = StringWidth(font, word);
        if (wordWidth + spaceWidth > spaceLeft)
        {
            result.AppendLine();
            spaceLeft = lineWidth - wordWidth;
        }
        else
        {
            spaceLeft -= (wordWidth + spaceWidth);
        }
        result.Append(word + space);
    }

    return result.ToString();
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
tvwxyz
  • 195
  • 3
  • 14
3

Thanks! I take a method from as-cii's answer with some changes, for using it in Windows Forms. I am using TextRenderer.MeasureText instead of FormattedText:

static List<string> WrapText(string text, double pixels, Font font)
{
    string[] originalLines = text.Split(new string[] { " " },
        StringSplitOptions.None);

    List<string> wrappedLines = new List<string>();

    StringBuilder actualLine = new StringBuilder();
    double actualWidth = 0;

    foreach (var item in originalLines)
    {
        int w = TextRenderer.MeasureText(item + " ", font).Width;
        actualWidth += w;

        if (actualWidth > pixels)
        {
            wrappedLines.Add(actualLine.ToString());
            actualLine.Clear();
            actualWidth = w;
        }

        actualLine.Append(item + " ");
    }

    if(actualLine.Length > 0)
        wrappedLines.Add(actualLine.ToString());

    return wrappedLines;
}

And a little remark: the line actualLine.Append(item + " "); needs to be placed after checking the width, because if actualWidth > pixels, this word must be in the next line.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
undejavue
  • 99
  • 6
2
public static string GetTextWithNewLines(string value = "", int charactersToWrapAt = 35, int maxLength = 250)
{
    if (string.IsNullOrWhiteSpace(value))
        return "";

    value = value.Replace("  ", " ");
    var words = value.Split(' ');
    var sb = new StringBuilder();
    var currString = new StringBuilder();

    foreach (var word in words)
    {
        if (currString.Length + word.Length + 1 < charactersToWrapAt) // The + 1 accounts for spaces
        {
            sb.AppendFormat(" {0}", word);
            currString.AppendFormat(" {0}", word);
        }
        else
        {
            currString.Clear();
            sb.AppendFormat("{0}{1}", Environment.NewLine, word);
            currString.AppendFormat(" {0}", word);
        }
    }

    if (sb.Length > maxLength)
    {
        return sb.ToString().Substring(0, maxLength) + " ...";
    }

    return sb.ToString().TrimStart().TrimEnd();
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
theJerm
  • 4,482
  • 2
  • 30
  • 23
1

For Windows Forms:

List<string> WrapText(string text, int maxWidthInPixels, Font font)
{
    string[] originalLines = text.Split(new string[] { " " }, StringSplitOptions.None);

    List<string> wrappedLines = new List<string>();

    StringBuilder actualLine = new StringBuilder();
    int actualWidth = 0;

    foreach (var item in originalLines)
    {
        Size szText = TextRenderer.MeasureText(item, font);

        actualLine.Append(item + " ");
        actualWidth += szText.Width;

        if (actualWidth > maxWidthInPixels)
        {
            wrappedLines.Add(actualLine.ToString());
            actualLine.Clear();
            actualWidth = 0;
        }
    }

    if (actualLine.Length > 0)
        wrappedLines.Add(actualLine.ToString());

    return wrappedLines;
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Martin.Martinsson
  • 1,894
  • 21
  • 25
0

You can get the (approximate) width of a string from the System.Drawing.Graphics class using the MeasureString() method. If you need a very precise width, I think you have to use the MeasureCharacterRanges() method. Here is some sample code using the MeasureString() method to do roughly what you asked for:

using System;
using System.Collections.Generic; // For List<>
using System.Drawing; // For Graphics and Font

private List<string> GetWordwrapped(string original)
{
    List<string> wordwrapped = new List<string>();

    Graphics graphics = Graphics.FromHwnd(this.Handle);
    Font font = new Font("Arial", 10);

    string currentLine = string.Empty;

    for (int i = 0; i < original.Length; i++)
    {
        char currentChar = original[i];
        currentLine += currentChar;
        if (graphics.MeasureString(currentLine, font).Width > 120)
        {
            // Exceeded length, back up to last space
            int moveback = 0;
            while (currentChar != ' ')
            {
                moveback++;
                i--;
                currentChar = original[i];
            }
            string lineToAdd = currentLine.Substring(0, currentLine.Length - moveback);
            wordwrapped.Add(lineToAdd);
            currentLine = string.Empty;
        }
    }
    return wordwrapped;
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Thorin
  • 626
  • 4
  • 11
0

I'm wanted to wrap text to draw it afterwards in my image. I tried as-cii's answer, but it didn't work in my case as expected. It always extends the given width of my line (maybe because I use it in combination with a Graphics object to draw the text in my image).

Furthermore his answer (and related ones) just work for > .NET 4 frameworks. In framework .NET 3.5 there isn't any Clear() function for StringBuilder objects. So here is an edited version:

    public static List<string> WrapText(string text, double pixels, string fontFamily, float emSize)
    {
        string[] originalWords = text.Split(new string[] { " " },
            StringSplitOptions.None);

        List<string> wrappedLines = new List<string>();

        StringBuilder actualLine = new StringBuilder();
        double actualWidth = 0;

        foreach (string word in originalWords)
        {
            string wordWithSpace = word + " ";
            FormattedText formattedWord = new FormattedText(wordWithSpace,
                CultureInfo.CurrentCulture,
                System.Windows.FlowDirection.LeftToRight,
                new Typeface(fontFamily), emSize, System.Windows.Media.Brushes.Black);

            actualLine.Append(wordWithSpace);
            actualWidth += formattedWord.Width;

            if (actualWidth > pixels)
            {
                actualLine.Remove(actualLine.Length - wordWithSpace.Length, wordWithSpace.Length);
                wrappedLines.Add(actualLine.ToString());
                actualLine = new StringBuilder();
                actualLine.Append(wordWithSpace);
                actualWidth = 0;
                actualWidth += formattedWord.Width;
            }
        }

        if (actualLine.Length > 0)
            wrappedLines.Add(actualLine.ToString());

        return wrappedLines;
    }

Because I'm working with a Graphics object I tried @Thorins solution. This worked for me much better, as it wraps my text right. But I made some changes so that you can give the method the required parameters. Also there was a bug: the last line was not added to the list, when the condition of the if-block in the for-loop was not reached. So you have to add this line afterwards. The edited code looks like:

    public static List<string> WrapTextWithGraphics(Graphics g, string original, int width, Font font)
    {
        List<string> wrappedLines = new List<string>();

        string currentLine = string.Empty;

        for (int i = 0; i < original.Length; i++)
        {
            char currentChar = original[i];
            currentLine += currentChar;
            if (g.MeasureString(currentLine, font).Width > width)
            {
                // Exceeded length, back up to last space
                int moveback = 0;
                while (currentChar != ' ')
                {
                    moveback++;
                    i--;
                    currentChar = original[i];
                }
                string lineToAdd = currentLine.Substring(0, currentLine.Length - moveback);
                wrappedLines.Add(lineToAdd);
                currentLine = string.Empty;
            }
        }

        if (currentLine.Length > 0)
            wrappedLines.Add(currentLine);

        return wrappedLines;
    }
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
  • Re *"isn't any Clear() function"*: One can simply set the property "`Length`" for a `StringBuilder` object to 0 to clear the text content (while keeping the current capacity). [Example](https://github.com/PeterMortensen/Edit_Overflow/blob/master/Dot_NET/OverflowHelper/OverflowHelper/Source/GUI/Forms/frmMainForm.cs): `mScratchSB.Length = 0;`. It is useful for reusing a scratch string buffer (avoiding creation overhead and memory pressure). – Peter Mortensen Nov 21 '19 at 13:33