0

I have a string with newline characters and I want to wrap the words. I want to keep the newline characters so that when I display the text it looks like separate paragraphs. Anyone have a good function to do this? Current function and code below.(not my own function). The WordWrap function seems to be stripping out \n characters.

static void Main(string[] args){

        StreamReader streamReader = new StreamReader("E:/Adventure Story/Intro.txt");

        string intro = "";
        string line;

        while ((line = streamReader.ReadLine()) != null)
        {
            intro += line;
            if(line == "")
            {
                intro += "\n\n";
            }
        } 
        WordWrap(intro);

public static void WordWrap(string paragraph)
    {
        paragraph = new Regex(@" {2,}").Replace(paragraph.Trim(), @" ");
        var left = Console.CursorLeft; var top = Console.CursorTop; var lines = new List<string>();
        for (var i = 0; paragraph.Length > 0; i++)
        {
            lines.Add(paragraph.Substring(0, Math.Min(Console.WindowWidth, paragraph.Length)));
            var length = lines[i].LastIndexOf(" ", StringComparison.Ordinal);
            if (length > 0) lines[i] = lines[i].Remove(length);
            paragraph = paragraph.Substring(Math.Min(lines[i].Length + 1, paragraph.Length));
            Console.SetCursorPosition(left, top + i); Console.WriteLine(lines[i]);
        }
    }
  • add `\n` to the end of each line – Rainbow Oct 02 '18 at 09:55
  • 2
    Or `System.Enviroment.NewLine` –  Oct 02 '18 at 09:56
  • 5
    It is a bit unclear what you're going to achieve. Provide your effort, even if written i pseudo-code. – krzyski Oct 02 '18 at 09:56
  • @ZohirSalakCeNa I already have \n s in my string – Alasdair Cooper Oct 02 '18 at 09:57
  • 1
    Possible duplicate of [Make Console.WriteLine() wrap words instead of letters](https://stackoverflow.com/questions/20534318/make-console-writeline-wrap-words-instead-of-letters) – Access Denied Oct 02 '18 at 09:58
  • 2
    Well then i can't help much further as though that will solve your problem yet it doesn't so i should assume there's some error in your code, so if you would include your code it would much easier for us to help you :) – Rainbow Oct 02 '18 at 09:58
  • @ZohirSalakCeNa I'm on my phone and I'll add the code later. Thanks for the help so far. – Alasdair Cooper Oct 02 '18 at 10:02
  • Could you clarify your question, please? For example, could you add a screenshot of your output and a sketch / simulation of what you want it to look like? Is there a specific limitation on the line length or it has to wrap with screen resize? What is your output media? It sound like a console screen, but many console screens DO wrap the words by default. Do you want to wrap lines at the a specific width? Is it OK if it doesn't re-wrap when you resize the console window? Do you expect your text to be written out to other devices (printers, other screens, etc)? – aiodintsov Oct 03 '18 at 19:21
  • @aiodintsov I've added the output. No, there's no limitations on line length and I don't care about screen resizing, only the initial printout. Console doesn't wrap by default. And no to the last question. – Alasdair Cooper Oct 03 '18 at 19:39

3 Answers3

3

Here is a word wrap function that works by using regular expressions to find the places that it's ok to break and places where it must break. Then it returns pieces of the original text based on the "break zones". It even allows for breaks at hyphens (and other characters) without removing the hyphens (since the regex uses a zero-width positive lookbehind assertion).

    IEnumerable<string> WordWrap(string text, int width)
    {
        const string forcedBreakZonePattern = @"\n";
        const string normalBreakZonePattern = @"\s+|(?<=[-,.;])|$";

        var forcedZones = Regex.Matches(text, forcedBreakZonePattern).Cast<Match>().ToList();
        var normalZones = Regex.Matches(text, normalBreakZonePattern).Cast<Match>().ToList();

        int start = 0;

        while (start < text.Length)
        {
            var zone = 
                forcedZones.Find(z => z.Index >= start && z.Index <= start + width) ??
                normalZones.FindLast(z => z.Index >= start && z.Index <= start + width);

            if (zone == null)
            {
                yield return text.Substring(start, width);
                start += width;
            }
            else
            {
                yield return text.Substring(start, zone.Index - start);
                start = zone.Index + zone.Length;
            }
        }
    }
Mike
  • 435
  • 2
  • 7
  • Wonder what will happen if I will provide it a string with out any spaces :) – HellBaby Aug 23 '21 at 06:44
  • @HellBaby It just breaks it at the specified width when there are no spaces. That's what the if zone == null block is for. That was the desired behavior, for my use case anyway. – Mike Sep 17 '21 at 13:14
0

If you want another newline to make text look-like paragraphs, just use Replace method of your String object.

        var str =
            "Line 1\n" +
            "Line 2\n" +
            "Line 3\n";

        Console.WriteLine("Before:\n" + str);

        str = str.Replace("\n", "\n\n");

        Console.WriteLine("After:\n" + str);
Dmitry
  • 436
  • 2
  • 7
0

Recently I've been working on creating some abstractions that imitate window-like features in a performance- and memory-sensitive console context.

To this end I had to implement word-wrapping functionality without any unnecessary string allocations.

The following is what I managed to simplify it into. This method:

  • preserves new-lines in the input string,
  • allows you to specify what characters it should break on (space, hyphen, etc.),
  • returns the start indices and lengths of the lines via Microsoft.Extensions.Primitives.StringSegment struct instances (but it's very simple to replace this struct with your own, or append directly to a StringBuilder).
public static IEnumerable<StringSegment> WordWrap(string input, int maxLineLength, char[] breakableCharacters)
{
    int lastBreakIndex = 0;

    while (true)
    {
        var nextForcedLineBreak = lastBreakIndex + maxLineLength;

        // If the remainder is shorter than the allowed line-length, return the remainder. Short-circuits instantly for strings shorter than line-length.
        if (nextForcedLineBreak >= input.Length)
        {
            yield return new StringSegment(input, lastBreakIndex, input.Length - lastBreakIndex);
            yield break;
        }

        // If there are native new lines before the next forced break position, use the last native new line as the starting position of our next line.
        int nativeNewlineIndex = input.LastIndexOf(Environment.NewLine, nextForcedLineBreak, maxLineLength);
        if (nativeNewlineIndex > -1)
        {
            nextForcedLineBreak = nativeNewlineIndex + Environment.NewLine.Length + maxLineLength;
        }

        // Find the last breakable point preceding the next forced break position (and include the breakable character, which might be a hypen).
        var nextBreakIndex = input.LastIndexOfAny(breakableCharacters, nextForcedLineBreak, maxLineLength) + 1;

        // If there is no breakable point, which means a word is longer than line length, force-break it.
        if (nextBreakIndex == 0)
        {
            nextBreakIndex = nextForcedLineBreak;
        }

        yield return new StringSegment(input, lastBreakIndex, nextBreakIndex - lastBreakIndex);

        lastBreakIndex = nextBreakIndex;
    }
}
Leaky
  • 3,088
  • 2
  • 26
  • 35