C++ word wrap for console output – Tutorial

I’v been tinkering in C++ and decided to start making an old fashioned “text adventure” game as a nice little project to grasp the language.

As I started writing the game it quickly became clear that formatting all the strings manually with “new line” characters (\n) to get the text to not breakup mid-sentence in the console was going to be hassle. I fancied a challenge and rather than grabbing some pre-written code from the web (how not to learn anything) I decided to work on an algorithm and make my own function that can deal with any string size and wrap the text neatly to the console.

I thought I’d make this little tutorial for anyone who wants to use it in their program but also wants to understand how the code works.

Here’s the function:

Note: Functionality has been gradually added, the updates further down detail the changes. I’ve included a Visual Studio code solution zip with the latest code at the bottom of this page.

void OutputText(std::string s)
{
	for (unsigned int i = 1; i <= s.length() ; i++)
	{
		if ((i % BUFFER_SIZE) == 0)
		{
			int spaceCount = 0;

			if (s[(i-1)] != ' ')
			{
				for (int j = (i-1); j > -1 ; j--)
				{
					if (s[j] == ' ')
					{
							s.insert(j, spaceCount, ' ');
							break;
					}
					else spaceCount++;
				}
			}
		}
	}

	std::cout << s << std::endl;
}

Although the function is written here in C++, the algorithm can easily be applied to C# and other languages with just a few syntax changes. I was in two minds whether to go a route of splitting a string at the end of the line and then adding a newline character on the end, then “glueing” the strings together and repeating for each line, or whether to do it the way I did it which was by finding the last character on each line and then looping back through the line until it gets to a space and simply inserting “whitespace” on the end.

I’ll go through exactly what the function is doing:

for (unsigned int i = 1; i <= s.length() ; i++) 

Here we are preparing to loop through each character in the string to find where we want the line breaks.

 if ((i % BUFFER_SIZE) == 0) 

This bit of modulo arithmetic checks the current loop iteration value to see if it is a multiple of the console window buffer width (80 default) and thus the character in the strings length that would be at the end of a line. By checking if it’s a multiple it allows us to apply this function to any size string. You’ll need to define BUFFER_SIZE in your program or simply replace it with the number value you want i.e 80.

BUFFER_SIZE could easily be changed via a variable and set to whatever buffer width your console window is using (see bottom of this article on how to get the current console buffer width value).

int spaceCount = 0;

I initialise this variable for later use to keep track of the number of characters the loop has backtracked through in the string to find a space. (We’ll need to insert this number of whitespace into the string).

 if (s[(i-1)] != ' ') 

Here we are establishing whether the character at the end of the line is already a space. If it is, we don’t need to do anything and it’ll skip to the next loop iteration. Note “(i-1)” here, we do this because “i” is looking at the string based on it’s length with the lowest possible length obviously being 1. However when it comes to looking at the character array of the string “s”, arrays in C languages start at 0, thus we need to subtract 1 from the total length of the string to balance it. I could have adjusted the “for” loop to start at 0 instead and end at (s.length()-1) however this would have meant adjusting the modulo statement and personally it made more sense to me this way.

 for (int j = (i-1); j > -1 ; j--)

Once we have the character at the end of a line in “i” and know it’s not already a space, we loop backwards from this position in the string until we find a space i.e the end of a word.

if (s[j] == ' ')
{
	s.insert(j, spaceCount, ' ');
	break;
}
else spaceCount++;

As stated above, we check if each character we loop through is a space (‘ ‘). If it is then we have found the end of the last whole word on the line that fits and thus where we need to insert whitespace. We know how many spaces to insert because each time a character is checked and isn’t a space, we increment the “spaceCount” variable so we know how many spaces to insert to fill the line to the end.

std::cout << s << std::endl;

We then output the newly word wrapped string to the console!

It’s great for text adventures or any program that outputs large strings since you can just call this function with any size string (within memory limits!) and it’ll do the rest. I tried to keep the solution as neat and minimal as I could.

