1 #include <string.h>
2 #include <ctype.h>
3 #include <math.h>
4 
5 #include "game/gui/text_render.h"
6 #include "video/video.h"
7 #include "utils/vector.h"
8 #include "utils/log.h"
9 
text_defaults(text_settings * settings)10 void text_defaults(text_settings *settings) {
11     memset(settings, 0, sizeof(text_settings));
12     settings->cforeground = color_create(0xFF,0xFF,0xFF,0xFF);
13     settings->opacity = 0xFF;
14 }
15 
text_render_char(const text_settings * settings,int x,int y,char ch)16 void text_render_char(const text_settings *settings, int x, int y, char ch) {
17     // Make sure code is valid
18     int code = ch - 32;
19     surface **sur = NULL;
20     if(code < 0) {
21         return;
22     }
23 
24     // Select font face surface
25     if(settings->font == FONT_BIG) {
26         sur = vector_get(&font_large.surfaces, code);
27     }
28     if(settings->font == FONT_SMALL) {
29         sur = vector_get(&font_small.surfaces, code);
30     }
31     if(sur == NULL || *sur == NULL) {
32         return;
33     }
34 
35     // Handle shadows if necessary
36     float of = settings->opacity / 255.0f;
37     if(settings->shadow & TEXT_SHADOW_RIGHT)
38         video_render_sprite_flip_scale_opacity_tint(
39             *sur, x+1, y, BLEND_ALPHA, 0, FLIP_NONE, 1.0f, of * 80, settings->cforeground
40         );
41     if(settings->shadow & TEXT_SHADOW_LEFT)
42         video_render_sprite_flip_scale_opacity_tint(
43             *sur, x-1, y, BLEND_ALPHA, 0, FLIP_NONE, 1.0f, of * 80, settings->cforeground
44         );
45     if(settings->shadow & TEXT_SHADOW_BOTTOM)
46         video_render_sprite_flip_scale_opacity_tint(
47             *sur, x, y+1, BLEND_ALPHA, 0, FLIP_NONE, 1.0f, of * 80, settings->cforeground
48         );
49     if(settings->shadow & TEXT_SHADOW_TOP)
50         video_render_sprite_flip_scale_opacity_tint(
51             *sur, x, y-1, BLEND_ALPHA, 0, FLIP_NONE, 1.0f, of * 80, settings->cforeground
52         );
53 
54     // Handle the font face itself
55     video_render_sprite_flip_scale_opacity_tint(
56         *sur, x, y, BLEND_ALPHA, 0, FLIP_NONE, 1, settings->opacity, settings->cforeground);
57 }
58 
text_find_max_strlen(int maxchars,const char * ptr)59 int text_find_max_strlen(int maxchars, const char *ptr) {
60     int i;
61     int len = strlen(ptr);
62 
63     // Skip whitespace at the start of the string
64     for(i = 0; i < len; i++) {
65         if(ptr[i] != ' ')
66             break;
67     }
68     if(i == len-1) {
69         return i;
70     }
71 
72     // Walk through the rest of the string
73     int last_space = i;
74     int max = maxchars + i;
75     int lstart = i;
76     for(; i < len; i++) {
77         // If we detect newline, this line ends here
78         if(ptr[i] == '\n')
79             return i+1;
80 
81         // If we are reading over our text limit ...
82         if(i >= max) {
83             if(ptr[i] == ' ') { // If we are at valid end character (space), end here
84                 return i;
85             } else if(last_space != lstart) {
86                 return last_space;
87             }
88         } else if(ptr[i] == ' ') {
89             last_space = i;
90         }
91     }
92 
93     return i;
94 }
95 
text_char_width(const text_settings * settings)96 int text_char_width(const text_settings *settings) {
97     return (settings->font == FONT_BIG) ? 8 : 6;
98 }
99 
text_find_line_count(text_direction dir,int cols,int rows,int len,const char * text)100 int text_find_line_count(text_direction dir, int cols, int rows, int len, const char *text) {
101     int ptr = 0;
102     int lines = 0;
103     while(ptr < len-1) {
104         // Find out how many characters for this row/col
105         int line_len;
106         if(dir == TEXT_HORIZONTAL)
107             line_len = text_find_max_strlen(cols, text + ptr);
108         else
109             line_len = text_find_max_strlen(rows, text + ptr);
110 
111         ptr += line_len;
112         lines++;
113     }
114     return lines;
115 }
116 
text_render(const text_settings * settings,int x,int y,int w,int h,const char * text)117 void text_render(const text_settings *settings, int x, int y, int w, int h, const char *text) {
118     int len = strlen(text);
119 
120     int size = text_char_width(settings);
121     int xspace = w - settings->padding.left - settings->padding.right;
122     int yspace = h - settings->padding.top - settings->padding.bottom;
123     int charw = size + settings->cspacing;
124     int charh = size + settings->lspacing;
125     int rows = (yspace + settings->lspacing) / charh;
126     int cols = (xspace + settings->cspacing) / charw;
127     int fit_lines = text_find_line_count(settings->direction, cols, rows, len, text);
128 
129     int start_x = x + settings->padding.left;
130     int start_y = y + settings->padding.top;
131     int tmp_s = 0;
132 
133     // Initial alignment for whole text block
134     switch(settings->direction) {
135         case TEXT_VERTICAL:
136             tmp_s = fit_lines * charw - settings->cspacing; // Total W minus last spacing
137             switch(settings->halign) {
138                 case TEXT_CENTER:
139                     start_x += ceil((xspace - tmp_s) / 2.0f);
140                     break;
141                 case TEXT_RIGHT:
142                     start_x += (xspace - tmp_s);
143                     break;
144                 default: break;
145             }
146             break;
147         case TEXT_HORIZONTAL:
148             tmp_s = fit_lines * charh - settings->lspacing; // Total H minus last spacing
149             switch(settings->valign) {
150                 case TEXT_MIDDLE:
151                     start_y += floor((yspace - tmp_s) / 2.0f);
152                     break;
153                 case TEXT_BOTTOM:
154                     start_y += (yspace - tmp_s);
155                     break;
156                 default: break;
157             }
158             break;
159     }
160 
161     int ptr = 0;
162     int line = 0;
163     while(ptr < len-1 && line < fit_lines) {
164         int line_len;
165         int real_len;
166         int mx = 0;
167         int my = 0;
168         int line_pw;
169         int line_ph;
170 
171         // Find out how many characters for this row/col
172         if(settings->direction == TEXT_HORIZONTAL)
173             line_len = text_find_max_strlen(cols, text + ptr);
174         else
175             line_len = text_find_max_strlen(rows, text + ptr);
176         real_len = line_len;
177 
178         // Skip spaces
179         int k = 0;
180         for(; k < line_len; k++) {
181             if(text[ptr+k] != ' ')
182                 break;
183             real_len--;
184         }
185 
186         // Find total size of this line and set newline start coords
187         switch(settings->direction) {
188             case TEXT_HORIZONTAL:
189                 line_pw = real_len * charw - settings->cspacing;
190                 my += charh * line;
191 
192                 // Horizontal alignment for this line
193                 switch(settings->halign) {
194                     case TEXT_CENTER:
195                         mx += floor((xspace - line_pw) / 2.0f);
196                         break;
197                     case TEXT_RIGHT:
198                         mx += (xspace - line_pw);
199                         break;
200                     default: break;
201                 }
202                 break;
203             case TEXT_VERTICAL:
204                 line_ph = real_len * charh - settings->lspacing;
205                 mx += charw * line;
206 
207                 // Vertical alignment for this line
208                 switch(settings->valign) {
209                     case TEXT_MIDDLE:
210                         my += ceil((yspace - line_ph) / 2.0f);
211                         break;
212                     case TEXT_BOTTOM:
213                         my += (yspace - line_ph);
214                         break;
215                     default: break;
216                 }
217                 break;
218         }
219 
220         // Render characters
221         for(; k < line_len; k++) {
222             // Skip line endings.
223             if(text[ptr+k] == '\n')
224                 continue;
225 
226             // Render character
227             text_render_char(settings, mx + start_x, my + start_y, text[ptr+k]);
228 
229             // Render to the right direction
230             if(settings->direction == TEXT_HORIZONTAL) {
231                 mx += charw;
232             } else {
233                 my += charh;
234             }
235         }
236 
237         ptr += line_len;
238         line++;
239     }
240 }
241 
242 /// ---------------- OLD RENDERER FUNCTIONS ---------------------
243 
font_render_char(const font * font,char ch,int x,int y,color c)244 void font_render_char(const font *font, char ch, int x, int y, color c) {
245     font_render_char_shadowed(font, ch, x, y, c, 0);
246 }
247 
font_render_char_shadowed(const font * font,char ch,int x,int y,color c,int shadow_flags)248 void font_render_char_shadowed(const font *font, char ch, int x, int y, color c, int shadow_flags) {
249     // Make sure code is valid
250     int code = ch - 32;
251     surface **sur = NULL;
252     if (code < 0) {
253         return;
254     }
255 
256     // Get font face
257     sur = vector_get(&font->surfaces, code);
258 
259     // Handle shadows if necessary
260     if(shadow_flags & TEXT_SHADOW_RIGHT)
261         video_render_sprite_flip_scale_opacity_tint(
262             *sur, x+1, y, BLEND_ALPHA, 0, FLIP_NONE, 1.0f, 80, c
263         );
264     if(shadow_flags & TEXT_SHADOW_LEFT)
265         video_render_sprite_flip_scale_opacity_tint(
266             *sur, x-1, y, BLEND_ALPHA, 0, FLIP_NONE, 1.0f, 80, c
267         );
268     if(shadow_flags & TEXT_SHADOW_BOTTOM)
269         video_render_sprite_flip_scale_opacity_tint(
270             *sur, x, y+1, BLEND_ALPHA, 0, FLIP_NONE, 1.0f, 80, c
271         );
272     if(shadow_flags & TEXT_SHADOW_TOP)
273         video_render_sprite_flip_scale_opacity_tint(
274             *sur, x, y-1, BLEND_ALPHA, 0, FLIP_NONE, 1.0f, 80, c
275         );
276 
277     // Handle the font face itself
278     video_render_sprite_tint(*sur, x, y, c, 0);
279 }
280 
font_render_len(const font * font,const char * text,int len,int x,int y,color c)281 void font_render_len(const font *font, const char *text, int len, int x, int y, color c) {
282     font_render_len_shadowed(font, text, len, x, y, c, 0);
283 }
284 
font_render_len_shadowed(const font * font,const char * text,int len,int x,int y,color c,int shadow_flags)285 void font_render_len_shadowed(const font *font, const char *text, int len, int x, int y, color c, int shadow_flags) {
286     int pos_x = x;
287     for(int i = 0; i < len; i++) {
288         font_render_char_shadowed(font, text[i], pos_x, y, c, shadow_flags);
289         pos_x += font->w;
290     }
291 }
292 
font_render(const font * font,const char * text,int x,int y,color c)293 void font_render(const font *font, const char *text, int x, int y, color c) {
294     int len = strlen(text);
295     font_render_len(font, text, len, x, y, c);
296 }
297 
font_render_shadowed(const font * font,const char * text,int x,int y,color c,int shadow_flags)298 void font_render_shadowed(const font *font, const char *text, int x, int y, color c, int shadow_flags) {
299     int len = strlen(text);
300     font_render_len_shadowed(font, text, len, x, y, c, shadow_flags);
301 }
302 
font_render_wrapped(const font * font,const char * text,int x,int y,int w,color c)303 void font_render_wrapped(const font *font, const char *text, int x, int y, int w, color c) {
304     font_render_wrapped_shadowed(font, text, x, y, w, c, 0);
305 }
306 
font_render_wrapped_internal(const font * font,const char * text,int x,int y,int max_w,color c,int shadow_flags,int only_size,int * out_w,int * out_h)307 void font_render_wrapped_internal(const font *font, const char *text, int x, int y, int max_w, color c, int shadow_flags, int only_size, int *out_w, int *out_h) {
308     int len = strlen(text);
309     int has_newline = 0;
310     for(int i = 0;i < len;i++) {
311         if(text[i] == '\n' || text[i] == '\r') {
312             has_newline = 1;
313             break;
314         }
315     }
316     if(!has_newline && font->w*len < max_w) {
317         // short enough text that we don't need to wrap
318         // render it centered, at least for now
319         if(!only_size) {
320             int xoff = (max_w - font->w*len)/2;
321             font_render_len_shadowed(font, text, len, x + xoff, y, c, shadow_flags);
322         }
323         *out_w = font->w*len;
324         *out_h = font->h;
325     } else {
326         // ok, we actually have to do some real work
327         // look ma, no mallocs!
328         const char *start = text;
329         const char *stop;
330         const char *end = &start[len];
331         const char *tmpstop;
332         int maxlen = max_w/font->w;
333         int yoff = 0;
334         int is_last_line = 0;
335 
336         *out_w = 0;
337         *out_h = 0;
338         while(start != end) {
339             stop = tmpstop = start;
340             while(1) {
341                 // rules:
342                 // 1. split lines by whitespaces
343                 // 2. pack as many words as possible into a line
344                 // 3. a line must be no more than maxlen long
345                 if(*stop == 0) {
346                     // hit the end
347                     if(stop - start > maxlen) {
348                         // the current line exceeds max len
349                         if(tmpstop - start > maxlen) {
350                             // this line cannot not be word-wrapped because it contains a word that exceeds maxlen, we'll let it pass
351                             stop--;
352                             is_last_line = 1;
353                         } else {
354                             // this line can be word-wrapped, go back to previous saved location
355                             stop = tmpstop;
356                         }
357                     } else {
358                         stop--;
359                         is_last_line = 1;
360                     }
361                     break;
362                 }
363                 if(*stop == '\n' || *stop == '\r') {
364                     if(stop - start > maxlen) {
365                         stop = tmpstop;
366                     }
367                     break;
368                 }
369                 if(isspace(*stop)) {
370                     if(stop - start > maxlen) {
371                         stop = tmpstop;
372                         break;
373                     } else {
374                         tmpstop = stop;
375                     }
376                 }
377                 stop++;
378             }
379             int linelen = stop - start;
380             if(shadow_flags & TEXT_SHADOW_TOP) {
381                 yoff++;
382             }
383             if(!only_size) {
384                 int xoff = (max_w - font->w*linelen)/2;
385                 font_render_len_shadowed(font, start, linelen + (is_last_line?1:0), x + xoff, y + yoff, c, shadow_flags);
386             }
387             if(*out_w < linelen*font->w) {
388                 *out_w = linelen*font->w;
389             }
390             yoff += font->h;
391             if(shadow_flags & TEXT_SHADOW_BOTTOM) {
392                 yoff++;
393             }
394             *out_h = yoff;
395             start = stop+1;
396         }
397     }
398 }
399 
font_render_wrapped_shadowed(const font * font,const char * text,int x,int y,int w,color c,int shadow_flags)400 void font_render_wrapped_shadowed(const font *font, const char *text, int x, int y, int w, color c, int shadow_flags) {
401     int tmp;
402     font_render_wrapped_internal(font, text, x, y, w, c, shadow_flags, 0, &tmp, &tmp);
403 }
404 
font_get_wrapped_size(const font * font,const char * text,int max_w,int * out_w,int * out_h)405 void font_get_wrapped_size(const font *font, const char *text, int max_w, int *out_w, int *out_h) {
406     static color c = {0};
407     font_render_wrapped_internal(font, text, 0, 0, max_w, c, 0, 1, out_w, out_h);
408 }
409 
font_get_wrapped_size_shadowed(const font * font,const char * text,int max_w,int shadow_flag,int * out_w,int * out_h)410 void font_get_wrapped_size_shadowed(const font *font, const char *text, int max_w, int shadow_flag, int *out_w, int *out_h) {
411     static color c = {0};
412     font_render_wrapped_internal(font, text, 0, 0, max_w, c, shadow_flag, 1, out_w, out_h);
413 }
414