1 /**
2  * @file lv_label.c
3  *
4  */
5 
6 /*********************
7  *      INCLUDES
8  *********************/
9 #include "lv_label.h"
10 #if LV_USE_LABEL != 0
11 #include "../lv_core/lv_obj.h"
12 #include "../lv_misc/lv_debug.h"
13 #include "../lv_core/lv_group.h"
14 #include "../lv_draw/lv_draw.h"
15 #include "../lv_misc/lv_color.h"
16 #include "../lv_misc/lv_math.h"
17 #include "../lv_misc/lv_bidi.h"
18 #include "../lv_misc/lv_txt_ap.h"
19 #include "../lv_misc/lv_printf.h"
20 #include "../lv_themes/lv_theme.h"
21 
22 /*********************
23  *      DEFINES
24  *********************/
25 #define LV_OBJX_NAME "lv_label"
26 
27 /*Test configurations*/
28 #ifndef LV_LABEL_DEF_SCROLL_SPEED
29     #define LV_LABEL_DEF_SCROLL_SPEED (25)
30 #endif
31 
32 #define LV_LABEL_DOT_END_INV 0xFFFF
33 #define LV_LABEL_HINT_HEIGHT_LIMIT                                                                                     \
34     1024 /*Enable "hint" to buffer info about labels larger than this. (Speed up their drawing)*/
35 
36 /**********************
37  *      TYPEDEFS
38  **********************/
39 
40 /**********************
41  *  STATIC PROTOTYPES
42  **********************/
43 static lv_res_t lv_label_signal(lv_obj_t * label, lv_signal_t sign, void * param);
44 static lv_design_res_t lv_label_design(lv_obj_t * label, const lv_area_t * clip_area, lv_design_mode_t mode);
45 static void lv_label_refr_text(lv_obj_t * label);
46 static void lv_label_revert_dots(lv_obj_t * label);
47 
48 #if LV_USE_ANIMATION
49     static void lv_label_set_offset_x(lv_obj_t * label, lv_coord_t x);
50     static void lv_label_set_offset_y(lv_obj_t * label, lv_coord_t y);
51 #endif
52 
53 static bool lv_label_set_dot_tmp(lv_obj_t * label, char * data, uint32_t len);
54 static char * lv_label_get_dot_tmp(lv_obj_t * label);
55 static void lv_label_dot_tmp_free(lv_obj_t * label);
56 static void get_txt_coords(const lv_obj_t * label, lv_area_t * area);
57 
58 /**********************
59  *  STATIC VARIABLES
60  **********************/
61 static lv_signal_cb_t ancestor_signal;
62 
63 /**********************
64  *      MACROS
65  **********************/
66 
67 /**********************
68  *   GLOBAL FUNCTIONS
69  **********************/
70 
71 /**
72  * Create a label objects
73  * @param par pointer to an object, it will be the parent of the new label
74  * @param copy pointer to a label object, if not NULL then the new object will be copied from it
75  * @return pointer to the created button
76  */
lv_label_create(lv_obj_t * par,const lv_obj_t * copy)77 lv_obj_t * lv_label_create(lv_obj_t * par, const lv_obj_t * copy)
78 {
79     LV_LOG_TRACE("label create started");
80 
81     /*Create a basic object*/
82     lv_obj_t * new_label = lv_obj_create(par, copy);
83     LV_ASSERT_MEM(new_label);
84     if(new_label == NULL) return NULL;
85 
86     if(ancestor_signal == NULL) ancestor_signal = lv_obj_get_signal_cb(new_label);
87 
88     /*Extend the basic object to a label object*/
89     lv_obj_allocate_ext_attr(new_label, sizeof(lv_label_ext_t));
90 
91     lv_label_ext_t * ext = lv_obj_get_ext_attr(new_label);
92     LV_ASSERT_MEM(ext);
93     if(ext == NULL) {
94         lv_obj_del(new_label);
95         return NULL;
96     }
97 
98     ext->text       = NULL;
99     ext->static_txt = 0;
100     ext->recolor    = 0;
101     ext->align      = LV_LABEL_ALIGN_AUTO;
102     ext->dot_end    = LV_LABEL_DOT_END_INV;
103     ext->long_mode  = LV_LABEL_LONG_EXPAND;
104 #if LV_USE_ANIMATION
105     ext->anim_speed = LV_LABEL_DEF_SCROLL_SPEED;
106 #endif
107     ext->offset.x = 0;
108     ext->offset.y = 0;
109 
110 #if LV_LABEL_LONG_TXT_HINT
111     ext->hint.line_start = -1;
112     ext->hint.coord_y    = 0;
113     ext->hint.y          = 0;
114 #endif
115 
116 #if LV_LABEL_TEXT_SEL
117     ext->sel_start = LV_DRAW_LABEL_NO_TXT_SEL;
118     ext->sel_end   = LV_DRAW_LABEL_NO_TXT_SEL;
119 #endif
120     ext->dot.tmp_ptr   = NULL;
121     ext->dot_tmp_alloc = 0;
122 
123 
124     lv_obj_set_design_cb(new_label, lv_label_design);
125     lv_obj_set_signal_cb(new_label, lv_label_signal);
126 
127     /*Init the new label*/
128     if(copy == NULL) {
129         lv_theme_apply(new_label, LV_THEME_LABEL);
130         lv_obj_set_click(new_label, false);
131         lv_label_set_long_mode(new_label, LV_LABEL_LONG_EXPAND);
132         lv_label_set_text(new_label, "Text");
133     }
134     /*Copy 'copy' if not NULL*/
135     else {
136         lv_label_ext_t * copy_ext = lv_obj_get_ext_attr(copy);
137         lv_label_set_long_mode(new_label, lv_label_get_long_mode(copy));
138         lv_label_set_recolor(new_label, lv_label_get_recolor(copy));
139         lv_label_set_align(new_label, lv_label_get_align(copy));
140         if(copy_ext->static_txt == 0)
141             lv_label_set_text(new_label, lv_label_get_text(copy));
142         else
143             lv_label_set_text_static(new_label, lv_label_get_text(copy));
144 
145         /*In DOT mode save the text byte-to-byte because a '\0' can be in the middle*/
146         if(copy_ext->long_mode == LV_LABEL_LONG_DOT) {
147             ext->text = lv_mem_realloc(ext->text, _lv_mem_get_size(copy_ext->text));
148             LV_ASSERT_MEM(ext->text);
149             if(ext->text == NULL) return NULL;
150             _lv_memcpy(ext->text, copy_ext->text, _lv_mem_get_size(copy_ext->text));
151         }
152 
153         if(copy_ext->dot_tmp_alloc && copy_ext->dot.tmp_ptr) {
154             uint32_t len = (uint32_t)strlen(copy_ext->dot.tmp_ptr);
155             lv_label_set_dot_tmp(new_label, ext->dot.tmp_ptr, len);
156         }
157         else {
158             _lv_memcpy(ext->dot.tmp, copy_ext->dot.tmp, sizeof(ext->dot.tmp));
159         }
160         ext->dot_tmp_alloc = copy_ext->dot_tmp_alloc;
161         ext->dot_end       = copy_ext->dot_end;
162 
163         /*Refresh the style with new signal function*/
164         lv_obj_refresh_style(new_label, LV_OBJ_PART_ALL, LV_STYLE_PROP_ALL);
165     }
166 
167     LV_LOG_INFO("label created");
168 
169     return new_label;
170 }
171 
172 /*=====================
173  * Setter functions
174  *====================*/
175 
176 /**
177  * Set a new text for a label. Memory will be allocated to store the text by the label.
178  * @param label pointer to a label object
179  * @param text '\0' terminated character string. NULL to refresh with the current text.
180  */
lv_label_set_text(lv_obj_t * label,const char * text)181 void lv_label_set_text(lv_obj_t * label, const char * text)
182 {
183     LV_ASSERT_OBJ(label, LV_OBJX_NAME);
184 
185     lv_obj_invalidate(label);
186 
187     lv_label_ext_t * ext = lv_obj_get_ext_attr(label);
188 
189     /*If text is NULL then refresh */
190     if(text == NULL) {
191         lv_label_refr_text(label);
192         return;
193     }
194 
195     LV_ASSERT_STR(text);
196 
197     if(ext->text == text && ext->static_txt == 0) {
198         /*If set its own text then reallocate it (maybe its size changed)*/
199 #if LV_USE_ARABIC_PERSIAN_CHARS
200         /*Get the size of the text and process it*/
201         size_t len = _lv_txt_ap_calc_bytes_cnt(text);
202 
203         ext->text = lv_mem_realloc(ext->text, len);
204         LV_ASSERT_MEM(ext->text);
205         if(ext->text == NULL) return;
206 
207         _lv_txt_ap_proc(ext->text, ext->text);
208 #else
209         ext->text = lv_mem_realloc(ext->text, strlen(ext->text) + 1);
210 #endif
211 
212         LV_ASSERT_MEM(ext->text);
213         if(ext->text == NULL) return;
214     }
215     else {
216         /*Free the old text*/
217         if(ext->text != NULL && ext->static_txt == 0) {
218             lv_mem_free(ext->text);
219             ext->text = NULL;
220         }
221 
222 #if LV_USE_ARABIC_PERSIAN_CHARS
223         /*Get the size of the text and process it*/
224         size_t len = _lv_txt_ap_calc_bytes_cnt(text);
225 
226         ext->text = lv_mem_alloc(len);
227         LV_ASSERT_MEM(ext->text);
228         if(ext->text == NULL) return;
229 
230         _lv_txt_ap_proc(text, ext->text);
231 #else
232         /*Get the size of the text*/
233         size_t len = strlen(text) + 1;
234 
235         /*Allocate space for the new text*/
236         ext->text = lv_mem_alloc(len);
237         LV_ASSERT_MEM(ext->text);
238         if(ext->text == NULL) return;
239         strcpy(ext->text, text);
240 #endif
241 
242         /*Now the text is dynamically allocated*/
243         ext->static_txt = 0;
244     }
245 
246     lv_label_refr_text(label);
247 }
248 
249 /**
250  * Set a new formatted text for a label. Memory will be allocated to store the text by the label.
251  * @param label pointer to a label object
252  * @param fmt `printf`-like format
253  */
lv_label_set_text_fmt(lv_obj_t * label,const char * fmt,...)254 void lv_label_set_text_fmt(lv_obj_t * label, const char * fmt, ...)
255 {
256     LV_ASSERT_OBJ(label, LV_OBJX_NAME);
257     LV_ASSERT_STR(fmt);
258 
259     lv_obj_invalidate(label);
260 
261     lv_label_ext_t * ext = lv_obj_get_ext_attr(label);
262 
263     /*If text is NULL then refresh */
264     if(fmt == NULL) {
265         lv_label_refr_text(label);
266         return;
267     }
268 
269     if(ext->text != NULL && ext->static_txt == 0) {
270         lv_mem_free(ext->text);
271         ext->text = NULL;
272     }
273 
274 
275     va_list ap, ap2;
276     va_start(ap, fmt);
277     va_copy(ap2, ap);
278 
279     /*Allocate space for the new text by using trick from C99 standard section 7.19.6.12 */
280     uint32_t len = lv_vsnprintf(NULL, 0, fmt, ap);
281     va_end(ap);
282 
283 #if LV_USE_ARABIC_PERSIAN_CHARS
284     /*Put together the text according to the format string*/
285     char * raw_txt = _lv_mem_buf_get(len + 1);
286     LV_ASSERT_MEM(raw_txt);
287     if(raw_txt == NULL) {
288         va_end(ap2);
289         return;
290     }
291 
292     lv_vsnprintf(raw_txt, len + 1, fmt, ap2);
293 
294     /*Get the size of the Arabic text and process it*/
295     size_t len_ap = _lv_txt_ap_calc_bytes_cnt(raw_txt);
296     ext->text = lv_mem_alloc(len_ap + 1);
297     LV_ASSERT_MEM(ext->text);
298     if(ext->text == NULL) {
299         va_end(ap2);
300         return;
301     }
302     _lv_txt_ap_proc(raw_txt, ext->text);
303 
304     _lv_mem_buf_release(raw_txt);
305 #else
306     ext->text = lv_mem_alloc(len + 1);
307     LV_ASSERT_MEM(ext->text);
308     if(ext->text == NULL) {
309         va_end(ap2);
310         return;
311     }
312     ext->text[len - 1] = 0; /* Ensure NULL termination */
313 
314     lv_vsnprintf(ext->text, len + 1, fmt, ap2);
315 #endif
316 
317     va_end(ap2);
318     ext->static_txt = 0; /*Now the text is dynamically allocated*/
319 
320     lv_label_refr_text(label);
321 }
322 
323 /**
324  * Set a static text. It will not be saved by the label so the 'text' variable
325  * has to be 'alive' while the label exist.
326  * @param label pointer to a label object
327  * @param text pointer to a text. NULL to refresh with the current text.
328  */
lv_label_set_text_static(lv_obj_t * label,const char * text)329 void lv_label_set_text_static(lv_obj_t * label, const char * text)
330 {
331     LV_ASSERT_OBJ(label, LV_OBJX_NAME);
332 
333     lv_label_ext_t * ext = lv_obj_get_ext_attr(label);
334     if(ext->static_txt == 0 && ext->text != NULL) {
335         lv_mem_free(ext->text);
336         ext->text = NULL;
337     }
338 
339     if(text != NULL) {
340         ext->static_txt = 1;
341         ext->text       = (char *)text;
342     }
343 
344     lv_label_refr_text(label);
345 }
346 
347 /**
348  * Set the behavior of the label with longer text then the object size
349  * @param label pointer to a label object
350  * @param long_mode the new mode from 'lv_label_long_mode' enum.
351  *                  In LV_LONG_BREAK/LONG/ROLL the size of the label should be set AFTER this
352  * function
353  */
lv_label_set_long_mode(lv_obj_t * label,lv_label_long_mode_t long_mode)354 void lv_label_set_long_mode(lv_obj_t * label, lv_label_long_mode_t long_mode)
355 {
356     LV_ASSERT_OBJ(label, LV_OBJX_NAME);
357 
358     lv_label_ext_t * ext = lv_obj_get_ext_attr(label);
359 
360 #if LV_USE_ANIMATION
361     /*Delete the old animation (if exists)*/
362     lv_anim_del(label, (lv_anim_exec_xcb_t)lv_obj_set_x);
363     lv_anim_del(label, (lv_anim_exec_xcb_t)lv_obj_set_y);
364     lv_anim_del(label, (lv_anim_exec_xcb_t)lv_label_set_offset_x);
365     lv_anim_del(label, (lv_anim_exec_xcb_t)lv_label_set_offset_y);
366 #endif
367     ext->offset.x = 0;
368     ext->offset.y = 0;
369 
370     if(long_mode == LV_LABEL_LONG_SROLL || long_mode == LV_LABEL_LONG_SROLL_CIRC || long_mode == LV_LABEL_LONG_CROP)
371         ext->expand = 1;
372     else
373         ext->expand = 0;
374 
375     /*Restore the character under the dots*/
376     if(ext->long_mode == LV_LABEL_LONG_DOT && ext->dot_end != LV_LABEL_DOT_END_INV) {
377         lv_label_revert_dots(label);
378     }
379 
380     ext->long_mode = long_mode;
381     lv_label_refr_text(label);
382 }
383 
384 /**
385  * Set the align of the label (left or center)
386  * @param label pointer to a label object
387  * @param align 'LV_LABEL_ALIGN_LEFT' or 'LV_LABEL_ALIGN_LEFT'
388  */
lv_label_set_align(lv_obj_t * label,lv_label_align_t align)389 void lv_label_set_align(lv_obj_t * label, lv_label_align_t align)
390 {
391     LV_ASSERT_OBJ(label, LV_OBJX_NAME);
392 
393     lv_label_ext_t * ext = lv_obj_get_ext_attr(label);
394     if(ext->align == align) return;
395 
396     ext->align = align;
397 
398     lv_obj_invalidate(label); /*Enough to invalidate because alignment is only drawing related
399                                  (lv_refr_label_text() not required)*/
400 }
401 
402 /**
403  * Enable the recoloring by in-line commands
404  * @param label pointer to a label object
405  * @param en true: enable recoloring, false: disable
406  */
lv_label_set_recolor(lv_obj_t * label,bool en)407 void lv_label_set_recolor(lv_obj_t * label, bool en)
408 {
409     LV_ASSERT_OBJ(label, LV_OBJX_NAME);
410 
411     lv_label_ext_t * ext = lv_obj_get_ext_attr(label);
412     if(ext->recolor == en) return;
413 
414     ext->recolor = en == false ? 0 : 1;
415 
416     lv_label_refr_text(label); /*Refresh the text because the potential color codes in text needs to
417                                   be hided or revealed*/
418 }
419 
420 /**
421  * Set the label's animation speed in LV_LABEL_LONG_SROLL/SCROLL_CIRC modes
422  * @param label pointer to a label object
423  * @param anim_speed speed of animation in px/sec unit
424  */
lv_label_set_anim_speed(lv_obj_t * label,uint16_t anim_speed)425 void lv_label_set_anim_speed(lv_obj_t * label, uint16_t anim_speed)
426 {
427     LV_ASSERT_OBJ(label, LV_OBJX_NAME);
428 
429 #if LV_USE_ANIMATION
430     lv_label_ext_t * ext = lv_obj_get_ext_attr(label);
431     if(ext->anim_speed == anim_speed) return;
432 
433     ext->anim_speed = anim_speed;
434 
435     if(ext->long_mode == LV_LABEL_LONG_SROLL || ext->long_mode == LV_LABEL_LONG_SROLL_CIRC) {
436         lv_label_refr_text(label);
437     }
438 #else
439     (void)label;      /*Unused*/
440     (void)anim_speed; /*Unused*/
441 #endif
442 }
443 
lv_label_set_text_sel_start(lv_obj_t * label,uint32_t index)444 void lv_label_set_text_sel_start(lv_obj_t * label, uint32_t index)
445 {
446     LV_ASSERT_OBJ(label, LV_OBJX_NAME);
447 
448 #if LV_LABEL_TEXT_SEL
449     lv_label_ext_t * ext = lv_obj_get_ext_attr(label);
450     ext->sel_start   = index;
451     lv_obj_invalidate(label);
452 #else
453     (void)label;    /*Unused*/
454     (void)index;    /*Unused*/
455 #endif
456 }
457 
lv_label_set_text_sel_end(lv_obj_t * label,uint32_t index)458 void lv_label_set_text_sel_end(lv_obj_t * label, uint32_t index)
459 {
460     LV_ASSERT_OBJ(label, LV_OBJX_NAME);
461 
462 #if LV_LABEL_TEXT_SEL
463     lv_label_ext_t * ext = lv_obj_get_ext_attr(label);
464     ext->sel_end     = index;
465     lv_obj_invalidate(label);
466 #else
467     (void)label;    /*Unused*/
468     (void)index;    /*Unused*/
469 #endif
470 }
471 
472 /*=====================
473  * Getter functions
474  *====================*/
475 
476 /**
477  * Get the text of a label
478  * @param label pointer to a label object
479  * @return the text of the label
480  */
lv_label_get_text(const lv_obj_t * label)481 char * lv_label_get_text(const lv_obj_t * label)
482 {
483     LV_ASSERT_OBJ(label, LV_OBJX_NAME);
484 
485     lv_label_ext_t * ext = lv_obj_get_ext_attr(label);
486 
487     return ext->text;
488 }
489 
490 /**
491  * Get the long mode of a label
492  * @param label pointer to a label object
493  * @return the long mode
494  */
lv_label_get_long_mode(const lv_obj_t * label)495 lv_label_long_mode_t lv_label_get_long_mode(const lv_obj_t * label)
496 {
497     LV_ASSERT_OBJ(label, LV_OBJX_NAME);
498 
499     lv_label_ext_t * ext = lv_obj_get_ext_attr(label);
500     return ext->long_mode;
501 }
502 
503 /**
504  * Get the align attribute
505  * @param label pointer to a label object
506  * @return LV_LABEL_ALIGN_LEFT or LV_LABEL_ALIGN_CENTER
507  */
lv_label_get_align(const lv_obj_t * label)508 lv_label_align_t lv_label_get_align(const lv_obj_t * label)
509 {
510     LV_ASSERT_OBJ(label, LV_OBJX_NAME);
511 
512     lv_label_ext_t * ext = lv_obj_get_ext_attr(label);
513 
514     lv_label_align_t align = ext->align;
515 
516     if(align == LV_LABEL_ALIGN_AUTO) {
517 #if LV_USE_BIDI
518         lv_bidi_dir_t base_dir = lv_obj_get_base_dir(label);
519         if(base_dir == LV_BIDI_DIR_AUTO) base_dir = _lv_bidi_detect_base_dir(ext->text);
520 
521         if(base_dir == LV_BIDI_DIR_LTR) align = LV_LABEL_ALIGN_LEFT;
522         else if(base_dir == LV_BIDI_DIR_RTL) align = LV_LABEL_ALIGN_RIGHT;
523 #else
524         align = LV_LABEL_ALIGN_LEFT;
525 #endif
526     }
527 
528     return align;
529 }
530 
531 /**
532  * Get the recoloring attribute
533  * @param label pointer to a label object
534  * @return true: recoloring is enabled, false: disable
535  */
lv_label_get_recolor(const lv_obj_t * label)536 bool lv_label_get_recolor(const lv_obj_t * label)
537 {
538     LV_ASSERT_OBJ(label, LV_OBJX_NAME);
539 
540     lv_label_ext_t * ext = lv_obj_get_ext_attr(label);
541     return ext->recolor == 0 ? false : true;
542 }
543 
544 /**
545  * Get the label's animation speed in LV_LABEL_LONG_ROLL and SCROLL modes
546  * @param label pointer to a label object
547  * @return speed of animation in px/sec unit
548  */
lv_label_get_anim_speed(const lv_obj_t * label)549 uint16_t lv_label_get_anim_speed(const lv_obj_t * label)
550 {
551     LV_ASSERT_OBJ(label, LV_OBJX_NAME);
552 
553 #if LV_USE_ANIMATION
554     lv_label_ext_t * ext = lv_obj_get_ext_attr(label);
555     return ext->anim_speed;
556 #else
557     (void)label;      /*Unused*/
558     return 0;
559 #endif
560 }
561 
562 /**
563  * Get the relative x and y coordinates of a letter
564  * @param label pointer to a label object
565  * @param index index of the letter [0 ... text length]. Expressed in character index, not byte
566  * index (different in UTF-8)
567  * @param pos store the result here (E.g. index = 0 gives 0;0 coordinates)
568  */
lv_label_get_letter_pos(const lv_obj_t * label,uint32_t char_id,lv_point_t * pos)569 void lv_label_get_letter_pos(const lv_obj_t * label, uint32_t char_id, lv_point_t * pos)
570 {
571     LV_ASSERT_OBJ(label, LV_OBJX_NAME);
572     LV_ASSERT_NULL(pos);
573 
574     const char * txt         = lv_label_get_text(label);
575     lv_label_align_t align = lv_label_get_align(label);
576 
577     if(txt[0] == '\0') {
578         pos->y = 0;
579         switch(align) {
580             case LV_LABEL_ALIGN_LEFT:
581                 pos->x = 0;
582                 break;
583             case LV_LABEL_ALIGN_RIGHT:
584                 pos->x = lv_obj_get_width(label);
585                 break;
586             case LV_LABEL_ALIGN_CENTER:
587                 pos->x = lv_obj_get_width(label) / 2;
588                 break;
589         }
590         return;
591     }
592 
593     lv_area_t txt_coords;
594     get_txt_coords(label, &txt_coords);
595 
596     lv_label_ext_t * ext     = lv_obj_get_ext_attr(label);
597     uint32_t line_start      = 0;
598     uint32_t new_line_start  = 0;
599     lv_coord_t max_w         = lv_area_get_width(&txt_coords);
600     const lv_font_t * font   = lv_obj_get_style_text_font(label, LV_LABEL_PART_MAIN);
601     lv_style_int_t line_space = lv_obj_get_style_text_line_space(label, LV_LABEL_PART_MAIN);
602     lv_style_int_t letter_space = lv_obj_get_style_text_letter_space(label, LV_LABEL_PART_MAIN);
603     lv_coord_t letter_height    = lv_font_get_line_height(font);
604     lv_coord_t y             = 0;
605     lv_txt_flag_t flag       = LV_TXT_FLAG_NONE;
606 
607     if(ext->recolor != 0) flag |= LV_TXT_FLAG_RECOLOR;
608     if(ext->expand != 0) flag |= LV_TXT_FLAG_EXPAND;
609     if(ext->long_mode == LV_LABEL_LONG_EXPAND) flag |= LV_TXT_FLAG_FIT;
610 
611     if(align == LV_LABEL_ALIGN_CENTER) flag |= LV_TXT_FLAG_CENTER;
612     if(align == LV_LABEL_ALIGN_RIGHT) flag |= LV_TXT_FLAG_RIGHT;
613 
614     uint32_t byte_id = _lv_txt_encoded_get_byte_id(txt, char_id);
615 
616     /*Search the line of the index letter */;
617     while(txt[new_line_start] != '\0') {
618         new_line_start += _lv_txt_get_next_line(&txt[line_start], font, letter_space, max_w, flag);
619         if(byte_id < new_line_start || txt[new_line_start] == '\0')
620             break; /*The line of 'index' letter begins at 'line_start'*/
621 
622         y += letter_height + line_space;
623         line_start = new_line_start;
624     }
625 
626     /*If the last character is line break then go to the next line*/
627     if(byte_id > 0) {
628         if((txt[byte_id - 1] == '\n' || txt[byte_id - 1] == '\r') && txt[byte_id] == '\0') {
629             y += letter_height + line_space;
630             line_start = byte_id;
631         }
632     }
633 
634     const char * bidi_txt;
635     uint32_t visual_byte_pos;
636 #if LV_USE_BIDI
637     char * mutable_bidi_txt = NULL;
638     /*Handle Bidi*/
639     if(new_line_start == byte_id) {
640         visual_byte_pos = byte_id - line_start;
641         bidi_txt =  &txt[line_start];
642     }
643     else {
644         uint32_t line_char_id = _lv_txt_encoded_get_char_id(&txt[line_start], byte_id - line_start);
645 
646         bool is_rtl;
647         uint32_t visual_char_pos = _lv_bidi_get_visual_pos(&txt[line_start], &mutable_bidi_txt, new_line_start - line_start,
648                                                            lv_obj_get_base_dir(label), line_char_id, &is_rtl);
649         bidi_txt = mutable_bidi_txt;
650         if(is_rtl) visual_char_pos++;
651 
652         visual_byte_pos = _lv_txt_encoded_get_byte_id(bidi_txt, visual_char_pos);
653     }
654 #else
655     bidi_txt = &txt[line_start];
656     visual_byte_pos = byte_id - line_start;
657 #endif
658 
659 
660     /*Calculate the x coordinate*/
661     lv_coord_t x = _lv_txt_get_width(bidi_txt, visual_byte_pos, font, letter_space, flag);
662     if(char_id != line_start) x += letter_space;
663 
664     if(align == LV_LABEL_ALIGN_CENTER) {
665         lv_coord_t line_w;
666         line_w = _lv_txt_get_width(bidi_txt, new_line_start - line_start, font, letter_space, flag);
667         x += lv_area_get_width(&txt_coords) / 2 - line_w / 2;
668 
669     }
670     else if(align == LV_LABEL_ALIGN_RIGHT) {
671         lv_coord_t line_w;
672         line_w = _lv_txt_get_width(bidi_txt, new_line_start - line_start, font, letter_space, flag);
673 
674         x += lv_area_get_width(&txt_coords) - line_w;
675     }
676     pos->x = x;
677     pos->y = y;
678 
679 #if LV_USE_BIDI
680     if(mutable_bidi_txt) _lv_mem_buf_release(mutable_bidi_txt);
681 #endif
682 }
683 
684 /**
685  * Get the index of letter on a relative point of a label
686  * @param label pointer to label object
687  * @param pos pointer to point with coordinates on a the label
688  * @return the index of the letter on the 'pos_p' point (E.g. on 0;0 is the 0. letter)
689  * Expressed in character index and not byte index (different in UTF-8)
690  */
lv_label_get_letter_on(const lv_obj_t * label,lv_point_t * pos_in)691 uint32_t lv_label_get_letter_on(const lv_obj_t * label, lv_point_t * pos_in)
692 {
693     LV_ASSERT_OBJ(label, LV_OBJX_NAME);
694     LV_ASSERT_NULL(pos_in);
695 
696     lv_point_t pos;
697     pos.x = pos_in->x - lv_obj_get_style_pad_left(label, LV_LABEL_PART_MAIN);
698     pos.y = pos_in->y - lv_obj_get_style_pad_top(label, LV_LABEL_PART_MAIN);
699 
700     lv_area_t txt_coords;
701     get_txt_coords(label, &txt_coords);
702     const char * txt         = lv_label_get_text(label);
703     lv_label_ext_t * ext     = lv_obj_get_ext_attr(label);
704     uint32_t line_start      = 0;
705     uint32_t new_line_start  = 0;
706     lv_coord_t max_w         = lv_area_get_width(&txt_coords);
707     const lv_font_t * font   = lv_obj_get_style_text_font(label, LV_LABEL_PART_MAIN);
708     lv_style_int_t line_space = lv_obj_get_style_text_line_space(label, LV_LABEL_PART_MAIN);
709     lv_style_int_t letter_space = lv_obj_get_style_text_letter_space(label, LV_LABEL_PART_MAIN);
710     lv_coord_t letter_height    = lv_font_get_line_height(font);
711     lv_coord_t y             = 0;
712     lv_txt_flag_t flag       = LV_TXT_FLAG_NONE;
713     uint32_t logical_pos;
714     char * bidi_txt;
715 
716     if(ext->recolor != 0) flag |= LV_TXT_FLAG_RECOLOR;
717     if(ext->expand != 0) flag |= LV_TXT_FLAG_EXPAND;
718     if(ext->long_mode == LV_LABEL_LONG_EXPAND) flag |= LV_TXT_FLAG_FIT;
719 
720     lv_label_align_t align = lv_label_get_align(label);
721     if(align == LV_LABEL_ALIGN_CENTER) flag |= LV_TXT_FLAG_CENTER;
722     if(align == LV_LABEL_ALIGN_RIGHT) flag |= LV_TXT_FLAG_RIGHT;
723 
724     /*Search the line of the index letter */;
725     while(txt[line_start] != '\0') {
726         new_line_start += _lv_txt_get_next_line(&txt[line_start], font, letter_space, max_w, flag);
727 
728         if(pos.y <= y + letter_height) {
729             /*The line is found (stored in 'line_start')*/
730             /* Include the NULL terminator in the last line */
731             uint32_t tmp = new_line_start;
732             uint32_t letter;
733             letter = _lv_txt_encoded_prev(txt, &tmp);
734             if(letter != '\n' && txt[new_line_start] == '\0') new_line_start++;
735             break;
736         }
737         y += letter_height + line_space;
738 
739         line_start = new_line_start;
740     }
741 
742 #if LV_USE_BIDI
743     bidi_txt = _lv_mem_buf_get(new_line_start - line_start + 1);
744     uint32_t txt_len = new_line_start - line_start;
745     if(new_line_start > 0 && txt[new_line_start - 1] == '\0' && txt_len > 0) txt_len--;
746     _lv_bidi_process_paragraph(txt + line_start, bidi_txt, txt_len, lv_obj_get_base_dir(label), NULL, 0);
747 #else
748     bidi_txt = (char *)txt + line_start;
749 #endif
750 
751     /*Calculate the x coordinate*/
752     lv_coord_t x = 0;
753     if(align == LV_LABEL_ALIGN_CENTER) {
754         lv_coord_t line_w;
755         line_w = _lv_txt_get_width(bidi_txt, new_line_start - line_start, font, letter_space, flag);
756         x += lv_area_get_width(&txt_coords) / 2 - line_w / 2;
757     }
758     else if(align == LV_LABEL_ALIGN_RIGHT) {
759         lv_coord_t line_w;
760         line_w = _lv_txt_get_width(bidi_txt, new_line_start - line_start, font, letter_space, flag);
761         x += lv_area_get_width(&txt_coords) - line_w;
762     }
763 
764     lv_txt_cmd_state_t cmd_state = LV_TXT_CMD_STATE_WAIT;
765 
766     uint32_t i = 0;
767     uint32_t i_act = i;
768 
769     if(new_line_start > 0) {
770         while(i + line_start < new_line_start) {
771             /* Get the current letter.*/
772             uint32_t letter = _lv_txt_encoded_next(bidi_txt, &i);
773 
774             /*Get the next letter too for kerning*/
775             uint32_t letter_next = _lv_txt_encoded_next(&bidi_txt[i], NULL);
776 
777             /*Handle the recolor command*/
778             if((flag & LV_TXT_FLAG_RECOLOR) != 0) {
779                 if(_lv_txt_is_cmd(&cmd_state, bidi_txt[i]) != false) {
780                     continue; /*Skip the letter is it is part of a command*/
781                 }
782             }
783 
784             lv_coord_t gw = lv_font_get_glyph_width(font, letter, letter_next);
785 
786             /*Finish if the x position or the last char of the next line is reached*/
787             if(pos.x < x + gw || i + line_start == new_line_start ||  txt[i_act + line_start] == '\0') {
788                 i = i_act;
789                 break;
790             }
791             x += gw;
792             x += letter_space;
793             i_act = i;
794         }
795     }
796 
797 #if LV_USE_BIDI
798     /*Handle Bidi*/
799     uint32_t cid = _lv_txt_encoded_get_char_id(bidi_txt, i);
800     if(txt[line_start + cid] == '\0') {
801         logical_pos = i;
802     }
803     else {
804         bool is_rtl;
805         logical_pos = _lv_bidi_get_logical_pos(&txt[line_start], NULL,
806                                                txt_len, lv_obj_get_base_dir(label), cid, &is_rtl);
807         if(is_rtl) logical_pos++;
808         _lv_mem_buf_release(bidi_txt);
809     }
810 #else
811     logical_pos = _lv_txt_encoded_get_char_id(bidi_txt, i);
812 #endif
813 
814     return  logical_pos + _lv_txt_encoded_get_char_id(txt, line_start);
815 }
816 
817 /**
818  * @brief Get the selection start index.
819  * @param label pointer to a label object.
820  * @return selection start index. `LV_LABEL_TXT_SEL_OFF` if nothing is selected.
821  */
lv_label_get_text_sel_start(const lv_obj_t * label)822 uint32_t lv_label_get_text_sel_start(const lv_obj_t * label)
823 {
824     LV_ASSERT_OBJ(label, LV_OBJX_NAME);
825 
826 #if LV_LABEL_TEXT_SEL
827     lv_label_ext_t * ext = lv_obj_get_ext_attr(label);
828     return ext->sel_start;
829 
830 #else
831     (void)label;    /*Unused*/
832     return LV_LABEL_TEXT_SEL_OFF;
833 #endif
834 }
835 
836 /**
837  * @brief Get the selection end index.
838  * @param label pointer to a label object.
839  * @return selection end index. `LV_LABEL_TXT_SEL_OFF` if nothing is selected.
840  */
lv_label_get_text_sel_end(const lv_obj_t * label)841 uint32_t lv_label_get_text_sel_end(const lv_obj_t * label)
842 {
843     LV_ASSERT_OBJ(label, LV_OBJX_NAME);
844 
845 #if LV_LABEL_TEXT_SEL
846     lv_label_ext_t * ext = lv_obj_get_ext_attr(label);
847     return ext->sel_end;
848 #else
849     (void)label; /*Unused*/
850     return LV_LABEL_TEXT_SEL_OFF;
851 #endif
852 }
853 
854 /**
855  * Check if a character is drawn under a point.
856  * @param label Label object
857  * @param pos Point to check for character under
858  * @return whether a character is drawn under the point
859  */
lv_label_is_char_under_pos(const lv_obj_t * label,lv_point_t * pos)860 bool lv_label_is_char_under_pos(const lv_obj_t * label, lv_point_t * pos)
861 {
862     LV_ASSERT_OBJ(label, LV_OBJX_NAME);
863     LV_ASSERT_NULL(pos);
864 
865     lv_area_t txt_coords;
866     get_txt_coords(label, &txt_coords);
867     const char * txt         = lv_label_get_text(label);
868     lv_label_ext_t * ext     = lv_obj_get_ext_attr(label);
869     uint32_t line_start      = 0;
870     uint32_t new_line_start  = 0;
871     lv_coord_t max_w         = lv_area_get_width(&txt_coords);
872     const lv_font_t * font   = lv_obj_get_style_text_font(label, LV_LABEL_PART_MAIN);
873     lv_style_int_t line_space = lv_obj_get_style_text_line_space(label, LV_LABEL_PART_MAIN);
874     lv_style_int_t letter_space = lv_obj_get_style_text_letter_space(label, LV_LABEL_PART_MAIN);
875     lv_coord_t letter_height    = lv_font_get_line_height(font);
876     lv_coord_t y             = 0;
877     lv_txt_flag_t flag       = LV_TXT_FLAG_NONE;
878     lv_label_align_t align = lv_label_get_align(label);
879 
880     if(ext->recolor != 0) flag |= LV_TXT_FLAG_RECOLOR;
881     if(ext->expand != 0) flag |= LV_TXT_FLAG_EXPAND;
882     if(ext->long_mode == LV_LABEL_LONG_EXPAND) flag |= LV_TXT_FLAG_FIT;
883     if(align == LV_LABEL_ALIGN_CENTER) flag |= LV_TXT_FLAG_CENTER;
884 
885     /*Search the line of the index letter */;
886     while(txt[line_start] != '\0') {
887         new_line_start += _lv_txt_get_next_line(&txt[line_start], font, letter_space, max_w, flag);
888 
889         if(pos->y <= y + letter_height) break; /*The line is found (stored in 'line_start')*/
890         y += letter_height + line_space;
891 
892         line_start = new_line_start;
893     }
894 
895     /*Calculate the x coordinate*/
896     lv_coord_t x      = 0;
897     lv_coord_t last_x = 0;
898     if(align == LV_LABEL_ALIGN_CENTER) {
899         lv_coord_t line_w;
900         line_w = _lv_txt_get_width(&txt[line_start], new_line_start - line_start, font, letter_space, flag);
901         x += lv_area_get_width(&txt_coords) / 2 - line_w / 2;
902     }
903     else if(align == LV_LABEL_ALIGN_RIGHT) {
904         lv_coord_t line_w;
905         line_w = _lv_txt_get_width(&txt[line_start], new_line_start - line_start, font, letter_space, flag);
906         x += lv_area_get_width(&txt_coords) - line_w;
907     }
908 
909     lv_txt_cmd_state_t cmd_state = LV_TXT_CMD_STATE_WAIT;
910 
911     uint32_t i           = line_start;
912     uint32_t i_current   = i;
913     uint32_t letter      = '\0';
914     uint32_t letter_next = '\0';
915 
916     if(new_line_start > 0) {
917         while(i <= new_line_start - 1) {
918             /* Get the current letter
919              * Be careful 'i' already points to the next character */
920             letter = _lv_txt_encoded_next(txt, &i);
921 
922             /*Get the next letter for kerning*/
923             letter_next = _lv_txt_encoded_next(&txt[i], NULL);
924 
925             /*Handle the recolor command*/
926             if((flag & LV_TXT_FLAG_RECOLOR) != 0) {
927                 if(_lv_txt_is_cmd(&cmd_state, txt[i]) != false) {
928                     continue; /*Skip the letter is it is part of a command*/
929                 }
930             }
931             last_x = x;
932             x += lv_font_get_glyph_width(font, letter, letter_next);
933             if(pos->x < x) {
934                 i = i_current;
935                 break;
936             }
937             x += letter_space;
938             i_current = i;
939         }
940     }
941 
942     int32_t max_diff = lv_font_get_glyph_width(font, letter, letter_next) + letter_space + 1;
943     return (pos->x >= (last_x - letter_space) && pos->x <= (last_x + max_diff));
944 }
945 
lv_label_get_style(lv_obj_t * label,uint8_t type)946 lv_style_list_t * lv_label_get_style(lv_obj_t * label, uint8_t type)
947 {
948     lv_style_list_t * style_dsc_p;
949     switch(type) {
950         case LV_LABEL_PART_MAIN:
951             style_dsc_p = &label->style_list;
952             break;
953         default:
954             style_dsc_p = NULL;
955     }
956 
957     return style_dsc_p;
958 }
959 /*=====================
960  * Other functions
961  *====================*/
962 
963 /**
964  * Insert a text to the label. The label text can not be static.
965  * @param label pointer to a label object
966  * @param pos character index to insert. Expressed in character index and not byte index (Different
967  * in UTF-8) 0: before first char. LV_LABEL_POS_LAST: after last char.
968  * @param txt pointer to the text to insert
969  */
lv_label_ins_text(lv_obj_t * label,uint32_t pos,const char * txt)970 void lv_label_ins_text(lv_obj_t * label, uint32_t pos, const char * txt)
971 {
972     LV_ASSERT_OBJ(label, LV_OBJX_NAME);
973     LV_ASSERT_STR(txt);
974 
975     lv_label_ext_t * ext = lv_obj_get_ext_attr(label);
976 
977     /*Can not append to static text*/
978     if(ext->static_txt != 0) return;
979 
980     lv_obj_invalidate(label);
981 
982     /*Allocate space for the new text*/
983     size_t old_len = strlen(ext->text);
984     size_t ins_len = strlen(txt);
985     size_t new_len = ins_len + old_len;
986     ext->text        = lv_mem_realloc(ext->text, new_len + 1);
987     LV_ASSERT_MEM(ext->text);
988     if(ext->text == NULL) return;
989 
990     if(pos == LV_LABEL_POS_LAST) {
991         pos = _lv_txt_get_encoded_length(ext->text);
992     }
993 
994 #if LV_USE_BIDI
995     char * bidi_buf = _lv_mem_buf_get(ins_len + 1);
996     LV_ASSERT_MEM(bidi_buf);
997     if(bidi_buf == NULL) return;
998 
999     _lv_bidi_process(txt, bidi_buf, lv_obj_get_base_dir(label));
1000     _lv_txt_ins(ext->text, pos, bidi_buf);
1001 
1002     _lv_mem_buf_release(bidi_buf);
1003 #else
1004     _lv_txt_ins(ext->text, pos, txt);
1005 #endif
1006     lv_label_refr_text(label);
1007 }
1008 
1009 /**
1010  * Delete characters from a label. The label text can not be static.
1011  * @param label pointer to a label object
1012  * @param pos character index to insert. Expressed in character index and not byte index (Different
1013  * in UTF-8) 0: before first char.
1014  * @param cnt number of characters to cut
1015  */
lv_label_cut_text(lv_obj_t * label,uint32_t pos,uint32_t cnt)1016 void lv_label_cut_text(lv_obj_t * label, uint32_t pos, uint32_t cnt)
1017 {
1018     LV_ASSERT_OBJ(label, LV_OBJX_NAME);
1019 
1020     lv_label_ext_t * ext = lv_obj_get_ext_attr(label);
1021 
1022     /*Can not append to static text*/
1023     if(ext->static_txt != 0) return;
1024 
1025     lv_obj_invalidate(label);
1026 
1027     char * label_txt = lv_label_get_text(label);
1028     /*Delete the characters*/
1029     _lv_txt_cut(label_txt, pos, cnt);
1030 
1031     /*Refresh the label*/
1032     lv_label_refr_text(label);
1033 }
1034 
1035 /**********************
1036  *   STATIC FUNCTIONS
1037  **********************/
1038 
1039 /**
1040  * Handle the drawing related tasks of the labels
1041  * @param label pointer to a label object
1042  * @param clip_area the object will be drawn only in this area
1043  * @param mode LV_DESIGN_COVER_CHK: only check if the object fully covers the 'mask_p' area
1044  *                                  (return 'true' if yes)
1045  *             LV_DESIGN_DRAW: draw the object (always return 'true')
1046  *             LV_DESIGN_DRAW_POST: drawing after every children are drawn
1047  * @param return an element of `lv_design_res_t`
1048  */
lv_label_design(lv_obj_t * label,const lv_area_t * clip_area,lv_design_mode_t mode)1049 static lv_design_res_t lv_label_design(lv_obj_t * label, const lv_area_t * clip_area, lv_design_mode_t mode)
1050 {
1051     /* A label never covers an area */
1052     if(mode == LV_DESIGN_COVER_CHK)
1053         return LV_DESIGN_RES_NOT_COVER;
1054     else if(mode == LV_DESIGN_DRAW_MAIN) {
1055         lv_label_ext_t * ext = lv_obj_get_ext_attr(label);
1056 
1057         lv_coord_t w = lv_obj_get_style_transform_width(label, LV_LABEL_PART_MAIN);
1058         lv_coord_t h = lv_obj_get_style_transform_height(label, LV_LABEL_PART_MAIN);
1059         lv_area_t bg_coords;
1060         lv_area_copy(&bg_coords, &label->coords);
1061         bg_coords.x1 -= w;
1062         bg_coords.x2 += w;
1063         bg_coords.y1 -= h;
1064         bg_coords.y2 += h;
1065 
1066         lv_draw_rect_dsc_t draw_rect_dsc;
1067         lv_draw_rect_dsc_init(&draw_rect_dsc);
1068         lv_obj_init_draw_rect_dsc(label, LV_LABEL_PART_MAIN, &draw_rect_dsc);
1069 
1070         lv_draw_rect(&bg_coords, clip_area, &draw_rect_dsc);
1071 
1072         lv_area_t txt_coords;
1073         get_txt_coords(label, &txt_coords);
1074 
1075         lv_area_t txt_clip;
1076         bool is_common = _lv_area_intersect(&txt_clip, clip_area, &txt_coords);
1077         if(!is_common) return LV_DESIGN_RES_OK;
1078 
1079         lv_label_align_t align = lv_label_get_align(label);
1080 
1081         lv_txt_flag_t flag = LV_TXT_FLAG_NONE;
1082         if(ext->recolor != 0) flag |= LV_TXT_FLAG_RECOLOR;
1083         if(ext->expand != 0) flag |= LV_TXT_FLAG_EXPAND;
1084         if(ext->long_mode == LV_LABEL_LONG_EXPAND) flag |= LV_TXT_FLAG_FIT;
1085         if(align == LV_LABEL_ALIGN_CENTER) flag |= LV_TXT_FLAG_CENTER;
1086         if(align == LV_LABEL_ALIGN_RIGHT) flag |= LV_TXT_FLAG_RIGHT;
1087 
1088         lv_draw_label_dsc_t label_draw_dsc;
1089         lv_draw_label_dsc_init(&label_draw_dsc);
1090 
1091         label_draw_dsc.sel_start = lv_label_get_text_sel_start(label);
1092         label_draw_dsc.sel_end = lv_label_get_text_sel_end(label);
1093         label_draw_dsc.ofs_x = ext->offset.x;
1094         label_draw_dsc.ofs_y = ext->offset.y;
1095         label_draw_dsc.flag = flag;
1096         lv_obj_init_draw_label_dsc(label, LV_LABEL_PART_MAIN, &label_draw_dsc);
1097 
1098         /* In SCROLL and SCROLL_CIRC mode the CENTER and RIGHT are pointless so remove them.
1099          * (In addition they will result misalignment is this case)*/
1100         if((ext->long_mode == LV_LABEL_LONG_SROLL || ext->long_mode == LV_LABEL_LONG_SROLL_CIRC) &&
1101            (ext->align == LV_LABEL_ALIGN_CENTER || ext->align == LV_LABEL_ALIGN_RIGHT)) {
1102             lv_point_t size;
1103             _lv_txt_get_size(&size, ext->text, label_draw_dsc.font, label_draw_dsc.letter_space, label_draw_dsc.line_space,
1104                              LV_COORD_MAX, flag);
1105             if(size.x > lv_area_get_width(&txt_coords)) {
1106                 label_draw_dsc.flag &= ~LV_TXT_FLAG_RIGHT;
1107                 label_draw_dsc.flag &= ~LV_TXT_FLAG_CENTER;
1108             }
1109         }
1110 #if LV_LABEL_LONG_TXT_HINT
1111         lv_draw_label_hint_t * hint = &ext->hint;
1112         if(ext->long_mode == LV_LABEL_LONG_SROLL_CIRC || lv_area_get_height(&txt_coords) < LV_LABEL_HINT_HEIGHT_LIMIT)
1113             hint = NULL;
1114 
1115 #else
1116         /*Just for compatibility*/
1117         lv_draw_label_hint_t * hint = NULL;
1118 #endif
1119 
1120         lv_draw_label(&txt_coords, &txt_clip, &label_draw_dsc, ext->text, hint);
1121 
1122         if(ext->long_mode == LV_LABEL_LONG_SROLL_CIRC) {
1123             lv_point_t size;
1124             _lv_txt_get_size(&size, ext->text, label_draw_dsc.font, label_draw_dsc.letter_space, label_draw_dsc.line_space,
1125                              LV_COORD_MAX, flag);
1126 
1127             /*Draw the text again next to the original to make an circular effect */
1128             if(size.x > lv_area_get_width(&txt_coords)) {
1129                 label_draw_dsc.ofs_x = ext->offset.x + size.x +
1130                                        lv_font_get_glyph_width(label_draw_dsc.font, ' ', ' ') * LV_LABEL_WAIT_CHAR_COUNT;
1131                 label_draw_dsc.ofs_y = ext->offset.y;
1132 
1133                 lv_draw_label(&txt_coords, &txt_clip, &label_draw_dsc, ext->text, hint);
1134             }
1135 
1136             /*Draw the text again below the original to make an circular effect */
1137             if(size.y > lv_area_get_height(&txt_coords)) {
1138                 label_draw_dsc.ofs_x = ext->offset.x;
1139                 label_draw_dsc.ofs_y = ext->offset.y + size.y + lv_font_get_line_height(label_draw_dsc.font);
1140 
1141                 lv_draw_label(&txt_coords, &txt_clip, &label_draw_dsc, ext->text, hint);
1142             }
1143         }
1144     }
1145 
1146     return LV_DESIGN_RES_OK;
1147 }
1148 
1149 /**
1150  * Signal function of the label
1151  * @param label pointer to a label object
1152  * @param sign a signal type from lv_signal_t enum
1153  * @param param pointer to a signal specific variable
1154  * @return LV_RES_OK: the object is not deleted in the function; LV_RES_INV: the object is deleted
1155  */
lv_label_signal(lv_obj_t * label,lv_signal_t sign,void * param)1156 static lv_res_t lv_label_signal(lv_obj_t * label, lv_signal_t sign, void * param)
1157 {
1158     lv_res_t res;
1159 
1160     if(sign == LV_SIGNAL_GET_STYLE) {
1161         lv_get_style_info_t * info = param;
1162         info->result = lv_label_get_style(label, info->part);
1163         if(info->result != NULL) return LV_RES_OK;
1164         else return ancestor_signal(label, sign, param);
1165     }
1166 
1167     /* Include the ancient signal function */
1168     res = ancestor_signal(label, sign, param);
1169     if(res != LV_RES_OK) return res;
1170     if(sign == LV_SIGNAL_GET_TYPE) return lv_obj_handle_get_type_signal(param, LV_OBJX_NAME);
1171 
1172     lv_label_ext_t * ext = lv_obj_get_ext_attr(label);
1173     if(sign == LV_SIGNAL_CLEANUP) {
1174         if(ext->static_txt == 0) {
1175             lv_mem_free(ext->text);
1176             ext->text = NULL;
1177         }
1178         lv_label_dot_tmp_free(label);
1179     }
1180     else if(sign == LV_SIGNAL_STYLE_CHG) {
1181         /*Revert dots for proper refresh*/
1182         lv_label_revert_dots(label);
1183         lv_label_refr_text(label);
1184     }
1185     else if(sign == LV_SIGNAL_COORD_CHG) {
1186         if(lv_area_get_width(&label->coords) != lv_area_get_width(param) ||
1187            lv_area_get_height(&label->coords) != lv_area_get_height(param)) {
1188             lv_label_revert_dots(label);
1189             lv_label_refr_text(label);
1190         }
1191     }
1192     else if(sign == LV_SIGNAL_BASE_DIR_CHG) {
1193 #if LV_USE_BIDI
1194         if(ext->static_txt == 0) lv_label_set_text(label, NULL);
1195 #endif
1196     }
1197     else if(sign == LV_SIGNAL_GET_TYPE) {
1198         lv_obj_type_t * buf = param;
1199         uint8_t i;
1200         for(i = 0; i < LV_MAX_ANCESTOR_NUM - 1; i++) { /*Find the last set data*/
1201             if(buf->type[i] == NULL) break;
1202         }
1203         buf->type[i] = "lv_label";
1204     }
1205 
1206     return res;
1207 }
1208 
1209 /**
1210  * Refresh the label with its text stored in its extended data
1211  * @param label pointer to a label object
1212  */
lv_label_refr_text(lv_obj_t * label)1213 static void lv_label_refr_text(lv_obj_t * label)
1214 {
1215     lv_label_ext_t * ext = lv_obj_get_ext_attr(label);
1216 
1217     if(ext->text == NULL) return;
1218 #if LV_LABEL_LONG_TXT_HINT
1219     ext->hint.line_start = -1; /*The hint is invalid if the text changes*/
1220 #endif
1221 
1222     lv_area_t txt_coords;
1223     get_txt_coords(label, &txt_coords);
1224     lv_coord_t max_w         = lv_area_get_width(&txt_coords);
1225     const lv_font_t * font   = lv_obj_get_style_text_font(label, LV_LABEL_PART_MAIN);
1226     lv_style_int_t line_space = lv_obj_get_style_text_line_space(label, LV_LABEL_PART_MAIN);
1227     lv_style_int_t letter_space = lv_obj_get_style_text_letter_space(label, LV_LABEL_PART_MAIN);
1228 
1229     /*Calc. the height and longest line*/
1230     lv_point_t size;
1231     lv_txt_flag_t flag = LV_TXT_FLAG_NONE;
1232     if(ext->recolor != 0) flag |= LV_TXT_FLAG_RECOLOR;
1233     if(ext->expand != 0) flag |= LV_TXT_FLAG_EXPAND;
1234     if(ext->long_mode == LV_LABEL_LONG_EXPAND) flag |= LV_TXT_FLAG_FIT;
1235     _lv_txt_get_size(&size, ext->text, font, letter_space, line_space, max_w, flag);
1236 
1237     /*Set the full size in expand mode*/
1238     if(ext->long_mode == LV_LABEL_LONG_EXPAND) {
1239         size.x += lv_obj_get_style_pad_left(label, LV_LABEL_PART_MAIN) + lv_obj_get_style_pad_right(label, LV_LABEL_PART_MAIN);
1240         size.y += lv_obj_get_style_pad_top(label, LV_LABEL_PART_MAIN) + lv_obj_get_style_pad_bottom(label, LV_LABEL_PART_MAIN);
1241         lv_obj_set_size(label, size.x, size.y);
1242     }
1243     /*In roll mode keep the size but start offset animations*/
1244     else if(ext->long_mode == LV_LABEL_LONG_SROLL) {
1245 #if LV_USE_ANIMATION
1246         lv_anim_t a;
1247         lv_anim_init(&a);
1248         lv_anim_set_var(&a, label);
1249         lv_anim_set_repeat_count(&a, LV_ANIM_REPEAT_INFINITE);
1250         lv_anim_set_playback_delay(&a, (((lv_font_get_glyph_width(font, ' ', ' ') + letter_space) * 1000) /
1251                                         ext->anim_speed) *
1252                                    LV_LABEL_WAIT_CHAR_COUNT);
1253         lv_anim_set_repeat_delay(&a, a.playback_delay);
1254 
1255         bool hor_anim = false;
1256         if(size.x > lv_area_get_width(&txt_coords)) {
1257             lv_anim_set_values(&a, 0, lv_area_get_width(&txt_coords) - size.x);
1258             lv_anim_set_exec_cb(&a, (lv_anim_exec_xcb_t)lv_label_set_offset_x);
1259             lv_anim_set_time(&a, lv_anim_speed_to_time(ext->anim_speed, a.start, a.end));
1260             lv_anim_set_playback_time(&a, a.time);
1261 
1262             lv_anim_t * anim_cur = lv_anim_get(label, (lv_anim_exec_xcb_t)lv_label_set_offset_x);
1263             int32_t act_time = 0;
1264             bool playback_now = false;
1265             if(anim_cur) {
1266                 act_time = anim_cur->act_time;
1267                 playback_now = anim_cur->playback_now;
1268             }
1269             if(act_time < a.time) {
1270                 a.act_time = act_time;      /*To keep the old position*/
1271                 a.early_apply = 0;
1272                 if(playback_now) {
1273                     a.playback_now = 1;
1274                     /*Swap the start and end values*/
1275                     int32_t tmp;
1276                     tmp      = a.start;
1277                     a.start = a.end;
1278                     a.end   = tmp;
1279                 }
1280             }
1281 
1282             lv_anim_start(&a);
1283             hor_anim = true;
1284         }
1285         else {
1286             /*Delete the offset animation if not required*/
1287             lv_anim_del(label, (lv_anim_exec_xcb_t)lv_label_set_offset_x);
1288             ext->offset.x = 0;
1289         }
1290 
1291         if(size.y > lv_area_get_height(&txt_coords) && hor_anim == false) {
1292             lv_anim_set_values(&a, 0, lv_area_get_height(&txt_coords) - size.y - (lv_font_get_line_height(font)));
1293             lv_anim_set_exec_cb(&a, (lv_anim_exec_xcb_t)lv_label_set_offset_y);
1294             lv_anim_set_time(&a, lv_anim_speed_to_time(ext->anim_speed, a.start, a.end));
1295             lv_anim_set_playback_time(&a, a.time);
1296 
1297             lv_anim_t * anim_cur = lv_anim_get(label, (lv_anim_exec_xcb_t)lv_label_set_offset_y);
1298             int32_t act_time = 0;
1299             bool playback_now = false;
1300             if(anim_cur) {
1301                 act_time = anim_cur->act_time;
1302                 playback_now = anim_cur->playback_now;
1303             }
1304             if(act_time < a.time) {
1305                 a.act_time = act_time;      /*To keep the old position*/
1306                 a.early_apply = 0;
1307                 if(playback_now) {
1308                     a.playback_now = 1;
1309                     /*Swap the start and end values*/
1310                     int32_t tmp;
1311                     tmp      = a.start;
1312                     a.start = a.end;
1313                     a.end   = tmp;
1314                 }
1315             }
1316 
1317             lv_anim_start(&a);
1318         }
1319         else {
1320             /*Delete the offset animation if not required*/
1321             lv_anim_del(label, (lv_anim_exec_xcb_t)lv_label_set_offset_y);
1322             ext->offset.y = 0;
1323         }
1324 #endif
1325     }
1326     /*In roll inf. mode keep the size but start offset animations*/
1327     else if(ext->long_mode == LV_LABEL_LONG_SROLL_CIRC) {
1328 #if LV_USE_ANIMATION
1329         lv_anim_t a;
1330         lv_anim_init(&a);
1331         lv_anim_set_var(&a, label);
1332         lv_anim_set_repeat_count(&a, LV_ANIM_REPEAT_INFINITE);
1333 
1334         bool hor_anim = false;
1335         if(size.x > lv_area_get_width(&txt_coords)) {
1336             lv_anim_set_values(&a, 0, -size.x - lv_font_get_glyph_width(font, ' ', ' ') * LV_LABEL_WAIT_CHAR_COUNT);
1337             lv_anim_set_exec_cb(&a, (lv_anim_exec_xcb_t)lv_label_set_offset_x);
1338             lv_anim_set_time(&a, lv_anim_speed_to_time(ext->anim_speed, a.start, a.end));
1339 
1340             lv_anim_t * anim_cur = lv_anim_get(label, (lv_anim_exec_xcb_t)lv_label_set_offset_x);
1341             int32_t act_time = anim_cur ? anim_cur->act_time : 0;
1342             if(act_time < a.time) {
1343                 a.act_time = act_time;      /*To keep the old position*/
1344                 a.early_apply = 0;
1345             }
1346 
1347             lv_anim_start(&a);
1348             hor_anim = true;
1349         }
1350         else {
1351             /*Delete the offset animation if not required*/
1352             lv_anim_del(label, (lv_anim_exec_xcb_t)lv_label_set_offset_x);
1353             ext->offset.x = 0;
1354         }
1355 
1356         if(size.y > lv_area_get_height(&txt_coords) && hor_anim == false) {
1357             lv_anim_set_values(&a, 0, -size.y - (lv_font_get_line_height(font)));
1358             lv_anim_set_exec_cb(&a, (lv_anim_exec_xcb_t)lv_label_set_offset_y);
1359             lv_anim_set_time(&a, lv_anim_speed_to_time(ext->anim_speed, a.start, a.end));
1360 
1361             lv_anim_t * anim_cur = lv_anim_get(label, (lv_anim_exec_xcb_t)lv_label_set_offset_y);
1362             int32_t act_time = anim_cur ? anim_cur->act_time : 0;
1363             if(act_time < a.time) {
1364                 a.act_time = act_time;      /*To keep the old position*/
1365                 a.early_apply = 0;
1366             }
1367 
1368             lv_anim_start(&a);
1369         }
1370         else {
1371             /*Delete the offset animation if not required*/
1372             lv_anim_del(label, (lv_anim_exec_xcb_t)lv_label_set_offset_y);
1373             ext->offset.y = 0;
1374         }
1375 #endif
1376     }
1377     else if(ext->long_mode == LV_LABEL_LONG_DOT) {
1378         if(size.y <= lv_area_get_height(&txt_coords)) { /*No dots are required, the text is short enough*/
1379             ext->dot_end = LV_LABEL_DOT_END_INV;
1380         }
1381         else if(_lv_txt_get_encoded_length(ext->text) <= LV_LABEL_DOT_NUM) {   /*Don't turn to dots all the characters*/
1382             ext->dot_end = LV_LABEL_DOT_END_INV;
1383         }
1384         else {
1385             lv_point_t p;
1386             p.x = lv_area_get_width(&txt_coords) -
1387                   (lv_font_get_glyph_width(font, '.', '.') + letter_space) *
1388                   LV_LABEL_DOT_NUM; /*Shrink with dots*/
1389             p.y = lv_area_get_height(&txt_coords);
1390             p.y -= p.y %
1391                    (lv_font_get_line_height(font) + line_space); /*Round down to the last line*/
1392             p.y -= line_space;                                               /*Trim the last line space*/
1393             uint32_t letter_id = lv_label_get_letter_on(label, &p);
1394 
1395 
1396             /*Be sure there is space for the dots*/
1397             size_t txt_len = strlen(ext->text);
1398             uint32_t byte_id     = _lv_txt_encoded_get_byte_id(ext->text, letter_id);
1399             while(byte_id + LV_LABEL_DOT_NUM > txt_len) {
1400                 byte_id -= _lv_txt_encoded_size(&ext->text[byte_id]);
1401                 letter_id--;
1402             }
1403 
1404             /*Save letters under the dots and replace them with dots*/
1405             uint32_t byte_id_ori = byte_id;
1406             uint32_t i;
1407             uint8_t len          = 0;
1408             for(i = 0; i <= LV_LABEL_DOT_NUM; i++) {
1409                 len += _lv_txt_encoded_size(&ext->text[byte_id]);
1410                 _lv_txt_encoded_next(ext->text, &byte_id);
1411             }
1412 
1413             if(lv_label_set_dot_tmp(label, &ext->text[byte_id_ori], len)) {
1414                 for(i = 0; i < LV_LABEL_DOT_NUM; i++) {
1415                     ext->text[byte_id_ori + i] = '.';
1416                 }
1417                 ext->text[byte_id_ori + LV_LABEL_DOT_NUM] = '\0';
1418                 ext->dot_end                              = letter_id + LV_LABEL_DOT_NUM;
1419             }
1420         }
1421     }
1422     /*In break mode only the height can change*/
1423     else if(ext->long_mode == LV_LABEL_LONG_BREAK) {
1424         size.y += lv_obj_get_style_pad_top(label, LV_LABEL_PART_MAIN) + lv_obj_get_style_pad_bottom(label, LV_LABEL_PART_MAIN);
1425         lv_obj_set_height(label, size.y);
1426     }
1427     /*Do not set the size in Clip mode*/
1428     else if(ext->long_mode == LV_LABEL_LONG_CROP) {
1429         /*Do nothing*/
1430     }
1431 
1432     lv_obj_invalidate(label);
1433 }
1434 
lv_label_revert_dots(lv_obj_t * label)1435 static void lv_label_revert_dots(lv_obj_t * label)
1436 {
1437     lv_label_ext_t * ext = lv_obj_get_ext_attr(label);
1438     if(ext->long_mode != LV_LABEL_LONG_DOT) return;
1439     if(ext->dot_end == LV_LABEL_DOT_END_INV) return;
1440     uint32_t letter_i = ext->dot_end - LV_LABEL_DOT_NUM;
1441     uint32_t byte_i   = _lv_txt_encoded_get_byte_id(ext->text, letter_i);
1442 
1443     /*Restore the characters*/
1444     uint8_t i      = 0;
1445     char * dot_tmp = lv_label_get_dot_tmp(label);
1446     while(ext->text[byte_i + i] != '\0') {
1447         ext->text[byte_i + i] = dot_tmp[i];
1448         i++;
1449     }
1450     ext->text[byte_i + i] = dot_tmp[i];
1451     lv_label_dot_tmp_free(label);
1452 
1453     ext->dot_end = LV_LABEL_DOT_END_INV;
1454 }
1455 
1456 #if LV_USE_ANIMATION
lv_label_set_offset_x(lv_obj_t * label,lv_coord_t x)1457 static void lv_label_set_offset_x(lv_obj_t * label, lv_coord_t x)
1458 {
1459     lv_label_ext_t * ext = lv_obj_get_ext_attr(label);
1460     ext->offset.x        = x;
1461     lv_obj_invalidate(label);
1462 }
1463 
lv_label_set_offset_y(lv_obj_t * label,lv_coord_t y)1464 static void lv_label_set_offset_y(lv_obj_t * label, lv_coord_t y)
1465 {
1466     lv_label_ext_t * ext = lv_obj_get_ext_attr(label);
1467     ext->offset.y        = y;
1468     lv_obj_invalidate(label);
1469 }
1470 #endif
1471 
1472 /**
1473  * Store `len` characters from `data`. Allocates space if necessary.
1474  *
1475  * @param label pointer to label object
1476  * @param len Number of characters to store.
1477  * @return true on success.
1478  */
lv_label_set_dot_tmp(lv_obj_t * label,char * data,uint32_t len)1479 static bool lv_label_set_dot_tmp(lv_obj_t * label, char * data, uint32_t len)
1480 {
1481     lv_label_ext_t * ext = lv_obj_get_ext_attr(label);
1482     lv_label_dot_tmp_free(label); /* Deallocate any existing space */
1483     if(len > sizeof(char *)) {
1484         /* Memory needs to be allocated. Allocates an additional byte
1485          * for a NULL-terminator so it can be copied. */
1486         ext->dot.tmp_ptr = lv_mem_alloc(len + 1);
1487         if(ext->dot.tmp_ptr == NULL) {
1488             LV_LOG_ERROR("Failed to allocate memory for dot_tmp_ptr");
1489             return false;
1490         }
1491         _lv_memcpy(ext->dot.tmp_ptr, data, len);
1492         ext->dot.tmp_ptr[len] = '\0';
1493         ext->dot_tmp_alloc    = true;
1494     }
1495     else {
1496         /* Characters can be directly stored in object */
1497         ext->dot_tmp_alloc = false;
1498         _lv_memcpy(ext->dot.tmp, data, len);
1499     }
1500     return true;
1501 }
1502 
1503 /**
1504  * Get the stored dot_tmp characters
1505  * @param label pointer to label object
1506  * @return char pointer to a stored characters. Is *not* necessarily NULL-terminated.
1507  */
lv_label_get_dot_tmp(lv_obj_t * label)1508 static char * lv_label_get_dot_tmp(lv_obj_t * label)
1509 {
1510     lv_label_ext_t * ext = lv_obj_get_ext_attr(label);
1511     if(ext->dot_tmp_alloc) {
1512         return ext->dot.tmp_ptr;
1513     }
1514     else {
1515         return ext->dot.tmp;
1516     }
1517 }
1518 
1519 /**
1520  * Free the dot_tmp_ptr field if it was previously allocated.
1521  * Always clears the field
1522  * @param label pointer to label object.
1523  */
lv_label_dot_tmp_free(lv_obj_t * label)1524 static void lv_label_dot_tmp_free(lv_obj_t * label)
1525 {
1526     lv_label_ext_t * ext = lv_obj_get_ext_attr(label);
1527     if(ext->dot_tmp_alloc && ext->dot.tmp_ptr) {
1528         lv_mem_free(ext->dot.tmp_ptr);
1529     }
1530     ext->dot_tmp_alloc = false;
1531     ext->dot.tmp_ptr   = NULL;
1532 }
1533 
get_txt_coords(const lv_obj_t * label,lv_area_t * area)1534 static void get_txt_coords(const lv_obj_t * label, lv_area_t * area)
1535 {
1536     lv_obj_get_coords(label, area);
1537 
1538     lv_coord_t left   = lv_obj_get_style_pad_left(label, LV_LABEL_PART_MAIN);
1539     lv_coord_t right  = lv_obj_get_style_pad_right(label, LV_LABEL_PART_MAIN);
1540     lv_coord_t top    = lv_obj_get_style_pad_top(label, LV_LABEL_PART_MAIN);
1541     lv_coord_t bottom = lv_obj_get_style_pad_bottom(label, LV_LABEL_PART_MAIN);
1542     area->x1 += left;
1543     area->x2 -= right;
1544     area->y1 += top;
1545     area->y2 -= bottom;
1546 }
1547 
1548 #endif
1549