Update (14/08/2012): Retrieving the console buffer width value

I thought I’d add in addition a way to get the console buffer width from the console each time text is output. This allows users to change the console window size and the text will wrap to it on the next output.

void OutputText(std::string s)
{
	int bufferWidth = GetBufferWidth();

	for (unsigned int i = 1; i <= s.length() ; i++)
	{
		if ((i % bufferWidth) == 0)
		{
			int spaceCount = 0;

			if (s[(i-1)] != ' ')
			{
				for (int j = (i-1); j > -1 ; j--)
				{
					if (s[j] == ' ')
					{
							s.insert(j, spaceCount, ' ');
							break;
					}
					else spaceCount++;
				}
			}
		}
	}

	// Output string to console
	std::cout << s << std::endl;
}

As you see here I have added at the top a function call “GetBufferWidth()”. This will return an integer with a value of the currently set console buffer width and store it in the bufferWidth variable that the OutputText function uses.

The code for GetBufferWidth is here:

int GetBufferWidth()
{
	CONSOLE_SCREEN_BUFFER_INFO bufferInfo;
	int bufferWidth, result;

	result = GetConsoleScreenBufferInfo(GetStdHandle( STD_OUTPUT_HANDLE ),&bufferInfo);

	if(result)
	{
		bufferWidth = bufferInfo.dwSize.X;
	}

	return bufferWidth;
}

It’s a relatively simple function that makes use of the CONSOLE_SCREEN_BUFFER_INFO class. You will need the ensure you have “#include” for windows.h specifically for the “wincon.h” child library containing the console services.

“dwsize.X” gives us the max number of available character “columns” in the window (by default 80), Note* “dwsize.Y” isn’t needed here but would give us the max number of available character lines for the window so by default it would return a value of 300 since that’s the default limit for console output (it would not give us the value of lines within the visible portion of the window).

Update (20/10/2012): Wrapping string text with newline characters (paragraphs)

I recently had a comment mentioning the problem with using this algorithm with paragraphed text. I had already come across this issue and had added some additional code to allow it to work with strings with “\n” (newline) characters in. I’ve been meaning to do a blog post update with it for quite a while. I appreciate comments from people because it means people are using the code I’ve put on here and gives me an incentive to update this post so thanks.

Below is the full modified code. As can be seen from line 11, I have added a new block of code to check for any “\n” characters (you could extract this into a separate function for neatness). If a “\n” is found it inserts spaces into the string before the “\n” character filling to the end of the line and jumps the loop iteration to the first character on the next line. I’ve also added a new char declaration on line 7.

By adding whitespace before the “\n” character and thus moving it right to the end of the line, we are effectively countering all that extra space that the console window needs to generate when it hits a newline character. This would completely mess up our algorithms positioning in the console window if it wasn’t at the end of the line.

void OutputText(std::string s)
{
	int bufferWidth = GetBufferWidth();

	for (unsigned int i = 1; i <= s.length() ; i++)
	{
		char c = s[i-1];

		int spaceCount = 0;

		// Add whitespace if newline detected.
		if (c == '\n')
		{
			int charNumOnLine = ((i) % bufferWidth);
			spaceCount = bufferWidth - charNumOnLine;
			s.insert((i-1), (spaceCount), ' ');
			i+=(spaceCount);
			continue;
		}

		if ((i % bufferWidth) == 0)
		{
			if (c != ' ')
			{
				for (int j = (i-1); j > -1 ; j--)
				{
					if (s[j] == ' ')
					{
							s.insert(j, spaceCount, ' ');
							break;
					}
					else spaceCount++;
				}
			}
		}
	}

	// Output string to console
	std::cout << s << std::endl;
}

In detail:

