1 #include "text.h"
2
3 #include "core/config.h"
4 #include "core/lang.h"
5 #include "core/locale.h"
6 #include "core/string.h"
7 #include "core/time.h"
8 #include "graphics/graphics.h"
9 #include "graphics/image.h"
10
11 #include <string.h>
12
13 #define ELLIPSIS_LENGTH 4
14 #define NUMBER_BUFFER_LENGTH 100
15
16 static uint8_t tmp_line[200];
17
18 static struct {
19 int capture;
20 int seen;
21 int position;
22 int cursor_position;
23 int width;
24 int visible;
25 time_millis updated;
26 int x_offset;
27 int y_offset;
28 int text_offset_start;
29 int text_offset_end;
30 } input_cursor;
31
32 static struct {
33 const uint8_t string[ELLIPSIS_LENGTH];
34 int width[FONT_TYPES_MAX];
35 } ellipsis = { {'.', '.', '.', 0} };
36
get_ellipsis_width(font_t font)37 static int get_ellipsis_width(font_t font)
38 {
39 if (!ellipsis.width[font]) {
40 ellipsis.width[font] = text_get_width(ellipsis.string, font);
41 }
42 return ellipsis.width[font];
43 }
44
text_capture_cursor(int cursor_position,int offset_start,int offset_end)45 void text_capture_cursor(int cursor_position, int offset_start, int offset_end)
46 {
47 input_cursor.capture = 1;
48 input_cursor.seen = 0;
49 input_cursor.position = 0;
50 input_cursor.width = 0;
51 input_cursor.cursor_position = cursor_position;
52 input_cursor.text_offset_start = offset_start;
53 input_cursor.text_offset_end = offset_end;
54 }
55
text_draw_cursor(int x_offset,int y_offset,int is_insert)56 void text_draw_cursor(int x_offset, int y_offset, int is_insert)
57 {
58 if (!input_cursor.capture) {
59 return;
60 }
61 input_cursor.capture = 0;
62 time_millis curr = time_get_millis();
63 time_millis diff = curr - input_cursor.updated;
64 if (!input_cursor.visible && diff >= 200) {
65 input_cursor.visible = 1;
66 input_cursor.updated = curr;
67 } else if (input_cursor.visible && diff >= 400) {
68 input_cursor.visible = 0;
69 input_cursor.updated = curr;
70 }
71 if (input_cursor.visible) {
72 if (is_insert) {
73 graphics_draw_horizontal_line(
74 x_offset + input_cursor.x_offset - 3, x_offset + input_cursor.x_offset + 1,
75 y_offset + input_cursor.y_offset - 3, COLOR_WHITE);
76 graphics_draw_vertical_line(
77 x_offset + input_cursor.x_offset - 1, y_offset + input_cursor.y_offset - 3,
78 y_offset + input_cursor.y_offset + 13, COLOR_WHITE);
79 graphics_draw_horizontal_line(
80 x_offset + input_cursor.x_offset - 3, x_offset + input_cursor.x_offset + 1,
81 y_offset + input_cursor.y_offset + 14, COLOR_WHITE);
82 } else {
83 graphics_fill_rect(
84 x_offset + input_cursor.x_offset, y_offset + input_cursor.y_offset + 14,
85 input_cursor.width, 2, COLOR_WHITE);
86 }
87 }
88 }
89
text_get_width(const uint8_t * str,font_t font)90 int text_get_width(const uint8_t *str, font_t font)
91 {
92 const font_definition *def = font_definition_for(font);
93 int maxlen = 10000;
94 int width = 0;
95 while (*str && maxlen > 0) {
96 int num_bytes = 1;
97 if (*str == ' ') {
98 width += def->space_width;
99 } else {
100 int letter_id = font_letter_id(def, str, &num_bytes);
101 if (letter_id >= 0) {
102 width += def->letter_spacing + image_letter(letter_id)->width;
103 }
104 }
105 str += num_bytes;
106 maxlen -= num_bytes;
107 }
108 return width;
109 }
110
get_letter_width(const uint8_t * str,const font_definition * def,int * num_bytes)111 static int get_letter_width(const uint8_t *str, const font_definition *def, int *num_bytes)
112 {
113 *num_bytes = 1;
114 if (*str == ' ') {
115 return def->space_width;
116 }
117 int letter_id = font_letter_id(def, str, num_bytes);
118 if (letter_id >= 0) {
119 return def->letter_spacing + image_letter(letter_id)->width;
120 } else {
121 return 0;
122 }
123 }
124
text_get_max_length_for_width(const uint8_t * str,int length,font_t font,unsigned int requested_width,int invert)125 unsigned int text_get_max_length_for_width(
126 const uint8_t *str, int length, font_t font, unsigned int requested_width, int invert)
127 {
128 const font_definition *def = font_definition_for(font);
129 if (!length) {
130 length = string_length(str);
131 }
132 if (invert) {
133 unsigned int maxlen = length;
134 unsigned int width = 0;
135 const uint8_t *s = str;
136 while (maxlen) {
137 int num_bytes;
138 width += get_letter_width(s, def, &num_bytes);
139 s += num_bytes;
140 maxlen -= num_bytes;
141 }
142
143 maxlen = length;
144 while (maxlen && width > requested_width) {
145 int num_bytes;
146 width -= get_letter_width(str, def, &num_bytes);
147 str += num_bytes;
148 maxlen -= num_bytes;
149 }
150 return maxlen;
151 } else {
152 unsigned int maxlen = length;
153 unsigned int width = 0;
154 while (maxlen) {
155 int num_bytes;
156 width += get_letter_width(str, def, &num_bytes);
157 if (width > requested_width) {
158 break;
159 }
160 str += num_bytes;
161 maxlen -= num_bytes;
162 }
163 return length - maxlen;
164 }
165 }
166
text_ellipsize(uint8_t * str,font_t font,int requested_width)167 void text_ellipsize(uint8_t *str, font_t font, int requested_width)
168 {
169 uint8_t *orig_str = str;
170 const font_definition *def = font_definition_for(font);
171 int ellipsis_width = get_ellipsis_width(font);
172 int maxlen = 10000;
173 int width = 0;
174 int length_with_ellipsis = 0;
175 while (*str && maxlen > 0) {
176 int num_bytes = 1;
177 if (*str == ' ') {
178 width += def->space_width;
179 } else {
180 int letter_id = font_letter_id(def, str, &num_bytes);
181 if (letter_id >= 0) {
182 width += def->letter_spacing + image_letter(letter_id)->width;
183 }
184 }
185 if (ellipsis_width + width <= requested_width) {
186 length_with_ellipsis += num_bytes;
187 }
188 if (width > requested_width) {
189 break;
190 }
191 str += num_bytes;
192 maxlen -= num_bytes;
193 }
194 if (10000 - maxlen < string_length(orig_str)) {
195 string_copy(ellipsis.string, orig_str + length_with_ellipsis, ELLIPSIS_LENGTH);
196 }
197 }
198
get_word_width(const uint8_t * str,font_t font,int * out_num_chars)199 static int get_word_width(const uint8_t *str, font_t font, int *out_num_chars)
200 {
201 const font_definition *def = font_definition_for(font);
202 int width = 0;
203 int guard = 0;
204 int word_char_seen = 0;
205 int num_chars = 0;
206 while (*str && ++guard < 200) {
207 int num_bytes = 1;
208 if (*str == ' ' || *str == '\n') {
209 if (word_char_seen) {
210 break;
211 }
212 width += def->space_width;
213 } else if (*str == '$') {
214 if (word_char_seen) {
215 break;
216 }
217 } else if (*str > ' ') {
218 // normal char
219 int letter_id = font_letter_id(def, str, &num_bytes);
220 if (letter_id >= 0) {
221 width += image_letter(letter_id)->width + def->letter_spacing;
222 }
223 word_char_seen = 1;
224 if (num_bytes > 1) {
225 num_chars += num_bytes;
226 break;
227 }
228 }
229 str += num_bytes;
230 num_chars += num_bytes;
231 }
232 *out_num_chars = num_chars;
233 return width;
234 }
235
text_draw_centered_with_linebreaks(const uint8_t * str,int x,int y,int box_width,font_t font,color_t color)236 void text_draw_centered_with_linebreaks(const uint8_t *str, int x, int y, int box_width, font_t font, color_t color)
237 {
238 int count = 0;
239 char *split;
240
241 char oldstr[512];
242 strcpy(oldstr, (char *) str);
243
244 split = strtok(oldstr, "\n");
245 while (split != NULL) {
246 text_draw_centered((uint8_t *) split, x, y + (20 * count), box_width, font, color);
247 count++;
248 split = strtok(NULL, "\n");
249 }
250 }
251
text_draw_centered(const uint8_t * str,int x,int y,int box_width,font_t font,color_t color)252 void text_draw_centered(const uint8_t *str, int x, int y, int box_width, font_t font, color_t color)
253 {
254 int offset = (box_width - text_get_width(str, font)) / 2;
255 if (offset < 0) {
256 offset = 0;
257 }
258 text_draw(str, offset + x, y, font, color);
259 }
260
text_draw_ellipsized(const uint8_t * str,int x,int y,int box_width,font_t font,color_t color)261 int text_draw_ellipsized(const uint8_t *str, int x, int y, int box_width, font_t font, color_t color)
262 {
263 static uint8_t buffer[1000];
264 string_copy(str, buffer, 1000);
265 text_ellipsize(buffer, font, box_width);
266 return text_draw(buffer, x, y, font, color);
267 }
268
text_draw(const uint8_t * str,int x,int y,font_t font,color_t color)269 int text_draw(const uint8_t *str, int x, int y, font_t font, color_t color)
270 {
271 const font_definition *def = font_definition_for(font);
272
273 int length = string_length(str);
274 if (input_cursor.capture) {
275 str += input_cursor.text_offset_start;
276 length = input_cursor.text_offset_end - input_cursor.text_offset_start;
277 }
278
279 int current_x = x;
280 while (length > 0) {
281 int num_bytes = 1;
282
283 if (*str >= ' ') {
284 int letter_id = font_letter_id(def, str, &num_bytes);
285 int width;
286 if (*str == ' ' || *str == '_' || letter_id < 0) {
287 width = def->space_width;
288 } else {
289 const image *img = image_letter(letter_id);
290 int height = def->image_y_offset(*str, img->height, def->line_height);
291 image_draw_letter(def->font, letter_id, current_x, y - height, color);
292 width = def->letter_spacing + img->width;
293 }
294 if (input_cursor.capture && input_cursor.position == input_cursor.cursor_position) {
295 if (!input_cursor.seen) {
296 input_cursor.width = width;
297 input_cursor.x_offset = current_x - x;
298 input_cursor.seen = 1;
299 }
300 }
301 current_x += width;
302 }
303
304 str += num_bytes;
305 length -= num_bytes;
306 input_cursor.position += num_bytes;
307 }
308 if (input_cursor.capture && !input_cursor.seen) {
309 input_cursor.width = 4;
310 input_cursor.x_offset = current_x - x;
311 input_cursor.seen = 1;
312 }
313 current_x += def->space_width;
314 return current_x - x;
315 }
316
number_to_string(uint8_t * str,int value,char prefix,const char * postfix)317 static int number_to_string(uint8_t *str, int value, char prefix, const char *postfix)
318 {
319 int offset = 0;
320 if (prefix) {
321 str[offset++] = prefix;
322 }
323 offset += string_from_int(&str[offset], value, 0);
324 while (*postfix) {
325 str[offset++] = *postfix;
326 postfix++;
327 }
328 str[offset] = 0;
329 return offset;
330 }
331
text_draw_number(int value,char prefix,const char * postfix,int x,int y,font_t font,color_t color)332 int text_draw_number(int value, char prefix, const char *postfix, int x, int y, font_t font, color_t color)
333 {
334 const font_definition *def = font_definition_for(font);
335 int current_x = x;
336
337 if (prefix > ' ') {
338 int num_bytes = 1;
339 int letter_id = font_letter_id(def, &prefix, &num_bytes);
340
341 int width;
342 if (prefix == ' ' || prefix == '_' || letter_id < 0) {
343 width = def->space_width;
344 } else {
345 const image *img = image_letter(letter_id);
346 int height = def->image_y_offset(prefix, img->height, def->line_height);
347 image_draw_letter(def->font, letter_id, current_x, y - height, color);
348 width = def->letter_spacing + img->width;
349 }
350
351 current_x += width;
352 }
353
354 uint8_t buffer[NUMBER_BUFFER_LENGTH];
355 int length = string_from_int(buffer, value, 0);
356 const char *str = buffer;
357
358 int separator_pixels = config_get(CONFIG_UI_DIGIT_SEPARATOR) * 3;
359
360 while (length > 0) {
361 int num_bytes = 1;
362
363 if (*str >= ' ') {
364 int letter_id = font_letter_id(def, str, &num_bytes);
365 int width;
366 if (*str == ' ' || *str == '_' || letter_id < 0) {
367 width = def->space_width;
368 } else {
369 const image *img = image_letter(letter_id);
370 int height = def->image_y_offset(*str, img->height, def->line_height);
371 image_draw_letter(def->font, letter_id, current_x, y - height, color);
372 width = def->letter_spacing + img->width;
373 }
374
375 current_x += width + ((length == 4 || length == 7) ? separator_pixels : 0);
376 }
377
378 str += num_bytes;
379 length -= num_bytes;
380 }
381
382 current_x += text_draw(postfix, current_x, y, font, color);
383
384 return current_x - x;
385 }
386
text_draw_money(int value,int x_offset,int y_offset,font_t font)387 int text_draw_money(int value, int x_offset, int y_offset, font_t font)
388 {
389 const uint8_t *postfix;
390 if (locale_translate_money_dn()) {
391 postfix = lang_get_string(6, 0);
392 } else {
393 postfix = string_from_ascii("Dn");
394 }
395 return text_draw_number(value, '@', postfix, x_offset, y_offset, font, 0);
396 }
397
text_draw_with_money(const uint8_t * text,int value,const char * prefix,const char * postfix,int x_offset,int y_offset,int box_width,font_t font,color_t color)398 void text_draw_with_money(const uint8_t *text, int value, const char *prefix, const char *postfix,
399 int x_offset, int y_offset, int box_width, font_t font, color_t color)
400 {
401 uint8_t str[NUMBER_BUFFER_LENGTH];
402 uint8_t *offset = string_copy(text, str, NUMBER_BUFFER_LENGTH);
403 if (prefix && *prefix) {
404 offset = string_copy(string_from_ascii(prefix), offset, NUMBER_BUFFER_LENGTH - (int) (offset - str) - 1);
405 }
406 offset += number_to_string(offset, value, 0, " ");
407 const uint8_t *money_postfix;
408 if (locale_translate_money_dn()) {
409 money_postfix = lang_get_string(6, 0);
410 } else {
411 money_postfix = string_from_ascii("Dn");
412 }
413 offset = string_copy(money_postfix, offset, NUMBER_BUFFER_LENGTH - (int) (offset - str) - 1);
414 if (postfix && *postfix) {
415 string_copy(string_from_ascii(postfix), offset, NUMBER_BUFFER_LENGTH - (int) (offset - str) - 1);
416 }
417 if (box_width > 0) {
418 text_draw_centered(str, x_offset, y_offset, box_width, font, color);
419 } else {
420 text_draw(str, x_offset, y_offset, font, color);
421 }
422 }
423
text_draw_percentage(int value,int x_offset,int y_offset,font_t font)424 int text_draw_percentage(int value, int x_offset, int y_offset, font_t font)
425 {
426 uint8_t str[NUMBER_BUFFER_LENGTH];
427 number_to_string(str, value, '@', "%");
428 return text_draw(str, x_offset, y_offset, font, 0);
429 }
430
text_draw_label_and_number(const uint8_t * label,int value,const char * postfix,int x_offset,int y_offset,font_t font,color_t color)431 int text_draw_label_and_number(const uint8_t *label, int value, const char *postfix, int x_offset, int y_offset, font_t font, color_t color)
432 {
433 uint8_t str[2 * NUMBER_BUFFER_LENGTH];
434 uint8_t *pos = label ? string_copy(label, str, NUMBER_BUFFER_LENGTH) : str;
435 number_to_string(pos, value, '@', postfix);
436 return text_draw(str, x_offset, y_offset, font, color);
437 }
438
text_draw_label_and_number_centered(const uint8_t * label,int value,const char * postfix,int x_offset,int y_offset,int box_width,font_t font,color_t color)439 void text_draw_label_and_number_centered(const uint8_t *label, int value, const char *postfix, int x_offset, int y_offset, int box_width, font_t font, color_t color)
440 {
441 uint8_t str[2 * NUMBER_BUFFER_LENGTH];
442 uint8_t *pos = label ? string_copy(label, str, NUMBER_BUFFER_LENGTH) : str;
443 number_to_string(pos, value, '@', postfix);
444 text_draw_centered(str, x_offset, y_offset, box_width, font, color);
445 }
446
text_draw_number_centered(int value,int x_offset,int y_offset,int box_width,font_t font)447 void text_draw_number_centered(int value, int x_offset, int y_offset, int box_width, font_t font)
448 {
449 uint8_t str[NUMBER_BUFFER_LENGTH];
450 number_to_string(str, value, '@', " ");
451 text_draw_centered(str, x_offset, y_offset, box_width, font, 0);
452 }
453
text_draw_number_centered_with_separator(int value,int x_offset,int y_offset,int box_width,font_t font)454 void text_draw_number_centered_with_separator(int value, int x_offset, int y_offset, int box_width, font_t font)
455 {
456 uint8_t str[NUMBER_BUFFER_LENGTH];
457 number_to_string(str, value, '@', " ");
458 text_draw_centered(str, x_offset, y_offset, box_width, font, 0);
459 }
460
text_draw_number_centered_prefix(int value,char prefix,int x_offset,int y_offset,int box_width,font_t font)461 void text_draw_number_centered_prefix(int value, char prefix, int x_offset, int y_offset, int box_width, font_t font)
462 {
463 uint8_t str[NUMBER_BUFFER_LENGTH];
464 number_to_string(str, value, prefix, " ");
465 text_draw_centered(str, x_offset, y_offset, box_width, font, 0);
466 }
467
text_draw_number_centered_colored(int value,int x_offset,int y_offset,int box_width,font_t font,color_t color)468 void text_draw_number_centered_colored(
469 int value, int x_offset, int y_offset, int box_width, font_t font, color_t color)
470 {
471 uint8_t str[NUMBER_BUFFER_LENGTH];
472 number_to_string(str, value, '@', " ");
473 text_draw_centered(str, x_offset, y_offset, box_width, font, color);
474 }
475
text_draw_multiline(const uint8_t * str,int x_offset,int y_offset,int box_width,font_t font,uint32_t color)476 int text_draw_multiline(const uint8_t *str, int x_offset, int y_offset, int box_width, font_t font, uint32_t color)
477 {
478 int line_height = font_definition_for(font)->line_height;
479 if (line_height < 11) {
480 line_height = 11;
481 }
482 int has_more_characters = 1;
483 int guard = 0;
484 int y = y_offset;
485 while (has_more_characters) {
486 if (++guard >= 100) {
487 break;
488 }
489 // clear line
490 for (int i = 0; i < 200; i++) {
491 tmp_line[i] = 0;
492 }
493 int current_width = 0;
494 int line_index = 0;
495 while (has_more_characters && current_width < box_width) {
496 int word_num_chars;
497 int word_width = get_word_width(str, font, &word_num_chars);
498 current_width += word_width;
499 if (current_width >= box_width) {
500 if (current_width == 0) {
501 has_more_characters = 0;
502 }
503 } else {
504 for (int i = 0; i < word_num_chars; i++) {
505 if (line_index == 0 && *str <= ' ') {
506 str++; // skip whitespace at start of line
507 } else {
508 tmp_line[line_index++] = *str++;
509 }
510 }
511 if (!*str) {
512 has_more_characters = 0;
513 } else if (*str == '\n') {
514 str++;
515 break;
516 }
517 }
518 }
519 text_draw(tmp_line, x_offset, y, font, color);
520 y += line_height + 5;
521 }
522 return y - y_offset;
523 }
524
text_measure_multiline(const uint8_t * str,int box_width,font_t font)525 int text_measure_multiline(const uint8_t *str, int box_width, font_t font)
526 {
527 int has_more_characters = 1;
528 int guard = 0;
529 int num_lines = 0;
530 while (has_more_characters) {
531 if (++guard >= 100) {
532 break;
533 }
534 int current_width = 0;
535 while (has_more_characters && current_width < box_width) {
536 int word_num_chars;
537 int word_width = get_word_width(str, font, &word_num_chars);
538 current_width += word_width;
539 if (current_width >= box_width) {
540 if (current_width == 0) {
541 has_more_characters = 0;
542 }
543 } else {
544 str += word_num_chars;
545 if (!*str) {
546 has_more_characters = 0;
547 } else if (*str == '\n') {
548 str++;
549 break;
550 }
551 }
552 }
553 num_lines += 1;
554 }
555 return num_lines;
556 }
557