1 /**
2  * @file lv_label.c
3  *
4  */
5 
6 /*********************
7  *      INCLUDES
8  *********************/
9 #include "lv_label_private.h"
10 #include "../../misc/lv_area_private.h"
11 #include "../../misc/lv_anim_private.h"
12 #include "../../draw/lv_draw_label_private.h"
13 #include "../../core/lv_obj_class_private.h"
14 #if LV_USE_LABEL != 0
15 #include "../../core/lv_obj_private.h"
16 #include "../../misc/lv_assert.h"
17 #include "../../core/lv_group.h"
18 #include "../../display/lv_display.h"
19 #include "../../draw/lv_draw_private.h"
20 #include "../../misc/lv_color.h"
21 #include "../../misc/lv_math.h"
22 #include "../../misc/lv_bidi_private.h"
23 #include "../../misc/lv_text_ap.h"
24 #include "../../misc/lv_text_private.h"
25 #include "../../stdlib/lv_sprintf.h"
26 #include "../../stdlib/lv_string.h"
27 
28 /*********************
29  *      DEFINES
30  *********************/
31 #define MY_CLASS (&lv_label_class)
32 
33 #define LV_LABEL_DEF_SCROLL_SPEED   lv_anim_speed_clamped(40, 300, 10000)
34 #define LV_LABEL_SCROLL_DELAY       300
35 #define LV_LABEL_DOT_BEGIN_INV 0xFFFFFFFF
36 #define LV_LABEL_HINT_HEIGHT_LIMIT 1024 /*Enable "hint" to buffer info about labels larger than this. (Speed up drawing)*/
37 
38 /**********************
39  *      TYPEDEFS
40  **********************/
41 
42 /**********************
43  *  STATIC PROTOTYPES
44  **********************/
45 static void lv_label_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj);
46 static void lv_label_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj);
47 static void lv_label_event(const lv_obj_class_t * class_p, lv_event_t * e);
48 static void draw_main(lv_event_t * e);
49 
50 static void lv_label_refr_text(lv_obj_t * obj);
51 static void lv_label_revert_dots(lv_obj_t * label);
52 static void lv_label_set_dots(lv_obj_t * label, uint32_t dot_begin);
53 
54 static void set_ofs_x_anim(void * obj, int32_t v);
55 static void set_ofs_y_anim(void * obj, int32_t v);
56 static size_t get_text_length(const char * text);
57 static void copy_text_to_label(lv_label_t * label, const char * text);
58 static lv_text_flag_t get_label_flags(lv_label_t * label);
59 static void calculate_x_coordinate(int32_t * x, const lv_text_align_t align, const char * txt,
60                                    uint32_t length, const lv_font_t * font, int32_t letter_space, lv_area_t * txt_coords, lv_text_flag_t flags);
61 
62 /**********************
63  *  STATIC VARIABLES
64  **********************/
65 #if LV_USE_OBJ_PROPERTY
66 static const lv_property_ops_t properties[] = {
67     {
68         .id = LV_PROPERTY_LABEL_TEXT,
69         .setter = lv_label_set_text,
70         .getter = lv_label_get_text,
71     },
72     {
73         .id = LV_PROPERTY_LABEL_LONG_MODE,
74         .setter = lv_label_set_long_mode,
75         .getter = lv_label_get_long_mode,
76     },
77     {
78         .id = LV_PROPERTY_LABEL_TEXT_SELECTION_START,
79         .setter = lv_label_set_text_selection_start,
80         .getter = lv_label_get_text_selection_start,
81     },
82     {
83         .id = LV_PROPERTY_LABEL_TEXT_SELECTION_END,
84         .setter = lv_label_set_text_selection_end,
85         .getter = lv_label_get_text_selection_end,
86     },
87 };
88 #endif
89 
90 const lv_obj_class_t lv_label_class = {
91     .constructor_cb = lv_label_constructor,
92     .destructor_cb = lv_label_destructor,
93     .event_cb = lv_label_event,
94     .width_def = LV_SIZE_CONTENT,
95     .height_def = LV_SIZE_CONTENT,
96     .instance_size = sizeof(lv_label_t),
97     .base_class = &lv_obj_class,
98     .name = "label",
99 #if LV_USE_OBJ_PROPERTY
100     .prop_index_start = LV_PROPERTY_LABEL_START,
101     .prop_index_end = LV_PROPERTY_LABEL_END,
102     .properties = properties,
103     .properties_count = sizeof(properties) / sizeof(properties[0]),
104 
105 #if LV_USE_OBJ_PROPERTY_NAME
106     .property_names = lv_label_property_names,
107     .names_count = sizeof(lv_label_property_names) / sizeof(lv_property_name_t),
108 #endif
109 
110 #endif
111 };
112 
113 /**********************
114  *      MACROS
115  **********************/
116 
117 /**********************
118  *   GLOBAL FUNCTIONS
119  **********************/
120 
lv_label_create(lv_obj_t * parent)121 lv_obj_t * lv_label_create(lv_obj_t * parent)
122 {
123     LV_LOG_INFO("begin");
124     lv_obj_t * obj = lv_obj_class_create_obj(MY_CLASS, parent);
125     lv_obj_class_init_obj(obj);
126     return obj;
127 }
128 
129 /*=====================
130  * Setter functions
131  *====================*/
132 
lv_label_set_text(lv_obj_t * obj,const char * text)133 void lv_label_set_text(lv_obj_t * obj, const char * text)
134 {
135     LV_ASSERT_OBJ(obj, MY_CLASS);
136     lv_label_t * label = (lv_label_t *)obj;
137 
138     /*If text is NULL then just refresh with the current text*/
139     if(text == NULL) text = label->text;
140 
141     lv_label_revert_dots(obj); /*In case text == label->text*/
142     const size_t text_len = get_text_length(text);
143 
144     /*If set its own text then reallocate it (maybe its size changed)*/
145     if(label->text == text && label->static_txt == 0) {
146         label->text = lv_realloc(label->text, text_len);
147         LV_ASSERT_MALLOC(label->text);
148         if(label->text == NULL) return;
149 
150 #if LV_USE_ARABIC_PERSIAN_CHARS
151         lv_text_ap_proc(label->text, label->text);
152 #endif
153 
154     }
155     else {
156         /*Free the old text*/
157         if(label->text != NULL && label->static_txt == 0) {
158             lv_free(label->text);
159             label->text = NULL;
160         }
161 
162         label->text = lv_malloc(text_len);
163         LV_ASSERT_MALLOC(label->text);
164         if(label->text == NULL) return;
165 
166         copy_text_to_label(label, text);
167 
168         /*Now the text is dynamically allocated*/
169         label->static_txt = 0;
170     }
171 
172     lv_label_refr_text(obj);
173 }
174 
lv_label_set_text_fmt(lv_obj_t * obj,const char * fmt,...)175 void lv_label_set_text_fmt(lv_obj_t * obj, const char * fmt, ...)
176 {
177     LV_ASSERT_OBJ(obj, MY_CLASS);
178     LV_ASSERT_NULL(fmt);
179 
180     lv_obj_invalidate(obj);
181     lv_label_t * label = (lv_label_t *)obj;
182 
183     /*If text is NULL then refresh*/
184     if(fmt == NULL) {
185         lv_label_refr_text(obj);
186         return;
187     }
188 
189     if(label->text != NULL && label->static_txt == 0) {
190         lv_free(label->text);
191         label->text = NULL;
192     }
193 
194     va_list args;
195     va_start(args, fmt);
196     label->text = lv_text_set_text_vfmt(fmt, args);
197     va_end(args);
198     label->static_txt = 0; /*Now the text is dynamically allocated*/
199 
200     lv_label_refr_text(obj);
201 }
202 
lv_label_set_text_static(lv_obj_t * obj,const char * text)203 void lv_label_set_text_static(lv_obj_t * obj, const char * text)
204 {
205     LV_ASSERT_OBJ(obj, MY_CLASS);
206     lv_label_t * label = (lv_label_t *)obj;
207 
208     if(label->static_txt == 0 && label->text != NULL) {
209         lv_free(label->text);
210         label->text = NULL;
211     }
212 
213     if(text != NULL) {
214         label->static_txt = 1;
215         label->text       = (char *)text;
216     }
217 
218     lv_label_refr_text(obj);
219 }
220 
lv_label_set_long_mode(lv_obj_t * obj,lv_label_long_mode_t long_mode)221 void lv_label_set_long_mode(lv_obj_t * obj, lv_label_long_mode_t long_mode)
222 {
223     LV_ASSERT_OBJ(obj, MY_CLASS);
224 
225     lv_label_t * label = (lv_label_t *)obj;
226 
227     /*Delete the old animation (if exists)*/
228     lv_anim_delete(obj, set_ofs_x_anim);
229     lv_anim_delete(obj, set_ofs_y_anim);
230     lv_point_set(&label->offset, 0, 0);
231 
232     if(long_mode == LV_LABEL_LONG_MODE_SCROLL || long_mode == LV_LABEL_LONG_MODE_SCROLL_CIRCULAR ||
233        long_mode == LV_LABEL_LONG_MODE_CLIP)
234         label->expand = 1;
235     else
236         label->expand = 0;
237 
238     label->long_mode = long_mode;
239     lv_label_refr_text(obj);
240 }
241 
lv_label_set_text_selection_start(lv_obj_t * obj,uint32_t index)242 void lv_label_set_text_selection_start(lv_obj_t * obj, uint32_t index)
243 {
244     LV_ASSERT_OBJ(obj, MY_CLASS);
245 
246 #if LV_LABEL_TEXT_SELECTION
247     lv_label_t * label = (lv_label_t *)obj;
248     label->sel_start   = index;
249     lv_obj_invalidate(obj);
250 #else
251     LV_UNUSED(obj);    /*Unused*/
252     LV_UNUSED(index);  /*Unused*/
253 #endif
254 }
255 
lv_label_set_text_selection_end(lv_obj_t * obj,uint32_t index)256 void lv_label_set_text_selection_end(lv_obj_t * obj, uint32_t index)
257 {
258     LV_ASSERT_OBJ(obj, MY_CLASS);
259 
260 #if LV_LABEL_TEXT_SELECTION
261     lv_label_t * label = (lv_label_t *)obj;
262     label->sel_end     = index;
263     lv_obj_invalidate(obj);
264 #else
265     LV_UNUSED(obj);   /*Unused*/
266     LV_UNUSED(index); /*Unused*/
267 #endif
268 }
269 
lv_label_set_recolor(lv_obj_t * obj,bool en)270 void lv_label_set_recolor(lv_obj_t * obj, bool en)
271 {
272     LV_ASSERT_OBJ(obj, MY_CLASS);
273 
274     lv_label_t * label = (lv_label_t *)obj;
275     if(label->recolor == en) return;
276 
277     label->recolor = en == false ? 0 : 1;
278 
279     /*Refresh the text because the potential color codes in text needs to be hidden or revealed*/
280     lv_label_refr_text(obj);
281 }
282 
283 /*=====================
284  * Getter functions
285  *====================*/
286 
lv_label_get_text(const lv_obj_t * obj)287 char * lv_label_get_text(const lv_obj_t * obj)
288 {
289     LV_ASSERT_OBJ(obj, MY_CLASS);
290     lv_label_t * label = (lv_label_t *)obj;
291     return label->text;
292 }
293 
lv_label_get_long_mode(const lv_obj_t * obj)294 lv_label_long_mode_t lv_label_get_long_mode(const lv_obj_t * obj)
295 {
296     LV_ASSERT_OBJ(obj, MY_CLASS);
297     lv_label_t * label = (lv_label_t *)obj;
298     return label->long_mode;
299 }
300 
lv_label_get_letter_pos(const lv_obj_t * obj,uint32_t char_id,lv_point_t * pos)301 void lv_label_get_letter_pos(const lv_obj_t * obj, uint32_t char_id, lv_point_t * pos)
302 {
303     LV_ASSERT_OBJ(obj, MY_CLASS);
304     LV_ASSERT_NULL(pos);
305 
306     lv_label_t * label = (lv_label_t *)obj;
307     const char * txt = lv_label_get_text(obj);
308     const lv_text_align_t align = lv_obj_calculate_style_text_align(obj, LV_PART_MAIN, txt);
309 
310     if(txt[0] == '\0') {
311         pos->y = 0;
312         switch(align) {
313             case LV_TEXT_ALIGN_LEFT:
314                 pos->x = 0;
315                 break;
316             case LV_TEXT_ALIGN_RIGHT:
317                 pos->x = lv_obj_get_content_width(obj);
318                 break;
319             case LV_TEXT_ALIGN_CENTER:
320                 pos->x = lv_obj_get_content_width(obj) / 2;
321                 break;
322             default:
323                 pos->x = 0;
324                 break;
325         }
326         return;
327     }
328 
329     lv_text_flag_t flag = get_label_flags(label);
330 
331     const uint32_t byte_id = lv_text_encoded_get_byte_id(txt, char_id);
332     /*Search the line of the index letter*/
333     const int32_t line_space = lv_obj_get_style_text_line_space(obj, LV_PART_MAIN);
334     const int32_t letter_space = lv_obj_get_style_text_letter_space(obj, LV_PART_MAIN);
335 
336     const lv_font_t * font = lv_obj_get_style_text_font(obj, LV_PART_MAIN);
337     const int32_t letter_height = lv_font_get_line_height(font);
338 
339     lv_area_t txt_coords;
340     lv_obj_get_content_coords(obj, &txt_coords);
341     const int32_t max_w = lv_area_get_width(&txt_coords);
342     const int32_t max_h = lv_area_get_height(&txt_coords);
343 
344     int32_t y = 0;
345     uint32_t line_start = 0;
346     uint32_t new_line_start = 0;
347     while(txt[new_line_start] != '\0') {
348         bool last_line = y + letter_height + line_space + letter_height > max_h;
349         if(last_line && label->long_mode == LV_LABEL_LONG_MODE_DOTS) flag |= LV_TEXT_FLAG_BREAK_ALL;
350 
351         new_line_start += lv_text_get_next_line(&txt[line_start], LV_TEXT_LEN_MAX, font, letter_space, max_w, NULL, flag);
352         if(byte_id < new_line_start || txt[new_line_start] == '\0')
353             break; /*The line of 'index' letter begins at 'line_start'*/
354 
355         y += letter_height + line_space;
356         line_start = new_line_start;
357     }
358 
359     /*If the last character is line break then go to the next line*/
360     if(byte_id > 0) {
361         if((txt[byte_id - 1] == '\n' || txt[byte_id - 1] == '\r') && txt[byte_id] == '\0') {
362             y += letter_height + line_space;
363             line_start = byte_id;
364         }
365     }
366 
367     const char * bidi_txt;
368     uint32_t visual_byte_pos;
369 #if LV_USE_BIDI
370     lv_base_dir_t base_dir = lv_obj_get_style_base_dir(obj, LV_PART_MAIN);
371     if(base_dir == LV_BASE_DIR_AUTO) base_dir = lv_bidi_detect_base_dir(txt);
372 
373     char * mutable_bidi_txt = NULL;
374     /*Handle Bidi*/
375     if(new_line_start == byte_id) {
376         visual_byte_pos = base_dir == LV_BASE_DIR_RTL ? 0 : byte_id - line_start;
377         bidi_txt = &txt[line_start];
378     }
379     else {
380         uint32_t line_char_id = lv_text_encoded_get_char_id(&txt[line_start], byte_id - line_start);
381 
382         bool is_rtl;
383         uint32_t visual_char_pos = lv_bidi_get_visual_pos(&txt[line_start], &mutable_bidi_txt, new_line_start - line_start,
384                                                           base_dir, line_char_id, &is_rtl);
385         bidi_txt = mutable_bidi_txt;
386         if(is_rtl) visual_char_pos++;
387 
388         visual_byte_pos = lv_text_encoded_get_byte_id(bidi_txt, visual_char_pos);
389     }
390 #else
391     bidi_txt = &txt[line_start];
392     visual_byte_pos = byte_id - line_start;
393 #endif
394 
395     /*Calculate the x coordinate*/
396     int32_t x = lv_text_get_width_with_flags(bidi_txt, visual_byte_pos, font, letter_space, flag);
397     if(char_id != line_start) x += letter_space;
398 
399     uint32_t length = new_line_start - line_start;
400     calculate_x_coordinate(&x, align, bidi_txt, length, font, letter_space, &txt_coords, flag);
401     pos->x = x;
402     pos->y = y;
403 
404 #if LV_USE_BIDI
405     if(mutable_bidi_txt) lv_free(mutable_bidi_txt);
406 #endif
407 }
408 
lv_label_get_letter_on(const lv_obj_t * obj,lv_point_t * pos_in,bool bidi)409 uint32_t lv_label_get_letter_on(const lv_obj_t * obj, lv_point_t * pos_in, bool bidi)
410 {
411     LV_UNUSED(bidi);
412     LV_ASSERT_OBJ(obj, MY_CLASS);
413     LV_ASSERT_NULL(pos_in);
414     lv_label_t * label = (lv_label_t *)obj;
415 
416     lv_point_t pos;
417     pos.x = pos_in->x - lv_obj_get_style_pad_left(obj, LV_PART_MAIN);
418     pos.y = pos_in->y - lv_obj_get_style_pad_top(obj, LV_PART_MAIN);
419 
420     lv_area_t txt_coords;
421     lv_obj_get_content_coords(obj, &txt_coords);
422     const char * txt         = lv_label_get_text(obj);
423     uint32_t line_start      = 0;
424     uint32_t new_line_start  = 0;
425     int32_t max_w = lv_area_get_width(&txt_coords);
426     int32_t max_h = lv_area_get_height(&txt_coords);
427     const lv_font_t * font   = lv_obj_get_style_text_font(obj, LV_PART_MAIN);
428     const int32_t line_space = lv_obj_get_style_text_line_space(obj, LV_PART_MAIN);
429     const int32_t letter_space = lv_obj_get_style_text_letter_space(obj, LV_PART_MAIN);
430     const int32_t letter_height = lv_font_get_line_height(font);
431     int32_t y = 0;
432 
433     lv_text_flag_t flag = get_label_flags(label);
434 
435     /*Search the line of the index letter*/;
436     while(txt[line_start] != '\0') {
437         /*If dots will be shown, break the last visible line anywhere,
438          *not only at word boundaries.*/
439         bool last_line = y + letter_height + line_space + letter_height > max_h;
440         if(last_line && label->long_mode == LV_LABEL_LONG_MODE_DOTS) flag |= LV_TEXT_FLAG_BREAK_ALL;
441 
442         new_line_start += lv_text_get_next_line(&txt[line_start], LV_TEXT_LEN_MAX, font, letter_space, max_w, NULL, flag);
443 
444         if(pos.y <= y + letter_height) {
445             /*The line is found (stored in 'line_start')*/
446             /*Include the NULL terminator in the last line*/
447             uint32_t tmp = new_line_start;
448             uint32_t letter;
449             letter = lv_text_encoded_prev(txt, &tmp);
450             if(letter != '\n' && txt[new_line_start] == '\0') new_line_start++;
451             break;
452         }
453         y += letter_height + line_space;
454 
455         line_start = new_line_start;
456     }
457 
458     char * bidi_txt;
459 
460 #if LV_USE_BIDI
461     uint32_t txt_len = 0;
462     if(bidi) {
463         bidi_txt = lv_malloc(new_line_start - line_start + 1);
464         txt_len = new_line_start - line_start;
465         if(new_line_start > 0 && txt[new_line_start - 1] == '\0' && txt_len > 0) txt_len--;
466         lv_bidi_process_paragraph(txt + line_start, bidi_txt, txt_len, lv_obj_get_style_base_dir(obj, LV_PART_MAIN), NULL, 0);
467     }
468     else
469 #endif
470     {
471         bidi_txt = (char *)txt + line_start;
472     }
473 
474     /*Calculate the x coordinate*/
475     int32_t x = 0;
476     const lv_text_align_t align = lv_obj_calculate_style_text_align(obj, LV_PART_MAIN, label->text);
477     uint32_t length = new_line_start - line_start;
478     calculate_x_coordinate(&x, align, bidi_txt, length, font, letter_space, &txt_coords, flag);
479 
480     lv_text_cmd_state_t cmd_state = LV_TEXT_CMD_STATE_WAIT;
481 
482     uint32_t i = 0;
483     uint32_t i_act = i;
484 
485     if(new_line_start > 0) {
486         while(i + line_start < new_line_start) {
487             /*Get the current letter and the next letter for kerning*/
488             /*Be careful 'i' already points to the next character*/
489             uint32_t letter;
490             uint32_t letter_next;
491             lv_text_encoded_letter_next_2(bidi_txt, &letter, &letter_next, &i);
492 
493             if((flag & LV_TEXT_FLAG_RECOLOR) != 0) {
494                 if(lv_text_is_cmd(&cmd_state, bidi_txt[i]) != false) {
495                     continue; /*Skip the letter if it is part of a command*/
496                 }
497             }
498 
499             int32_t gw = lv_font_get_glyph_width(font, letter, letter_next);
500 
501             /*Finish if the x position or the last char of the next line is reached*/
502             if(pos.x < x + gw || i + line_start == new_line_start ||  txt[i_act + line_start] == '\0') {
503                 i = i_act;
504                 break;
505             }
506             x += gw;
507             x += letter_space;
508             i_act = i;
509         }
510     }
511 
512     uint32_t logical_pos;
513 #if LV_USE_BIDI
514     if(bidi) {
515         /*Handle Bidi*/
516         uint32_t cid = lv_text_encoded_get_char_id(bidi_txt, i);
517         if(txt[line_start + i] == '\0') {
518             logical_pos = i;
519         }
520         else {
521             bool is_rtl;
522             logical_pos = lv_bidi_get_logical_pos(&txt[line_start], NULL,
523                                                   txt_len, lv_obj_get_style_base_dir(obj, LV_PART_MAIN), cid, &is_rtl);
524             if(is_rtl) logical_pos++;
525         }
526         lv_free(bidi_txt);
527     }
528     else
529 #endif
530     {
531         logical_pos = lv_text_encoded_get_char_id(bidi_txt, i);
532     }
533 
534     return  logical_pos + lv_text_encoded_get_char_id(txt, line_start);
535 }
536 
lv_label_is_char_under_pos(const lv_obj_t * obj,lv_point_t * pos)537 bool lv_label_is_char_under_pos(const lv_obj_t * obj, lv_point_t * pos)
538 {
539     LV_ASSERT_OBJ(obj, MY_CLASS);
540     LV_ASSERT_NULL(pos);
541 
542     lv_area_t txt_coords;
543     lv_obj_get_content_coords(obj, &txt_coords);
544     const char * txt         = lv_label_get_text(obj);
545     lv_label_t * label     = (lv_label_t *)obj;
546     uint32_t line_start      = 0;
547     uint32_t new_line_start  = 0;
548     const int32_t max_w = lv_area_get_width(&txt_coords);
549     const int32_t max_h = lv_area_get_height(&txt_coords);
550     const lv_font_t * font   = lv_obj_get_style_text_font(obj, LV_PART_MAIN);
551     const int32_t line_space = lv_obj_get_style_text_line_space(obj, LV_PART_MAIN);
552     const int32_t letter_space = lv_obj_get_style_text_letter_space(obj, LV_PART_MAIN);
553     const int32_t letter_height    = lv_font_get_line_height(font);
554 
555     lv_text_flag_t flag = get_label_flags(label);
556 
557     /*Search the line of the index letter*/
558     int32_t y = 0;
559     while(txt[line_start] != '\0') {
560         bool last_line = y + letter_height + line_space + letter_height > max_h;
561         if(last_line && label->long_mode == LV_LABEL_LONG_MODE_DOTS) flag |= LV_TEXT_FLAG_BREAK_ALL;
562 
563         new_line_start += lv_text_get_next_line(&txt[line_start], LV_TEXT_LEN_MAX, font, letter_space, max_w, NULL, flag);
564 
565         if(pos->y <= y + letter_height) break; /*The line is found (stored in 'line_start')*/
566         y += letter_height + line_space;
567 
568         line_start = new_line_start;
569     }
570 
571     /*Calculate the x coordinate*/
572     const lv_text_align_t align = lv_obj_calculate_style_text_align(obj, LV_PART_MAIN, label->text);
573 
574     int32_t x = 0;
575     if(align == LV_TEXT_ALIGN_CENTER) {
576         const int32_t line_w = lv_text_get_width_with_flags(&txt[line_start], new_line_start - line_start, font, letter_space,
577                                                             flag);
578         x += lv_area_get_width(&txt_coords) / 2 - line_w / 2;
579     }
580     else if(align == LV_TEXT_ALIGN_RIGHT) {
581         const int32_t line_w = lv_text_get_width_with_flags(&txt[line_start], new_line_start - line_start, font, letter_space,
582                                                             flag);
583         x += lv_area_get_width(&txt_coords) - line_w;
584     }
585 
586     lv_text_cmd_state_t cmd_state = LV_TEXT_CMD_STATE_WAIT;
587 
588     int32_t last_x = 0;
589     uint32_t i           = line_start;
590     uint32_t i_current   = i;
591     uint32_t letter      = '\0';
592     uint32_t letter_next = '\0';
593 
594     if(new_line_start > 0) {
595         while(i <= new_line_start - 1) {
596             /*Get the current letter and the next letter for kerning*/
597             /*Be careful 'i' already points to the next character*/
598             lv_text_encoded_letter_next_2(txt, &letter, &letter_next, &i);
599 
600             if((flag & LV_TEXT_FLAG_RECOLOR) != 0) {
601                 if(lv_text_is_cmd(&cmd_state, txt[i]) != false) {
602                     continue; /*Skip the letter if it is part of a command*/
603                 }
604             }
605 
606             last_x = x;
607             x += lv_font_get_glyph_width(font, letter, letter_next);
608             if(pos->x < x) {
609                 i = i_current;
610                 break;
611             }
612             x += letter_space;
613             i_current = i;
614         }
615     }
616 
617     const int32_t max_diff = lv_font_get_glyph_width(font, letter, letter_next) + letter_space + 1;
618     return (pos->x >= (last_x - letter_space) && pos->x <= (last_x + max_diff));
619 }
620 
lv_label_get_text_selection_start(const lv_obj_t * obj)621 uint32_t lv_label_get_text_selection_start(const lv_obj_t * obj)
622 {
623     LV_ASSERT_OBJ(obj, MY_CLASS);
624 
625 #if LV_LABEL_TEXT_SELECTION
626     lv_label_t * label = (lv_label_t *)obj;
627     return label->sel_start;
628 #else
629     LV_UNUSED(obj); /*Unused*/
630     return LV_LABEL_TEXT_SELECTION_OFF;
631 #endif
632 }
633 
lv_label_get_text_selection_end(const lv_obj_t * obj)634 uint32_t lv_label_get_text_selection_end(const lv_obj_t * obj)
635 {
636     LV_ASSERT_OBJ(obj, MY_CLASS);
637 
638 #if LV_LABEL_TEXT_SELECTION
639     lv_label_t * label = (lv_label_t *)obj;
640     return label->sel_end;
641 #else
642     LV_UNUSED(obj); /*Unused*/
643     return LV_LABEL_TEXT_SELECTION_OFF;
644 #endif
645 }
646 
lv_label_get_recolor(const lv_obj_t * obj)647 bool lv_label_get_recolor(const lv_obj_t * obj)
648 {
649     LV_ASSERT_OBJ(obj, MY_CLASS);
650 
651     lv_label_t * label = (lv_label_t *)obj;
652     return label->recolor == 0 ? false : true;
653 }
654 
655 /*=====================
656  * Other functions
657  *====================*/
658 
lv_label_ins_text(lv_obj_t * obj,uint32_t pos,const char * txt)659 void lv_label_ins_text(lv_obj_t * obj, uint32_t pos, const char * txt)
660 {
661     LV_ASSERT_OBJ(obj, MY_CLASS);
662     LV_ASSERT_NULL(txt);
663 
664     lv_label_t * label = (lv_label_t *)obj;
665 
666     /*Cannot append to static text*/
667     if(label->static_txt != 0) return;
668 
669     lv_obj_invalidate(obj);
670 
671     /*Allocate space for the new text*/
672     size_t old_len = lv_strlen(label->text);
673     size_t ins_len = lv_strlen(txt);
674     size_t new_len = ins_len + old_len;
675     label->text        = lv_realloc(label->text, new_len + 1);
676     LV_ASSERT_MALLOC(label->text);
677     if(label->text == NULL) return;
678 
679     if(pos == LV_LABEL_POS_LAST) {
680         pos = lv_text_get_encoded_length(label->text);
681     }
682 
683     lv_text_ins(label->text, pos, txt);
684     lv_label_set_text(obj, NULL);
685 }
686 
lv_label_cut_text(lv_obj_t * obj,uint32_t pos,uint32_t cnt)687 void lv_label_cut_text(lv_obj_t * obj, uint32_t pos, uint32_t cnt)
688 {
689     LV_ASSERT_OBJ(obj, MY_CLASS);
690     lv_label_t * label = (lv_label_t *)obj;
691 
692     /*Cannot append to static text*/
693     if(label->static_txt) return;
694 
695     lv_obj_invalidate(obj);
696 
697     char * label_txt = lv_label_get_text(obj);
698     /*Delete the characters*/
699     lv_text_cut(label_txt, pos, cnt);
700 
701     /*Refresh the label*/
702     lv_label_refr_text(obj);
703 }
704 
705 /**********************
706  *   STATIC FUNCTIONS
707  **********************/
708 
lv_label_constructor(const lv_obj_class_t * class_p,lv_obj_t * obj)709 static void lv_label_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj)
710 {
711     LV_UNUSED(class_p);
712     LV_TRACE_OBJ_CREATE("begin");
713 
714     lv_label_t * label = (lv_label_t *)obj;
715 
716     label->text       = NULL;
717     label->recolor    = 0;
718     label->static_txt = 0;
719     label->dot_begin  = LV_LABEL_DOT_BEGIN_INV;
720     label->long_mode  = LV_LABEL_LONG_MODE_WRAP;
721     lv_point_set(&label->offset, 0, 0);
722 
723 #if LV_LABEL_LONG_TXT_HINT
724     label->hint.line_start = -1;
725     label->hint.coord_y    = 0;
726     label->hint.y          = 0;
727 #endif
728 
729 #if LV_LABEL_TEXT_SELECTION
730     label->sel_start = LV_DRAW_LABEL_NO_TXT_SEL;
731     label->sel_end   = LV_DRAW_LABEL_NO_TXT_SEL;
732 #endif
733 
734     lv_obj_remove_flag(obj, LV_OBJ_FLAG_CLICKABLE);
735     lv_label_set_long_mode(obj, LV_LABEL_LONG_MODE_WRAP);
736     lv_label_set_text(obj, LV_LABEL_DEFAULT_TEXT);
737 
738     LV_TRACE_OBJ_CREATE("finished");
739 }
740 
lv_label_destructor(const lv_obj_class_t * class_p,lv_obj_t * obj)741 static void lv_label_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj)
742 {
743     LV_UNUSED(class_p);
744     lv_label_t * label = (lv_label_t *)obj;
745 
746     if(!label->static_txt) lv_free(label->text);
747     label->text = NULL;
748 }
749 
lv_label_event(const lv_obj_class_t * class_p,lv_event_t * e)750 static void lv_label_event(const lv_obj_class_t * class_p, lv_event_t * e)
751 {
752     LV_UNUSED(class_p);
753 
754     /*Call the ancestor's event handler*/
755     const lv_result_t res = lv_obj_event_base(MY_CLASS, e);
756     if(res != LV_RESULT_OK) return;
757 
758     const lv_event_code_t code = lv_event_get_code(e);
759     lv_obj_t * obj = lv_event_get_current_target(e);
760 
761     if((code == LV_EVENT_STYLE_CHANGED) || (code == LV_EVENT_SIZE_CHANGED)) {
762         lv_label_refr_text(obj);
763     }
764     else if(code == LV_EVENT_REFR_EXT_DRAW_SIZE) {
765         /* Italic or other non-typical letters can be drawn of out of the object.
766          * It happens if box_w + ofs_x > adw_w in the glyph.
767          * To avoid this add some extra draw area.
768          * font_h / 4 is an empirical value. */
769         const lv_font_t * font = lv_obj_get_style_text_font(obj, LV_PART_MAIN);
770         const int32_t font_h = lv_font_get_line_height(font);
771         lv_event_set_ext_draw_size(e, font_h / 4);
772     }
773     else if(code == LV_EVENT_GET_SELF_SIZE) {
774         lv_label_t * label = (lv_label_t *)obj;
775         if(label->invalid_size_cache) {
776             const lv_font_t * font = lv_obj_get_style_text_font(obj, LV_PART_MAIN);
777             int32_t letter_space = lv_obj_get_style_text_letter_space(obj, LV_PART_MAIN);
778             int32_t line_space = lv_obj_get_style_text_line_space(obj, LV_PART_MAIN);
779             lv_text_flag_t flag = LV_TEXT_FLAG_NONE;
780             if(label->recolor != 0) flag |= LV_TEXT_FLAG_RECOLOR;
781             if(label->expand != 0) flag |= LV_TEXT_FLAG_EXPAND;
782 
783             int32_t w;
784             if(lv_obj_get_style_width(obj, LV_PART_MAIN) == LV_SIZE_CONTENT && !obj->w_layout) w = LV_COORD_MAX;
785             else w = lv_obj_get_content_width(obj);
786             w = LV_MIN(w, lv_obj_get_style_max_width(obj, LV_PART_MAIN));
787 
788             uint32_t dot_begin = label->dot_begin;
789             lv_label_revert_dots(obj);
790             lv_text_get_size(&label->size_cache, label->text, font, letter_space, line_space, w, flag);
791             lv_label_set_dots(obj, dot_begin);
792 
793             label->size_cache.y = LV_MIN(label->size_cache.y, lv_obj_get_style_max_height(obj, LV_PART_MAIN));
794 
795             label->invalid_size_cache = false;
796         }
797 
798         lv_point_t * self_size = lv_event_get_param(e);
799         self_size->x = LV_MAX(self_size->x, label->size_cache.x);
800         self_size->y = LV_MAX(self_size->y, label->size_cache.y);
801     }
802     else if(code == LV_EVENT_DRAW_MAIN) {
803         draw_main(e);
804     }
805 }
806 
draw_main(lv_event_t * e)807 static void draw_main(lv_event_t * e)
808 {
809     lv_obj_t * obj = lv_event_get_current_target(e);
810     lv_label_t * label = (lv_label_t *)obj;
811     lv_layer_t * layer = lv_event_get_layer(e);
812 
813     lv_area_t txt_coords;
814     lv_obj_get_content_coords(obj, &txt_coords);
815 
816     lv_text_flag_t flag = get_label_flags(label);
817 
818     lv_draw_label_dsc_t label_draw_dsc;
819     lv_draw_label_dsc_init(&label_draw_dsc);
820     label_draw_dsc.text = label->text;
821     label_draw_dsc.text_static = label->static_txt;
822     label_draw_dsc.ofs_x = label->offset.x;
823     label_draw_dsc.ofs_y = label->offset.y;
824 #if LV_LABEL_LONG_TXT_HINT
825     if(label->long_mode != LV_LABEL_LONG_MODE_SCROLL_CIRCULAR &&
826        lv_area_get_height(&txt_coords) >= LV_LABEL_HINT_HEIGHT_LIMIT) {
827         label_draw_dsc.hint = &label->hint;
828     }
829 #endif
830 
831     label_draw_dsc.flag = flag;
832     label_draw_dsc.base.layer = layer;
833     lv_obj_init_draw_label_dsc(obj, LV_PART_MAIN, &label_draw_dsc);
834     lv_bidi_calculate_align(&label_draw_dsc.align, &label_draw_dsc.bidi_dir, label->text);
835 
836     label_draw_dsc.sel_start = lv_label_get_text_selection_start(obj);
837     label_draw_dsc.sel_end = lv_label_get_text_selection_end(obj);
838     if(label_draw_dsc.sel_start != LV_DRAW_LABEL_NO_TXT_SEL && label_draw_dsc.sel_end != LV_DRAW_LABEL_NO_TXT_SEL) {
839         label_draw_dsc.sel_color = lv_obj_get_style_text_color_filtered(obj, LV_PART_SELECTED);
840         label_draw_dsc.sel_bg_color = lv_obj_get_style_bg_color(obj, LV_PART_SELECTED);
841     }
842 
843     /* In SCROLL and SCROLL_CIRCULAR mode the CENTER and RIGHT are pointless, so remove them.
844      * (In addition, they will create misalignment in this situation)*/
845     if((label->long_mode == LV_LABEL_LONG_MODE_SCROLL || label->long_mode == LV_LABEL_LONG_MODE_SCROLL_CIRCULAR) &&
846        (label_draw_dsc.align == LV_TEXT_ALIGN_CENTER || label_draw_dsc.align == LV_TEXT_ALIGN_RIGHT)) {
847         lv_point_t size;
848         lv_text_get_size(&size, label->text, label_draw_dsc.font, label_draw_dsc.letter_space, label_draw_dsc.line_space,
849                          LV_COORD_MAX, flag);
850         if(size.x > lv_area_get_width(&txt_coords)) {
851             label_draw_dsc.align = LV_TEXT_ALIGN_LEFT;
852         }
853     }
854 
855     lv_area_t txt_clip;
856     bool is_common = lv_area_intersect(&txt_clip, &txt_coords, &layer->_clip_area);
857     if(!is_common) {
858         return;
859     }
860 
861     if(label->long_mode == LV_LABEL_LONG_MODE_WRAP) {
862         int32_t s = lv_obj_get_scroll_top(obj);
863         lv_area_move(&txt_coords, 0, -s);
864         txt_coords.y2 = obj->coords.y2;
865     }
866     if(label->long_mode == LV_LABEL_LONG_MODE_SCROLL || label->long_mode == LV_LABEL_LONG_MODE_SCROLL_CIRCULAR) {
867         const lv_area_t clip_area_ori = layer->_clip_area;
868         layer->_clip_area = txt_clip;
869         lv_draw_label(layer, &label_draw_dsc, &txt_coords);
870         layer->_clip_area = clip_area_ori;
871     }
872     else {
873         lv_draw_label(layer, &label_draw_dsc, &txt_coords);
874     }
875 
876     lv_area_t clip_area_ori = layer->_clip_area;
877     layer->_clip_area = txt_clip;
878 
879     if(label->long_mode == LV_LABEL_LONG_MODE_SCROLL_CIRCULAR) {
880         lv_point_t size;
881         lv_text_get_size(&size, label->text, label_draw_dsc.font, label_draw_dsc.letter_space, label_draw_dsc.line_space,
882                          LV_COORD_MAX, flag);
883 
884         /*Draw the text again on label to the original to make a circular effect */
885         if(size.x > lv_area_get_width(&txt_coords)) {
886             label_draw_dsc.ofs_x = label->offset.x + size.x +
887                                    lv_font_get_glyph_width(label_draw_dsc.font, ' ', ' ') * LV_LABEL_WAIT_CHAR_COUNT;
888             label_draw_dsc.ofs_y = label->offset.y;
889 
890             lv_draw_label(layer, &label_draw_dsc, &txt_coords);
891         }
892 
893         /*Draw the text again below the original to make a circular effect */
894         if(size.y > lv_area_get_height(&txt_coords)) {
895             label_draw_dsc.ofs_x = label->offset.x;
896             label_draw_dsc.ofs_y = label->offset.y + size.y + lv_font_get_line_height(label_draw_dsc.font);
897 
898             lv_draw_label(layer, &label_draw_dsc, &txt_coords);
899         }
900     }
901 
902     layer->_clip_area = clip_area_ori;
903 }
904 
overwrite_anim_property(lv_anim_t * dest,const lv_anim_t * src,lv_label_long_mode_t mode)905 static void overwrite_anim_property(lv_anim_t * dest, const lv_anim_t * src, lv_label_long_mode_t mode)
906 {
907     switch(mode) {
908         case LV_LABEL_LONG_MODE_SCROLL:
909             /* If the dest animation is already running, overwrite is not allowed */
910             if(dest->act_time <= 0)
911                 dest->act_time = src->act_time;
912             dest->repeat_cnt = src->repeat_cnt;
913             dest->repeat_delay = src->repeat_delay;
914             dest->completed_cb = src->completed_cb;
915             dest->reverse_delay = src->reverse_delay;
916             break;
917         case LV_LABEL_LONG_MODE_SCROLL_CIRCULAR:
918             /* If the dest animation is already running, overwrite is not allowed */
919             if(dest->act_time <= 0)
920                 dest->act_time = src->act_time;
921             dest->repeat_cnt = src->repeat_cnt;
922             dest->repeat_delay = src->repeat_delay;
923             dest->completed_cb = src->completed_cb;
924             break;
925         default:
926             break;
927     }
928 }
929 
930 /**
931  * Refresh the label with its text stored in its extended data
932  * @param label pointer to a label object
933  */
lv_label_refr_text(lv_obj_t * obj)934 static void lv_label_refr_text(lv_obj_t * obj)
935 {
936     lv_label_t * label = (lv_label_t *)obj;
937     if(label->text == NULL) return;
938 #if LV_LABEL_LONG_TXT_HINT
939     label->hint.line_start = -1; /*The hint is invalid if the text changes*/
940 #endif
941     label->invalid_size_cache = true;
942 
943     lv_area_t txt_coords;
944     lv_obj_get_content_coords(obj, &txt_coords);
945     int32_t max_w         = lv_area_get_width(&txt_coords);
946     const lv_font_t * font   = lv_obj_get_style_text_font(obj, LV_PART_MAIN);
947     int32_t line_space = lv_obj_get_style_text_line_space(obj, LV_PART_MAIN);
948     int32_t letter_space = lv_obj_get_style_text_letter_space(obj, LV_PART_MAIN);
949 
950     /*Calc. the height and longest line*/
951     lv_point_t size;
952     lv_text_flag_t flag = get_label_flags(label);
953 
954     lv_label_revert_dots(obj);
955     lv_text_get_size(&size, label->text, font, letter_space, line_space, max_w, flag);
956 
957     lv_obj_refresh_self_size(obj);
958 
959     /*In scroll mode start an offset animation*/
960     if(label->long_mode == LV_LABEL_LONG_MODE_SCROLL) {
961         const lv_anim_t * anim_template = lv_obj_get_style_anim(obj, LV_PART_MAIN);
962         uint32_t anim_time = lv_obj_get_style_anim_duration(obj, LV_PART_MAIN);
963         if(anim_time == 0) anim_time = LV_LABEL_DEF_SCROLL_SPEED;
964         lv_anim_t a;
965         lv_anim_init(&a);
966         lv_anim_set_var(&a, obj);
967         lv_anim_set_repeat_count(&a, LV_ANIM_REPEAT_INFINITE);
968         lv_anim_set_reverse_delay(&a, LV_LABEL_SCROLL_DELAY);
969         lv_anim_set_repeat_delay(&a, a.reverse_delay);
970 
971         bool hor_anim = false;
972         if(size.x > lv_area_get_width(&txt_coords)) {
973             int32_t start = 0;
974             int32_t end = 0;
975 
976 #if LV_USE_BIDI
977             lv_base_dir_t base_dir = lv_obj_get_style_base_dir(obj, LV_PART_MAIN);
978 
979             if(base_dir == LV_BASE_DIR_AUTO)
980                 base_dir = lv_bidi_detect_base_dir(label->text);
981 
982             if(base_dir == LV_BASE_DIR_RTL) {
983                 start = lv_area_get_width(&txt_coords) - size.x;
984                 end = 0;
985             }
986             else {
987                 start = 0;
988                 end = lv_area_get_width(&txt_coords) - size.x;
989             }
990 #else
991             end = lv_area_get_width(&txt_coords) - size.x;
992 #endif
993 
994             lv_anim_set_values(&a, start, end);
995             lv_anim_set_exec_cb(&a, set_ofs_x_anim);
996 
997             lv_anim_t * anim_cur = lv_anim_get(obj, set_ofs_x_anim);
998             int32_t act_time = 0;
999             bool reverse_play_in_progress = false;
1000             if(anim_cur) {
1001                 act_time = anim_cur->act_time;
1002                 reverse_play_in_progress = anim_cur->reverse_play_in_progress;
1003             }
1004 
1005             int32_t duration_resolved = lv_anim_resolve_speed(anim_time, start, end);
1006             /*To keep the old position*/
1007             if(act_time < duration_resolved) {
1008                 a.act_time = act_time;
1009                 if(reverse_play_in_progress) {
1010                     a.reverse_play_in_progress = 1;
1011                     /*Swap the start and end values*/
1012                     int32_t tmp;
1013                     tmp      = a.start_value;
1014                     a.start_value = a.end_value;
1015                     a.end_value   = tmp;
1016                 }
1017             }
1018 
1019             lv_anim_set_duration(&a, anim_time);
1020             lv_anim_set_reverse_duration(&a, anim_time);
1021 
1022             /*If a template animation exists, overwrite some property*/
1023             if(anim_template)
1024                 overwrite_anim_property(&a, anim_template, label->long_mode);
1025             lv_anim_start(&a);
1026 
1027             /*If a delay is happening, apply the start value manually*/
1028             if(act_time < 0) label->offset.x = start;
1029 
1030             hor_anim = true;
1031         }
1032         else {
1033             /*Delete the offset animation if not required*/
1034             lv_anim_delete(obj, set_ofs_x_anim);
1035             label->offset.x = 0;
1036         }
1037 
1038         if(size.y > lv_area_get_height(&txt_coords) && hor_anim == false) {
1039             lv_anim_set_values(&a, 0, lv_area_get_height(&txt_coords) - size.y - (lv_font_get_line_height(font)));
1040             lv_anim_set_exec_cb(&a, set_ofs_y_anim);
1041 
1042             lv_anim_t * anim_cur = lv_anim_get(obj, set_ofs_y_anim);
1043             int32_t act_time = 0;
1044             bool reverse_play_in_progress = false;
1045             if(anim_cur) {
1046                 act_time = anim_cur->act_time;
1047                 reverse_play_in_progress = anim_cur->reverse_play_in_progress;
1048             }
1049             if(act_time < a.duration) {
1050                 a.act_time = act_time;      /*To keep the old position*/
1051                 if(reverse_play_in_progress) {
1052                     a.reverse_play_in_progress = 1;
1053                     /*Swap the start and end values*/
1054                     int32_t tmp;
1055                     tmp      = a.start_value;
1056                     a.start_value = a.end_value;
1057                     a.end_value   = tmp;
1058                 }
1059             }
1060 
1061             lv_anim_set_duration(&a, anim_time);
1062             lv_anim_set_reverse_duration(&a, anim_time);
1063 
1064             /*If a template animation exists, overwrite some property*/
1065             if(anim_template) {
1066                 overwrite_anim_property(&a, anim_template, label->long_mode);
1067             }
1068             lv_anim_start(&a);
1069         }
1070         else {
1071             /*Delete the offset animation if not required*/
1072             lv_anim_delete(obj, set_ofs_y_anim);
1073             label->offset.y = 0;
1074         }
1075     }
1076     /*In roll inf. mode keep the size but start offset animations*/
1077     else if(label->long_mode == LV_LABEL_LONG_MODE_SCROLL_CIRCULAR) {
1078         const lv_anim_t * anim_template = lv_obj_get_style_anim(obj, LV_PART_MAIN);
1079         uint32_t anim_time = lv_obj_get_style_anim_duration(obj, LV_PART_MAIN);
1080         if(anim_time == 0) anim_time = LV_LABEL_DEF_SCROLL_SPEED;
1081         lv_anim_t a;
1082         lv_anim_init(&a);
1083         lv_anim_set_var(&a, obj);
1084         lv_anim_set_repeat_count(&a, LV_ANIM_REPEAT_INFINITE);
1085 
1086         bool hor_anim = false;
1087         if(size.x > lv_area_get_width(&txt_coords)) {
1088 #if LV_USE_BIDI
1089             int32_t start, end;
1090             lv_base_dir_t base_dir = lv_obj_get_style_base_dir(obj, LV_PART_MAIN);
1091 
1092             if(base_dir == LV_BASE_DIR_AUTO)
1093                 base_dir = lv_bidi_detect_base_dir(label->text);
1094 
1095             if(base_dir == LV_BASE_DIR_RTL) {
1096                 start = -size.x - lv_font_get_glyph_width(font, ' ', ' ') * LV_LABEL_WAIT_CHAR_COUNT;
1097                 end = 0;
1098             }
1099             else {
1100                 start = 0;
1101                 end = -size.x - lv_font_get_glyph_width(font, ' ', ' ') * LV_LABEL_WAIT_CHAR_COUNT;
1102             }
1103 
1104             lv_anim_set_values(&a, start, end);
1105 #else
1106             lv_anim_set_values(&a, 0, -size.x - lv_font_get_glyph_width(font, ' ', ' ') * LV_LABEL_WAIT_CHAR_COUNT);
1107 #endif
1108             lv_anim_set_exec_cb(&a, set_ofs_x_anim);
1109             lv_anim_set_duration(&a, anim_time);
1110 
1111             lv_anim_t * anim_cur = lv_anim_get(obj, set_ofs_x_anim);
1112             int32_t act_time = anim_cur ? anim_cur->act_time : 0;
1113 
1114             /*To keep the old position when the label text is updated mid-scrolling*/
1115             int32_t duration_resolved = lv_anim_resolve_speed(anim_time, a.start_value, a.end_value);
1116             if(act_time < duration_resolved) {
1117                 a.act_time = act_time;
1118             }
1119 
1120             /*If a template animation exists, overwrite some property*/
1121             if(anim_template) {
1122                 overwrite_anim_property(&a, anim_template, label->long_mode);
1123             }
1124 
1125             lv_anim_start(&a);
1126             hor_anim = true;
1127         }
1128         else {
1129             /*Delete the offset animation if not required*/
1130             lv_anim_delete(obj, set_ofs_x_anim);
1131             label->offset.x = 0;
1132         }
1133 
1134         if(size.y > lv_area_get_height(&txt_coords) && hor_anim == false) {
1135             lv_anim_set_values(&a, 0, -size.y - (lv_font_get_line_height(font)));
1136             lv_anim_set_exec_cb(&a, set_ofs_y_anim);
1137             lv_anim_set_duration(&a, anim_time);
1138 
1139             lv_anim_t * anim_cur = lv_anim_get(obj, set_ofs_y_anim);
1140             int32_t act_time = anim_cur ? anim_cur->act_time : 0;
1141 
1142             /*If a template animation exists, overwrite some property*/
1143             if(anim_template) {
1144                 overwrite_anim_property(&a, anim_template, label->long_mode);
1145             }
1146             /*To keep the old position when the label text is updated mid-scrolling*/
1147             else if(act_time < a.duration) {
1148                 a.act_time = act_time;
1149             }
1150 
1151             lv_anim_start(&a);
1152         }
1153         else {
1154             /*Delete the offset animation if not required*/
1155             lv_anim_delete(obj, set_ofs_y_anim);
1156             label->offset.y = 0;
1157         }
1158     }
1159     else if(label->long_mode == LV_LABEL_LONG_MODE_DOTS) {
1160         if(size.y > lv_area_get_height(&txt_coords) && /*Text overflows available area*/
1161            size.y > lv_font_get_line_height(font) && /*No break requested, so no dots required*/
1162            lv_text_get_encoded_length(label->text) > LV_LABEL_DOT_NUM) { /*Do not turn all characters into dots*/
1163             lv_point_t p;
1164             int32_t y_overed;
1165             p.x = lv_area_get_width(&txt_coords) -
1166                   (lv_font_get_glyph_width(font, '.', '.') + letter_space) *
1167                   LV_LABEL_DOT_NUM; /*Shrink with dots*/
1168             p.y = lv_area_get_height(&txt_coords);
1169             y_overed = p.y %
1170                        (lv_font_get_line_height(font) + line_space); /*Round down to the last line*/
1171             if(y_overed >= lv_font_get_line_height(font)) {
1172                 p.y -= y_overed;
1173                 p.y += lv_font_get_line_height(font);
1174             }
1175             else {
1176                 p.y -= y_overed;
1177                 p.y -= line_space;
1178             }
1179 
1180             uint32_t letter_id = lv_label_get_letter_on(obj, &p, false);
1181 
1182             /*Be sure there is space for the dots*/
1183             size_t txt_len = lv_strlen(label->text);
1184             uint32_t byte_id     = lv_text_encoded_get_byte_id(label->text, letter_id);
1185             while(byte_id + LV_LABEL_DOT_NUM > txt_len) {
1186                 lv_text_encoded_prev(label->text, &byte_id);
1187                 letter_id--;
1188             }
1189 
1190             /*Save letters under the dots and replace them with dots*/
1191             lv_label_set_dots(obj, byte_id);
1192         }
1193     }
1194     else if(label->long_mode == LV_LABEL_LONG_MODE_CLIP || label->long_mode == LV_LABEL_LONG_MODE_WRAP) {
1195         /*Do nothing*/
1196     }
1197 
1198     lv_obj_invalidate(obj);
1199 }
1200 
lv_label_revert_dots(lv_obj_t * obj)1201 static void lv_label_revert_dots(lv_obj_t * obj)
1202 {
1203     lv_label_t * label = (lv_label_t *)obj;
1204     if(label->dot_begin != LV_LABEL_DOT_BEGIN_INV) {
1205         for(int i = 0; i < LV_LABEL_DOT_NUM + 1 && label->dot[i]; i++) {
1206             label->text[label->dot_begin + i] = label->dot[i];
1207         }
1208         label->dot_begin = LV_LABEL_DOT_BEGIN_INV;
1209     }
1210 }
1211 
lv_label_set_dots(lv_obj_t * obj,uint32_t dot_begin)1212 static void lv_label_set_dots(lv_obj_t * obj, uint32_t dot_begin)
1213 {
1214     lv_label_t * label = (lv_label_t *)obj;
1215     LV_ASSERT_MSG(label->dot_begin == LV_LABEL_DOT_BEGIN_INV, "Label dots already set");
1216     if(dot_begin != LV_LABEL_DOT_BEGIN_INV) {
1217         /*Save characters*/
1218         lv_strncpy(label->dot, &label->text[dot_begin], LV_LABEL_DOT_NUM + 1);
1219         label->dot_begin = dot_begin;
1220 
1221         /*Overwrite up to LV_LABEL_DOT_NUM + 1 characters with dots and null terminator*/
1222         int i = 0;
1223         for(; i < LV_LABEL_DOT_NUM && label->text[dot_begin + i]; i++) {
1224             label->text[dot_begin + i] = '.';
1225         }
1226         label->text[dot_begin + i] = '\0';
1227     }
1228 }
1229 
set_ofs_x_anim(void * obj,int32_t v)1230 static void set_ofs_x_anim(void * obj, int32_t v)
1231 {
1232     lv_label_t * label = (lv_label_t *)obj;
1233     label->offset.x    = v;
1234     lv_obj_invalidate(obj);
1235 }
1236 
set_ofs_y_anim(void * obj,int32_t v)1237 static void set_ofs_y_anim(void * obj, int32_t v)
1238 {
1239     lv_label_t * label = (lv_label_t *)obj;
1240     label->offset.y    = v;
1241     lv_obj_invalidate(obj);
1242 }
1243 
get_text_length(const char * text)1244 static size_t get_text_length(const char * text)
1245 {
1246     size_t len = 0;
1247 #if LV_USE_ARABIC_PERSIAN_CHARS
1248     len = lv_text_ap_calc_bytes_count(text);
1249 #else
1250     len = lv_strlen(text) + 1;
1251 #endif
1252 
1253     return len;
1254 }
1255 
copy_text_to_label(lv_label_t * label,const char * text)1256 static void copy_text_to_label(lv_label_t * label, const char * text)
1257 {
1258 #if LV_USE_ARABIC_PERSIAN_CHARS
1259     lv_text_ap_proc(text, label->text);
1260 #else
1261     lv_strcpy(label->text, text);
1262 #endif
1263 }
1264 
get_label_flags(lv_label_t * label)1265 static lv_text_flag_t get_label_flags(lv_label_t * label)
1266 {
1267     lv_text_flag_t flag = LV_TEXT_FLAG_NONE;
1268 
1269     if(label->recolor) flag |= LV_TEXT_FLAG_RECOLOR;
1270     if(label->expand) flag |= LV_TEXT_FLAG_EXPAND;
1271 
1272     lv_obj_t * obj = (lv_obj_t *) label;
1273     if(lv_obj_get_style_width(obj, LV_PART_MAIN) == LV_SIZE_CONTENT &&
1274        lv_obj_get_style_max_width(obj, LV_PART_MAIN) == LV_COORD_MAX &&
1275        !obj->w_layout) {
1276         flag |= LV_TEXT_FLAG_FIT;
1277     }
1278 
1279     return flag;
1280 }
1281 
1282 /* Function created because of this pattern be used in multiple functions */
calculate_x_coordinate(int32_t * x,const lv_text_align_t align,const char * txt,uint32_t length,const lv_font_t * font,int32_t letter_space,lv_area_t * txt_coords,lv_text_flag_t flags)1283 static void calculate_x_coordinate(int32_t * x, const lv_text_align_t align, const char * txt, uint32_t length,
1284                                    const lv_font_t * font, int32_t letter_space, lv_area_t * txt_coords, lv_text_flag_t flags)
1285 {
1286     if(align == LV_TEXT_ALIGN_CENTER) {
1287         const int32_t line_w = lv_text_get_width_with_flags(txt, length, font, letter_space, flags);
1288         *x += lv_area_get_width(txt_coords) / 2 - line_w / 2;
1289     }
1290     else if(align == LV_TEXT_ALIGN_RIGHT) {
1291         const int32_t line_w = lv_text_get_width_with_flags(txt, length, font, letter_space, flags);
1292         *x += lv_area_get_width(txt_coords) - line_w;
1293     }
1294     else {
1295         /* Nothing to do */
1296     }
1297 }
1298 
1299 #endif
1300