Allegro.cc - Online Community

Allegro.cc Forums » Programming Questions » [A5] New Line

This thread is locked; no one can reply to it. rss feed Print
[A5] New Line
tarlkea
Member #13,014
July 2011

When I used al_draw_text, the text gives me squares or nothing when i use the \n character in the char * argument. Is there a way to get it to do line breaks without modifying the text string?

Thomas Fjellstrom
Member #476
June 2000
avatar

Sadly no. It does not do wrapping or support newlines. You will have to parse out each line and display it, potentially doing additional word wrapping if needed.

--
Thomas Fjellstrom - [website] - [email] - [Allegro Wiki] - [Allegro TODO]
"If you can't think of a better solution, don't try to make a better solution." -- weapon_S
"The less evidence we have for what we believe is certain, the more violently we defend beliefs against those who don't agree" -- https://twitter.com/neiltyson/status/592870205409353730

beoran
Member #12,636
March 2011

Since everyone and their dog is forced to implement this themselves with the current API, which is a waste of effort, I think an al_draw_multiline_text function would be a "nice to have" feature for 5.2 or later. Ideally this would also inclue a way to limit the width of the text and allow for text blocks. So I added this functionality as "nice to have" to the roadmap on the wiki http://wiki.allegro.cc/index.php?title=Allegro_roadmap.

pkrcel
Member #14,001
February 2012

beoran said:

Since everyone and their dog is forced to implement this themselves

