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