Layout of text - wordwrap

Started by
4 comments, last by NotAYakk 17 years, 11 months ago
Hmm, I am having a bit of trouble figuring out how I can layout my text in a neat box for display. Say I have a box to display the text in, and it is 50 pixels high by 200 wide, or something simular. If I input a string and it is too long to fit in box, I want it to continue on a new line. Wordwrapping I guess I mean. So far I have:
void TALK(std::string MyWords)
{
    std::string MyWords1, MyWords2, MyWords3;
    if(MyWords.size > 80)
    {
	    int SIZE = MyWords.size;
	    MyWords1.assign(MyWords, 80, 80);
        MyWords.erase(80, 80);
        Line1 = TTF_RenderText_Solid(FONT, MyWords1.c_str(), WhiteColor);
        if(SIZE > 160)
        {
             MyWords2.assign(MyWords, 80, 80);
             MyWords.erase(80, 80);
             Line2 = TTF_RenderText_Solid(FONT, MyWords2.c_str(), WhiteColor);
             if(SIZE > 240)
             {
                  MyWords3.assign(MyWords, 80, 80);
                  MyWords.erase(80, 80);
                  Line3 = TTF_RenderText_Solid(FONT, MyWords3.c_str(), WhiteColor);
             }
        }
    }
I will take the 'LineX's and combine them into a single texture 500 * 200 pixels, but my current problem is how would I break off my strings at an ideal spot so as to not cut words in half? If at the 78th letter of my string is the word 'Servant', I would get:
......................Se
rvant...................
Which I don't want. I would rather the whole word drop to the next line, and so no string is bigger than 80 charactors. Can someone point me in the right direction?
Advertisement
If you're doing word-wrapping manually, it would seem you just need to process it one word at a time (check if the next word will fit on the current line, and move to next line if not). Just don't forget to check the case where the word is LONGER than the line, in which case you will have to break it no matter what.
Pseudo-code for single-direction, untabbed, simply-aligned (no mixing arabic and english!), static (non-interactive) word wrap:

0> Start with a queue of characters and a queue of lines to place them on and a temp_line_buffer.
1> Get the first line. How long is it?
2> Examine the queue of characters until you hit a word boundry, or enough characters to overfill the first line.
3a> If there is no word boundry before the first line overfills, run "shove_characters_onto_line", and start the algorithm over on the next line with the resulting queue of characters.
4b> If you reach a word boundry, you can fit at least one word on the line. Place the first word into the temp_line_buffer.
5> Repeat:
6> Find the next word boundry.
7> Is there room for the word and leading whitespace on the line?
7a> Yes: Shove it into the temp_line_buffer, goto 5b>
7b> No room for the next word. Goto 8.
8> Insert the "temp word buffer" onto the line, possibly with justification.
9> Consume any "whitespace" off the front of the queue of characters.
10> Proceed to the next line, restarting algorithm with the current line and character queue.

"shove_characters_onto_line":
1> Shove all the characters that can fit onto the line. Possibly "squish" them if you want to.

Thanks guys. I'm still at a bit of a loss, but I think I have more of an idea, and will post back when I get it more into shape.
Well, it works now. Any suggestions to help me speed it up, or get it to work better? I will be optimizing it abit, for my game project, and could use the input on the code.

Here is the code for other's use if they want to look it over. Hope it helps someone else.
#include <iostream>using namespace std;string MyLine[20];short int LNum = 0;bool TALK(string MyWords){    bool stop = 0;    bool Quit = false;    int Break, SIZE;    while(Quit != true)    {       if(MyWords.size() >= 80)       {	       SIZE = MyWords.size();	       Break = 80;	       if(MyWords.at(80) == ' ')           {	            stop = 1;           }           else           {                while(!stop)                {                     Break--;                     if(MyWords.at(Break) == ' ')                     {                          stop = 1;                     }                }            }	        MyLine[LNum].assign(MyWords, 1, Break);            MyWords.erase(0, Break);            LNum++;            stop = 0;            if(MyWords.empty()){Quit = true;}        }        else        {            Break = MyWords.size();            MyLine[LNum].assign(MyWords, 1, Break);            MyWords.erase(0, Break);            LNum++;        }        if(MyWords.empty()){Quit = true;}    }    return 0;}int main(){    TALK("_One two three four five six seven eight nine ten eleven twelve thirteen fourteen fifteen sixteen seventeen eighteen nineteen twenty twenty-one twenty-two twenty-three twenty-four twenty-five twenty-six twenty-seven twenty-eight twenty-nine thirty thirty-one thirty-two thirty-three thirty-four thirty-five thirty-six thirty-seven thirty-eight thirty-nine fourty fourty-one fourty-two fourty-three fourty-four fourty-five fourty-six fourty-seven fourty-eight fourty-nine fifty fifty-one fifty-two fifty-three fifty-four fifty-five fifty-six fifty-seven fifty-eight fifty-nine sixty sixty-one sixty-two sixty-three sixty-four sixty-five sixty-six sixty-seven sixty-eight sixty-nine seventy.");        int i = LNum;    LNum = 0;    while(i != 0)    {        cout << MyLine[LNum] << endl;        LNum++;        i--;    }    system("pause");    return 0;}
1> Merge the "if at 80, there is a space" with "the last space before 80", just to clean up the code.

2> Keep three indexes into the source-string, and don't erase characters from it.
"Low water", "line break" and "high water".

"Low water" is the index for which all characters before it have been copied to a line.

You then work out where "high water" and "line break" is.[foootnote a]

The range "low water" to "line break" is then copied into the line buffer. Then assign "low water" to be equal to "high water" and continue until the entire source string is consumed.

Footnote a:
To work out where "high water" and "line break" is:
This algorithm does not modify "low water".

Start out with all three "low water", "high water" and "line break" equal to each other.

Keep track of a boolean "in word", which starts out equal to true.

Iterate "high water" forward. On each step (including the first:)

If you are "in word" and you see a space, set "line break" equal to your current position, and set "in word" to be false.

If you are not "in word" and you see a non-space, set "in word" to true.

If ("high water"-"low water") is equal to the length of the line, examine "line break". If "line break" does not equal "low water", advance "high water" until the end of the source string, or "in word" becomes true -- and then you are done (you want to eat trailing whitespace). If "line break" equals "low water", set "line break" to "high water", and you are done (if there is no line break on the line, simply cut the line. Optionally, you might want to add a "-" or something to the end of the line).

And please, for the love of pete, don't write your code with hard-coded values like "80" in it. Take line-limits as a parameter -- or if you must, have a const int max_line_length=80.

HTH.

This topic is closed to new replies.

Advertisement