if (c == '\n')
		{
			int charNumOnLine = ((i) % bufferWidth);

Here if the current character in the string array is a “\n” then it enters the new block. It then declares a new temporary variable for use later that contains the characters position within the line (defined by the width of your console window).

spaceCount = bufferWidth - charNumOnLine;

Next we calculate the number of spaces we will need to insert into the string from the “\n” characters position to move it to the end of the line. We do this by finding the difference between the position of the current character in the line and the last position on the line.

s.insert((i-1), (spaceCount), ' ');

We then insert just before the “\n” character the calculated number of spaces.

i+=(spaceCount);
continue;
}

Then we increment the current loop iteration by the number of spaces we just added which should set the loop index to the first character on the next line and “continue;” which will take us straight to execute the next loop iteration, repeating the process and checking every subsequent character for any more line breaks. If it doesn’t find one, it carries on as normal.

Here’s a screenshot of it in action with double “/n/n” line breaks inserted randomly into the string:

 

Download link for latest version of code (Visual Studio Solution): http://sdrv.ms/11nRRZy

 

Advertisements

15 thoughts on “C++ word wrap for console output – Tutorial

  1. had the same problem with my Tweet Printer, where I wanted to avoid the tweets breaking over a line when they were printed. I used C#, broke the string into words using Spilt and then added each word to the output, adding spaces where required.

    Funny how you can find multiple answers to the same question. I much prefer your approach to mine, as it should be a bit quicker. One thing though. I spent some time worrying about what would happen if the output line contained a string that was longer than the line but contained no spaces (perhaps the writer has used commas and full stops). In that case I’m not sure what would happen with your solution. You might want to give it some test data and find out.

    • Really good point and it’s something I didn’t directly consider. Now I know if the string is contiguous with no spaces it would still work because it would loop back all the way for each line and not bother reading the inner code, thus not inserting any whitespace and would output the string as normal but…you gave me an idea of where it would fail just to take your point further:

      Although the scenario wouldn’t be common if the first part of a long string was contiguous with no spaces and ran over the first line break but then after that there WERE spaces it would mess the layout up. This is because I’m not resetting the spaceCount value back to 0 each time it starts to loop back from the end of each line. So for example if the first several lines worth had no spaces, spaceCount would have been incrementing all that time and as soon as it found a space it would insert a massive amount of whitespace!

      Luckily I can fix this just by adding “spaceCount = 0;” after the modulo “if” statement. Thanks Rob!

    • Thanks for the link, that’ll be a nice addition specifically for C# or any .Net integrated programs. I’ll post an update to this article with a way to get the buffer width using only C++ code libraries.

  2. This is a helpful snippet of code, although in my case I have ‘paragraphs’ so the text has (or rather, had) embedded newlines, which looks to screw up your counting of where you are in your line (naturally enough).

  3. Suggestion for improvement. Your for loop that increments i is going to increment i 79 times (for example) and do nothing else (other than check to see if i % 80 == 0. Here’s a suggested alternative:

    for ( unsigned int i = bufferWidth; i <= s.length ( ) ; i+=bufferWidth )

    This also allows you to get rid of the if ( ( i % bufferWidth ) == 0 ) check because you can know that every time you come in, that will be so.

    I haven't tested extensively, but on a fairly large block of text, it produced the same output.

    • Thanks for the suggestion. Your right, this improvement would work great if not for my recent addition for dealing with newline characters in the string. I now need the For loop to go through each and every character in the string to get the exact character position on the line when a “/n” is detected.

      However, if you don’t need to check for any line breaks using my method, then including your suggestion is neater 🙂

  4. I hope you won’t mind if I throw another suggestion out there. As I kept thinking about this problem, I realized that from my perspective, the approach of modifying the string was unappealing. It required making a copy of what should be a string constant, and inserting a bunch of spaces to fill lines. If, for example, we had a megabyte long string (unlikely example, I know…), and for each of the 12,500 lines we had to insert some spaces, we’d be shifting the tail of that string down in memory (and expanding the allocation) as many times, and on average, you’d be shifting 500,000 bytes. So I did what I had hoped to avoid, which was writing my own function to do this. Thought I’d share it here, if you’d like. I’m sure it can be cleaned up some. Also don’t know if it can be formatted as code here…

    void ConsoleWordWrap ( const _TCHAR *s, unsigned long maxlen )
    {
    	// in order to avoid mucking with the string itself, we'll need to use some of the
    	// lower-level Win32 console output functions which will take a buffer and a TCHAR count
    	// rather than require null-terminated strings.
    	//
    	// general algorithm is to walk the string and output a line whenever:
    	// 1. we hit a newline --or--
    	// 2. we hit our column limit. Break the line at the last space prior, if there was one
    	// lastSpace should remember the beginning of the last space, if more than one in a row
    
    	int				bufferWidth = GetBufferWidth ( );
    	unsigned long	i, cc, nCW;
    	_TCHAR			c;
    	const _TCHAR	*line=s, *lastSpace=s;
    	BOOL			inSpaceString=FALSE;
    	HANDLE			hCO = GetStdHandle ( STD_OUTPUT_HANDLE );
    
    	if ( hCO == INVALID_HANDLE_VALUE ) return;
    
    	for ( i=0, cc=0, c=s[i]; i<maxlen && c; c=s[i++], cc++ )
    	{
    		if ( c == _T('\n') )
    		{
    			// output current line; reset counters
    			WriteConsole ( hCO, line, cc, &nCW, NULL );
    			cc=0;
    			while ( s[i] == _T(' ') ) i++; // skip leading spaces
    			line=&s[i]; lastSpace=line;
    		}
    		else if ( cc == bufferWidth+2 )
    		{
    			// force line out at last space, if there was one
    			if ( lastSpace == line ) WriteConsole ( hCO, line, bufferWidth, &nCW, NULL ); // no spaces found, so just spit out bufferWidth TCHARs; cursor will be forced to next line
    			else
    			{
    				unsigned long len=lastSpace-line;
    
    				WriteConsole ( hCO, line, len, &nCW, NULL );
    				if ( len < bufferWidth ) WriteConsole ( hCO, _T("\n"), 1, &nCW, NULL );
    				i-=(cc-len); // go back to the character after the break
    			}
    			cc=0;
    			while ( s[i] == _T(' ') ) i++; // skip leading spaces
    			line=&s[i]; lastSpace=line;
    		}
    		else if ( c == _T(' ') )
    		{
    			if ( !inSpaceString )
    			{
    				inSpaceString = TRUE;
    				lastSpace = &s[i-1];
    			}
    		}
    		else inSpaceString = FALSE;
    	}
    	if ( cc > 1 ) WriteConsole ( hCO, line, cc-1, &nCW, NULL ); // write out remainder of buff
    	WriteConsole ( hCO, _T("\n"), 1, &nCW, NULL );
    }
    
    • Well if you change the console window dimensions (specifically the width) the next time you write something to the window it will adjust to the new size.

      As per Tastewar, anything already output won’t change. In my case for a Text Adventure game it’s not a big problem because I would generally clear the screen and rewrite the screen as soon as something new happens in the program. It would really only be apparent for things that continually append to the console after a window size change.

  5. Is it possible to display something like this:

    console:
    1_________________________________________________________________
    2| Once a long ago………….blah blahhhhhh………….. |
    3| //story continues to display via loop |
    4| //like the star of pokemon ………… |
    5| |
    6| |
    7| |
    8| Press x to skip |
    9——————————————————————————————————–

    this is the loop for the story:
    for( int i=0; i<strlen(cArray1); i++)
    {
    cout << cArray1[i]; //display character one by one
    Sleep(50);
    if(kbhit()) //press any key to skip
    {
    system("CLS");
    return main();
    }
    }//endfor

    I want to display line 8 while the story is looping and it should be at the bottom of the console. Like it's fixed there. When the user press x, the program will skip the story and move forward to the game.

    Sorry to ask help from you.
    Thank you.

  6. A bit of a c++ noob here. But where in your code would you actually put this. I am writing a satirical little program that needs this function installed but I am unsure as to where in the code it would be inserted. Any help would be appreciated. Thank you

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s