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