I do not have a dog and I am not programming a GUI :(

It is unlikely that Google shares your distaste for capitalism. - Derezo
If one had the eternity of time, one would do things later. - Johan Halmén

Thomas Fjellstrom
Member #476
June 2000
avatar

pkrcel said:

I do not have a dog and I am not programming a GUI :(

You should think about getting a dog.

--
Thomas Fjellstrom - [website] - [email] - [Allegro Wiki] - [Allegro TODO]
"If you can't think of a better solution, don't try to make a better solution." -- weapon_S
"The less evidence we have for what we believe is certain, the more violently we defend beliefs against those who don't agree" -- https://twitter.com/neiltyson/status/592870205409353730

Edgar Reynaldo
Member #8,592
May 2007
avatar

pkrcel
Member #14,001
February 2012

AH! ;D I know you guys were over your heads but really hoped we could save you.

It is unlikely that Google shares your distaste for capitalism. - Derezo
If one had the eternity of time, one would do things later. - Johan Halmén

Arthur Kalliokoski
Second in Command
February 2005
avatar

pkrcel said:

I know you guys were over your heads

video

#SelectExpand
1//note: if you reuse a particular wordwrap struct, you have to free frame->wrapbox.bufferarray to avoid a memory leak, 2//and strictly speaking to free it before program exit. 3static int _zz_wordwrap(char *src, ALLEGRO_FONT *font, int pixelwidth, ZZ_WRAPBOX *wrapbox) 4{ 5 char *current_line = src; //allow zero based indices within current line 6 char *current_dst; 7 char *dst_resize; 8 char *wrapbuff; //temp store for current line 9 int dst_current_char = 0; //index of current line in dest 10 int src_prev_space = 0; //marks last space in source (if we have to ignore part of a word at end of line) 11 int dst_prev_space = 0; //marks last space in dest 12 int src_current_char = 0; //how far within the current string we are 13 int dst_lines_used = 0; //check that we don't overrun the destination buffer 14 int num_dst_lines; //how many lines in dst available (chars_width * num_dest_lines = number of bytes in dst buffer) 15 int num_src_bytes; 16 char c; 17 18 memset(wrapbox, 0, sizeof(ZZ_WRAPBOX)); 19 wrapbox->textfont = font; 20 wrapbox->fontheight = al_get_font_line_height(font); 21 wrapbox->pixelwidth = pixelwidth; 22 23 wrapbox->chars_width = al_get_text_width(font, ten_narrowchars); 24 wrapbox->chars_width = (float)(10.0 / ((float)wrapbox->chars_width / (float)pixelwidth)); 25 26 wrapbuff = (char *)malloc(wrapbox->chars_width + 1); 27 if(!wrapbuff) 28 { 29 return ZZ_WRAP_NOMEM; 30 } 31 32 //valgrind complains about conditional jump on uninitialized values if this isn't here, remove if you like 33 memset(wrapbuff,0,wrapbox->chars_width + 1); 34 35 //allocating src_bytes worth of dst plus a WAG of 20% for slop 36 num_src_bytes = strlen(src); 37 num_dst_lines = num_src_bytes / wrapbox->chars_width; //first approximation to how many lines 38 num_dst_lines = (float)num_dst_lines * 1.2; //WAG of 20% guessing most lines waste that much on the end 39 num_dst_lines += 10; //for those one-liners ;) 40 current_dst = (char *)malloc(num_dst_lines * wrapbox->chars_width); 41 if(!current_dst) 42 { 43 return ZZ_WRAP_NOMEM; 44 } 45 46 memset(current_dst, 0, num_dst_lines * wrapbox->chars_width); //zero them all out for asciiz terminators 47 48 while(1) 49 { 50 c = current_line[src_current_char]; //grab next char from source 51 52 if(c == 0) //null terminator? done 53 { 54 strcpy(&current_dst[dst_lines_used * wrapbox->chars_width], wrapbuff); //copy temp to dest buffer 55 free(wrapbuff); 56 wrapbox->numdestlines = dst_lines_used+1; //tell caller how many lines of wrapped text there are 57 58 wrapbox->bufferarray = current_dst; 59 return 0; 60 } 61 62 if(c == '\n') //start a new line in dest buffer regardless of room remaining in this line 63 { 64 wrapbox->linetoolong++; 65 strcpy(&current_dst[dst_lines_used * wrapbox->chars_width], wrapbuff); //copy temp to dest buffer 66 memset(wrapbuff, 0, wrapbox->chars_width + 1); //zero out temp for asciiz terminators 67 dst_lines_used++; 68 if(dst_lines_used == num_dst_lines) //need destination buffer enlarged? 69 { 70 /* trying out Peter Wang suggestion of doubling size every realloc() 71 static int lcount = 1; 72 printf("doubling line count %d times\n",lcount); 73 lcount++; 74 */ 75 num_dst_lines *= 2; //+= 50; //add 50 more lines available 76 dst_resize = (char *)realloc(current_dst, num_dst_lines * wrapbox->chars_width); 77 if(!dst_resize) 78 { 79 free(wrapbuff); 80 free(current_dst); 81 return ZZ_WRAP_NOMEM; 82 } 83 current_dst = dst_resize; 84 } 85 current_line = &current_line[src_current_char + 1]; //+1 to get past the '\n' 86 src_current_char = -1; 87 src_prev_space = 0; 88 dst_prev_space = 0; 89 dst_current_char = 0; 90 } 91 92 if(c < ' ') //ignore backspaces, tabs, '\r's, control chars 93 { 94 src_current_char++; //get past the '\r' or whatever 95 continue; //since c is signed, all the "high" non-ASCII chars will be less than ' ' too 96 } 97 98 if(c == ' ') //keep track of possible line end (wrapbuff full part way through a word) 99 { 100 src_prev_space = src_current_char + 1; //+1 to ignore it next line 101 dst_prev_space = dst_current_char; 102 } 103 104 //fall through to storing in wrapbuff 105 if( (wrapbox->chars_width == (dst_current_char + 1)) || (wrapbox->pixelwidth < al_get_text_width(font, wrapbuff))) //run out of room in this line? 106 { 107 if(dst_prev_space == 0) //we found one single word that's too long for a line 108 { 109 free(wrapbuff); 110 free(current_dst); 111 return ZZ_WRAP_TOOLONG; 112 } 113 114 if(c != ' ') //part way through a word? 115 { 116 dst_current_char = dst_prev_space; 117 src_current_char = src_prev_space; 118 while(wrapbuff[dst_current_char] == ' ') 119 { 120 wrapbuff[dst_current_char] = 0; 121 dst_current_char--; 122 if(dst_current_char < 0) 123 { 124 break; 125 } 126 } 127 } 128 129 strcpy(&current_dst[dst_lines_used * wrapbox->chars_width], wrapbuff); //copy temp to dest buffer 130 memset(wrapbuff, 0, wrapbox->chars_width + 1); //zero out temp for asciiz terminators 131 dst_lines_used++; 132 if(dst_lines_used == num_dst_lines) //need destination buffer enlarged? 133 { 134 num_dst_lines += 50; //add 50 more lines available 135 dst_resize = (char *)realloc(current_dst, num_dst_lines * wrapbox->chars_width); 136 if(!dst_resize) 137 { 138 free(wrapbuff); 139 free(current_dst); 140 return ZZ_WRAP_NOMEM; 141 } 142 current_dst = dst_resize; 143 } 144 current_line = &current_line[src_prev_space]; 145 src_current_char = 0; 146 dst_prev_space = 0; 147 dst_current_char = 0; 148 continue; 149 } 150 wrapbuff[dst_current_char] = c; 151 src_current_char++; 152 dst_current_char++; 153 } 154 return 0; 155} 156 157static void _zz_init_frame(int left, int top, int right, int bottom, 158 char *titletext, ALLEGRO_FONT *titlefont, 159 ZZ_TEXTBOX *frame, ZZ_WIN *window) 160{ 161 int height, width; 162 frame->framerect.left = left; 163 frame->framerect.top = top; 164 frame->framerect.right = right; 165 166 frame->textbox.left = frame->framerect.left + TEXTBOXMARGIN; 167 frame->textbox.top = frame->framerect.top + TEXTBOXMARGIN; 168 frame->textbox.right = frame->framerect.right - TEXTBOXMARGIN; 169 170 frame->titletext = titletext; 171 frame->titlefont = titlefont; 172 frame->window = window; 173 174 height = al_get_font_line_height(titlefont); 175 width = al_get_text_width(titlefont, titletext); 176 177 frame->titlebox.top = frame->framerect.top - (height >> 1); 178 frame->titlebox.bottom = frame->titlebox.top + height; 179 frame->titlebox.left = frame->framerect.left + 10; 180 frame->titlebox.right = frame->titlebox.left + width + 10; 181 182 //leave zero in the "bottom" parameter to have bottom automagically 183 //align with bottom of text 184 if(bottom != 0) 185 { 186 frame->framerect.bottom = bottom; 187 frame->textbox.bottom = frame->framerect.bottom - TEXTBOXMARGIN; 188 } 189 else 190 { 191 frame->textbox.bottom = frame->titlebox.bottom + (frame->wrapbox.numdestlines * frame->wrapbox.fontheight); 192 frame->framerect.bottom = frame->textbox.bottom + TEXTBOXMARGIN; 193 } 194} 195 196int zz_init_textbox(ZZ_TEXTBOX *textbox, //textbox struct location 197 ZZ_WIN *parentwin, //parent window struct location 198 ALLEGRO_FONT *titlefont, //font for title (may be 0 if no title desired) 199 ALLEGRO_FONT *bodyfont, //font for body of text (may be 0 if no body desired) 200 char *titletext, //pointer to text for title (may be 0 if no title desired) 201 char *body_text_src, //pointer to original cstring for body of text (may be 0 if no body desired) 202 int left, //left edge on parent window 203 int top, //top edge on parent window 204 int right, //right edge on parent window 205 int bottom, //bottom edge on parent window (may be 0 if you want bottom set to end of body of text) 206 int gray) 207{ 208 int err; 209 210 err = _zz_wordwrap(body_text_src, bodyfont, right-left-TEXTBOXMARGIN*2, &textbox->wrapbox); 211 if(err) 212 { 213 return err; 214 } 215 216 _zz_init_frame(left, top, right, bottom, //was _zz_init_textbox(?) 217 titletext, titlefont, 218 textbox, parentwin); 219 textbox->gray = gray; 220 return 0; 221} 222 223void zz_draw_textbox(ZZ_TEXTBOX *frame) 224{ 225 int x; 226 int y; 227 int width; 228 int height; 229 230 int row; 231 int limit; 232 int indx; 233 int i; 234 int gray; 235 236 if(frame->gray == 0) //force default 237 gray = UNSELECTED; //force washed out appearance 238 else 239 gray = frame->window->grayout; 240 241 x = frame->framerect.left + frame->window->rect.left; 242 y = frame->framerect.top + frame->window->rect.top; 243 width = frame->framerect.right - frame->framerect.left; 244 height = frame->framerect.bottom - frame->framerect.top; 245 246 //draw outline 247 248 //top line (tapered at the ends) 249 al_draw_line(x - 0.5, 250 y - 0.5, 251 x + width + 0.5, 252 y - 0.5, 253 zz_gui_colors[gray][DIMGRAY], 1.0); 254 255 al_draw_line(x + 0.5, 256 y + 0.5, 257 x + width - 0.5, 258 y + 0.5, 259 zz_gui_colors[gray][BRIGHTGRAY], 1.0); 260 261 //bottom line 262 al_draw_line(x + 0.5, 263 height + y - 0.5, 264 x + width - 0.5, 265 height + y - 0.5, 266 zz_gui_colors[gray][DIMGRAY], 1.0); 267 268 al_draw_line(x - 0.5, 269 height + y + 0.5, 270 x + width + 0.5, 271 height + y + 0.5, 272 zz_gui_colors[gray][BRIGHTGRAY], 1.0); 273 274 //left line 275 al_draw_line(x - 0.5, 276 y - 0.5, 277 x - 0.5, 278 y + height + 0.5, 279 zz_gui_colors[gray][DARKGRAY], 1.0); 280 281 al_draw_line(x + 0.5, 282 y - 0.5, 283 x + 0.5, 284 y + height + 0.5, 285 zz_gui_colors[gray][LIGHTGRAY], 1.0); 286 287 //right line 288 al_draw_line(x + width - 0.5, 289 y + 0.5, 290 x + width - 0.5, 291 height + y - 0.5, 292 zz_gui_colors[gray][DARKGRAY], 1.0); 293 294 al_draw_line(x + width + 0.5, 295 y - 0.5, 296 x + width + 0.5, 297 height + y + 0.5, 298 zz_gui_colors[gray][LIGHTGRAY], 1.0); 299 300 if(frame->titletext) 301 { 302 al_draw_filled_rectangle(frame->window->rect.left + frame->titlebox.left, 303 frame->window->rect.top + frame->titlebox.top, 304 frame->window->rect.left + frame->titlebox.right, 305 frame->window->rect.top + frame->titlebox.bottom, 306 zz_gui_colors[gray][GRAY]); 307 308 al_draw_text(frame->titlefont, zz_gui_colors[gray][BLACK], (float) frame->titlebox.left + frame->window->rect.left + 5, 309 (float) frame->titlebox.top + frame->window->rect.top, 0, frame->titletext); 310 } 311 312 if(frame->wrapbox.bufferarray) 313 { 314 row = frame->window->rect.top + frame->textbox.top; 315 width = frame->wrapbox.chars_width; 316 limit = frame->wrapbox.numdestlines; 317 318 for(indx = 0, i = 0; i < limit;indx+=width,i++) 319 { 320 al_draw_text(frame->wrapbox.textfont, 321 zz_gui_colors[gray][BLACK], 322 frame->window->rect.left + frame->textbox.left, 323 row, 324 0, 325 &frame->wrapbox.bufferarray[indx]); 326 row+=frame->wrapbox.fontheight; 327 } 328 } 329} 330 331int zz_init_scrolltext( ZZ_SCROLLTEXT *textbox, //textbox struct location 332 ZZ_WIN *parentwin, //parent window struct location 333 ALLEGRO_FONT *scrollfont, //font for body of text (may be 0 if no body desired) 334 char *body_text_src, //pointer to original cstring for body of text (may be 0 if no body desired) 335 int left, //left edge on parent window 336 int top, //top edge on parent window 337 int right, //right edge on parent window 338 int bottom, //bottom edge on parent window (will be rounded from 0.5 lines of text to provide vertical alignment) 339 int scrollwidth, //how wide the strings can be in pixels (horizontal scroll necessary if wider than right-left) 340 int gray) 341{ 342 int err; 343 float rnder; 344 err = _zz_wordwrap(body_text_src, scrollfont, scrollwidth, &textbox->wrapbox); 345 if(err) 346 { 347 return err; 348 } 349 textbox->parent = parentwin; 350 textbox->boxwidth = right-left; 351 textbox->scrollrect.parent = parentwin; 352 textbox->scrollrect.rect.left = left; 353 textbox->scrollrect.rect.top = top; 354 textbox->scrollrect.rect.right = right; 355 rnder = bottom - top; 356 rnder /= textbox->wrapbox.fontheight; 357 textbox->scrollrect.rect.bottom = rnder; 358 textbox->scrollrect.rect.bottom *= textbox->wrapbox.fontheight; 359 textbox->gray = gray; 360 err = (textbox->scrollrect.rect.bottom - top)/textbox->wrapbox.fontheight; 361 zz_init_scrollbar((textbox->scrollrect.rect.bottom - top)/textbox->wrapbox.fontheight,textbox->wrapbox.numdestlines,ZZ_VERTICAL,&textbox->vscroll,&textbox->scrollrect); 362 return 0; 363} 364 365void zz_do_scrolltext(ZZ_SCROLLTEXT *textbox) 366{ 367 int top; 368 int len; 369 int i; 370 int line; 371 int hgt; 372 zz_do_scrollbar(&textbox->vscroll); 373 top = textbox->vscroll.topline; 374 len = textbox->vscroll.items_in_window + top; 375 hgt = textbox->wrapbox.fontheight; 376 line = textbox->scrollrect.rect.top; 377 al_draw_filled_rectangle(textbox->scrollrect.rect.left,textbox->scrollrect.rect.top,textbox->scrollrect.rect.right,textbox->scrollrect.rect.bottom,zz_gui_colors[SELECTED][WHITE]); 378 zz_bevel(textbox->scrollrect.rect.left, 379 textbox->scrollrect.rect.top, 380 textbox->scrollrect.rect.right, 381 textbox->scrollrect.rect.bottom, 382 1, 383 textbox->parent, 384 1); 385 for(i=top;i<len;i++) 386 { 387 al_draw_text(textbox->wrapbox.textfont,zz_gui_colors[SELECTED][BLACK], textbox->scrollrect.rect.left, line, 0,&textbox->wrapbox.bufferarray[i * textbox->wrapbox.chars_width]); 388 line += hgt; 389 } 390}

“Throughout history, poverty is the normal condition of man. Advances which permit this norm to be exceeded — here and there, now and then — are the work of an extremely small minority, frequently despised, often condemned, and almost always opposed by all right-thinking people. Whenever this tiny minority is kept from creating, or (as sometimes happens) is driven out of a society, the people then slip back into abject poverty. This is known as "bad luck.”

― Robert A. Heinlein

Felix-The-Ghost
Member #9,729
April 2008
avatar

I would like that feature to be vanilla as well, I didn't know we couldn't do it in A5.

==========================
<--- The ghost with the most!
---------------------------
[Website] [Youtube]

jmasterx
Member #11,410
October 2009

Here is the Agui class that does resizable text. It includes:
-WordWrap (greedy algorithm)
-Break on \n
-Single line mode (in this mode, it will ensure the line does not exceed the maxWidth and append a nice ellipsis right before it does if you desire)
-It can paint the lines of text left aligned, center aligned, or right aligned.

I use this for Agui's Label class (and buttons and other widgets too). The Label was intended to be as feature-rich as the Label in .NET.

https://github.com/jmasterx/Agui/blob/master/include/Agui/ResizableText.hpp
https://github.com/jmasterx/Agui/blob/master/src/Agui/ResizableText.cpp

SiegeLord
Member #7,827
October 2006
avatar

My opinion on this (and the HTML parser in the other thread) is that it's hard to get this routine to have just enough features for people to actually be able to use it while at the same time not being out of place for a mid/low-level library that Allegro is trying to be.

If this was really desired, I'd rather add a al_draw_tokens(..., void (*cb)(*out_x, *out_y, *out_width, *out_color, ...) which would tokenize the text and call the callback in order to do the line switching etc. Of course it still wouldn't do the multi-line drawing out of the box but it'd be simpler to implement.

Either way, this seems like a good topic for a wiki article or something so people can just be pointed to it.

"For in much wisdom is much grief: and he that increases knowledge increases sorrow."-Ecclesiastes 1:18
[SiegeLord's Abode][Codes]:[DAllegro5]:[RustAllegro]

beoran
Member #12,636
March 2011

Actually, a low level function that could be useful for composing higher level ones in a layout library would be

void al_draw_character(const ALLEGRO_FONT *font,
   ALLEGRO_COLOR color, float x, float y, int character)

to draw a single character / unicode code point. This function would arguably be faster when doing word by word or letter by letter text output (as in some RPG's ,etc), than al_draw_ustr, etc. It would also enable, for example, alternative layouts such as lright to left or top to down writing, by allowing us to implement our own output functions for those.

Edgar Reynaldo
Member #8,592
May 2007
avatar

beoran
Member #12,636
March 2011

true, though arguably one should be able to query that as well somehow...

Edgar Reynaldo
Member #8,592
May 2007
avatar

beoran
Member #12,636
March 2011

Yeah. It could return 0 in case of no kerning and negative in case of kerning. Also a al_get_character_width(const ALLEGRO_FONT* font , int codepoint); should be added then to complete the API (we can already get height, etc ccrom font-specific functions).

Cassio Renan
Member #14,189
April 2012
avatar

What I did was create a "text" structure witch will have word, font and spacing info for regular and justified drawing. That way it can be built and stored instead of a string, for (presumably)faster drawing. It doesn't support \n's yet, but I'll work on it.

Full code, for anyone interested.

#SelectExpand
1struct word{ 2 std::string value; 3 int width; 4}; 5 6struct line{ 7 std::vector<word> words; 8 int size; 9 float jSpaceSize; 10}; 11 12struct text{ 13 std::vector<line> lines; 14 float spaceSize; 15 int maxSize; 16 int height; 17 ALLEGRO_FONT *font; 18}; 19 20void drawtext(text t, float x, float y, ALLEGRO_COLOR color, bool justified) 21{ 22 std::vector<word>::iterator it; 23 for(int i = 0;i < t.lines.size();i++){ 24 float curx = x; 25 for(it = t.lines[i].words.begin();it!=t.lines[i].words.end();it++){ 26 al_draw_text(t.font, color, curx, y + t.height*i,0,it->value.c_str()); 27 curx += it->width; 28 curx += justified?t.lines[i].jSpaceSize:t.spaceSize; 29 } 30 } 31} 32 33 34text buildText(const string &s, ALLEGRO_FONT* font, int maxSize, int maxLines) 35{ 36 std::stringstream ss(s); 37 text result; 38 result.lines.clear(); 39 40 line curline; 41 curline.words.clear(); 42 curline.size = 0; 43 44 word curword; 45 bool firstword = true; 46 47 result.spaceSize = al_get_text_width(font, " "); 48 result.maxSize = maxSize; 49 result.font = font; 50 result.height = al_get_font_line_height(font); 51 52 while(!ss.eof()){ 53 ss >> curword.value; 54 curword.width = al_get_text_width(font,curword.value.c_str()); 55 if(firstword){ 56 curline.words.push_back(curword); 57 curline.size += curword.width; 58 firstword = false; 59 } 60 else if(curline.size + curword.width + result.spaceSize < maxSize){ 61 curline.words.push_back(curword); 62 curline.size += curword.width + result.spaceSize; 63 } 64 else{ 65 curline.jSpaceSize = 0; 66 for(std::vector<word>::iterator it = curline.words.begin();it!= curline.words.end();it++){ 67 curline.jSpaceSize += it->width; 68 } 69 curline.jSpaceSize = maxSize - curline.jSpaceSize; 70 curline.jSpaceSize /= curline.words.size() - 1; 71 72 result.lines.push_back(curline); 73 curline.size = 0; 74 curline.words.clear(); 75 curline.words.push_back(curword); 76 curline.size += curword.width; 77 } 78 } 79 if(curline.size){ 80 result.lines.push_back(curline); 81 } 82 while(result.lines.size() > maxLines){ 83 result.lines.pop_back(); 84 } 85 return result; 86}

Go to: