1 /**
2  * @file lv_span.c
3  *
4  */
5 
6 /*********************
7  *      INCLUDES
8  *********************/
9 #include "lv_span_private.h"
10 #include "../../misc/lv_area_private.h"
11 #include "../../draw/lv_draw_private.h"
12 #include "../../core/lv_obj_class_private.h"
13 
14 #if LV_USE_SPAN != 0
15 
16 #include "../../misc/lv_assert.h"
17 #include "../../misc/lv_text_private.h"
18 #include "../../core/lv_global.h"
19 
20 /*********************
21  *      DEFINES
22  *********************/
23 #define MY_CLASS (&lv_spangroup_class)
24 #define snippet_stack LV_GLOBAL_DEFAULT()->span_snippet_stack
25 
26 /**********************
27  *      TYPEDEFS
28  **********************/
29 typedef struct {
30     lv_span_t * span;
31     const char * txt;
32     const lv_font_t * font;
33     uint32_t   bytes;
34     int32_t txt_w;
35     int32_t line_h;
36     int32_t letter_space;
37 } lv_snippet_t;
38 
39 struct _snippet_stack {
40     lv_snippet_t    stack[LV_SPAN_SNIPPET_STACK_SIZE];
41     uint32_t        index;
42 };
43 
44 /**********************
45  *  STATIC PROTOTYPES
46  **********************/
47 static void lv_spangroup_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj);
48 static void lv_spangroup_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj);
49 static void lv_spangroup_event(const lv_obj_class_t * class_p, lv_event_t * e);
50 static void draw_main(lv_event_t * e);
51 static void refresh_self_size(lv_obj_t * obj);
52 
53 static const lv_font_t * lv_span_get_style_text_font(lv_obj_t * par, lv_span_t * span);
54 static int32_t lv_span_get_style_text_letter_space(lv_obj_t * par, lv_span_t * span);
55 static lv_color_t lv_span_get_style_text_color(lv_obj_t * par, lv_span_t * span);
56 static lv_opa_t lv_span_get_style_text_opa(lv_obj_t * par, lv_span_t * span);
57 static lv_blend_mode_t lv_span_get_style_text_blend_mode(lv_obj_t * par, lv_span_t * span);
58 static int32_t lv_span_get_style_text_decor(lv_obj_t * par, lv_span_t * span);
59 
60 static inline void span_text_check(const char ** text);
61 static void lv_draw_span(lv_obj_t * obj, lv_layer_t * layer);
62 static bool lv_text_get_snippet(const char * txt, const lv_font_t * font, int32_t letter_space,
63                                 int32_t max_width, lv_text_flag_t flag, int32_t * use_width,
64                                 uint32_t * end_ofs);
65 
66 static void lv_snippet_clear(void);
67 static uint32_t lv_get_snippet_count(void);
68 static void lv_snippet_push(lv_snippet_t * item);
69 static lv_snippet_t * lv_get_snippet(uint32_t index);
70 static int32_t convert_indent_pct(lv_obj_t * spans, int32_t width);
71 
72 static lv_span_coords_t make_span_coords(const lv_span_t * prev_span, const lv_span_t * curr_span, int32_t width,
73                                          lv_area_t padding, int32_t indent);
74 
75 /**********************
76  *  STATIC VARIABLES
77  **********************/
78 
79 const lv_obj_class_t lv_spangroup_class  = {
80     .base_class = &lv_obj_class,
81     .constructor_cb = lv_spangroup_constructor,
82     .destructor_cb = lv_spangroup_destructor,
83     .event_cb = lv_spangroup_event,
84     .instance_size = sizeof(lv_spangroup_t),
85     .width_def = LV_SIZE_CONTENT,
86     .height_def = LV_SIZE_CONTENT,
87     .name = "span",
88 };
89 
90 /**********************
91  *      MACROS
92  **********************/
93 
94 /**********************
95  *   GLOBAL FUNCTIONS
96  **********************/
lv_span_stack_init(void)97 void lv_span_stack_init(void)
98 {
99     struct _snippet_stack * stack = snippet_stack = lv_malloc(sizeof(struct _snippet_stack));
100     LV_ASSERT_MALLOC(stack);
101     if(!stack) {
102         LV_LOG_ERROR("malloc failed for snippet_stack");
103     }
104 }
105 
lv_span_stack_deinit(void)106 void lv_span_stack_deinit(void)
107 {
108     lv_free(snippet_stack);
109 }
110 
lv_spangroup_create(lv_obj_t * par)111 lv_obj_t * lv_spangroup_create(lv_obj_t * par)
112 {
113     lv_obj_t * obj = lv_obj_class_create_obj(&lv_spangroup_class, par);
114     lv_obj_class_init_obj(obj);
115     return obj;
116 }
117 
lv_spangroup_new_span(lv_obj_t * obj)118 lv_span_t * lv_spangroup_new_span(lv_obj_t * obj)
119 {
120     if(obj == NULL) {
121         return NULL;
122     }
123 
124     LV_ASSERT_OBJ(obj, MY_CLASS);
125     lv_spangroup_t * spans = (lv_spangroup_t *)obj;
126     lv_span_t * span = lv_ll_ins_tail(&spans->child_ll);
127     LV_ASSERT_MALLOC(span);
128 
129     lv_style_init(&span->style);
130     span->txt = (char *)"";
131     span->static_flag = 1;
132     span->spangroup = obj;
133 
134     refresh_self_size(obj);
135 
136     return span;
137 }
138 
lv_spangroup_delete_span(lv_obj_t * obj,lv_span_t * span)139 void lv_spangroup_delete_span(lv_obj_t * obj, lv_span_t * span)
140 {
141     if(obj == NULL || span == NULL) {
142         return;
143     }
144 
145     LV_ASSERT_OBJ(obj, MY_CLASS);
146     lv_spangroup_t * spans = (lv_spangroup_t *)obj;
147     lv_span_t * cur_span;
148     LV_LL_READ(&spans->child_ll, cur_span) {
149         if(cur_span == span) {
150             lv_ll_remove(&spans->child_ll, cur_span);
151             if(cur_span->txt && cur_span->static_flag == 0) {
152                 lv_free(cur_span->txt);
153                 cur_span->txt = NULL;
154             }
155             lv_style_reset(&cur_span->style);
156             lv_free(cur_span);
157             cur_span = NULL;
158             break;
159         }
160     }
161 
162     refresh_self_size(obj);
163 }
164 
165 /*=====================
166  * Setter functions
167  *====================*/
168 
lv_span_set_text(lv_span_t * span,const char * text)169 void lv_span_set_text(lv_span_t * span, const char * text)
170 {
171     if(span == NULL || text == NULL) {
172         return;
173     }
174 
175     size_t text_alloc_len = lv_strlen(text) + 1;
176 
177     if(span->txt == NULL || span->static_flag == 1) {
178         span->txt = lv_malloc(text_alloc_len);
179         LV_ASSERT_MALLOC(span->txt);
180     }
181     else {
182         span->txt = lv_realloc(span->txt, text_alloc_len);
183         LV_ASSERT_MALLOC(span->txt);
184     }
185 
186     if(span->txt == NULL) return;
187 
188     span->static_flag = 0;
189     lv_memcpy(span->txt, text, text_alloc_len);
190 
191     refresh_self_size(span->spangroup);
192 }
193 
lv_span_set_text_static(lv_span_t * span,const char * text)194 void lv_span_set_text_static(lv_span_t * span, const char * text)
195 {
196     if(span == NULL || text == NULL) {
197         return;
198     }
199 
200     if(span->txt && span->static_flag == 0) {
201         lv_free(span->txt);
202         span->txt = NULL;
203     }
204     span->static_flag = 1;
205     span->txt = (char *)text;
206 
207     refresh_self_size(span->spangroup);
208 }
209 
lv_spangroup_set_align(lv_obj_t * obj,lv_text_align_t align)210 void lv_spangroup_set_align(lv_obj_t * obj, lv_text_align_t align)
211 {
212     lv_obj_set_style_text_align(obj, align, LV_PART_MAIN);
213 }
214 
lv_spangroup_set_overflow(lv_obj_t * obj,lv_span_overflow_t overflow)215 void lv_spangroup_set_overflow(lv_obj_t * obj, lv_span_overflow_t overflow)
216 {
217     LV_ASSERT_OBJ(obj, MY_CLASS);
218     lv_spangroup_t * spans = (lv_spangroup_t *)obj;
219     if(spans->overflow == overflow) return;
220     if(overflow >= LV_SPAN_OVERFLOW_LAST) return;
221     spans->overflow = overflow;
222     lv_obj_invalidate(obj);
223 }
224 
lv_spangroup_set_indent(lv_obj_t * obj,int32_t indent)225 void lv_spangroup_set_indent(lv_obj_t * obj, int32_t indent)
226 {
227     LV_ASSERT_OBJ(obj, MY_CLASS);
228     lv_spangroup_t * spans = (lv_spangroup_t *)obj;
229     if(spans->indent == indent) return;
230 
231     spans->indent = indent;
232 
233     refresh_self_size(obj);
234 }
235 
lv_spangroup_set_mode(lv_obj_t * obj,lv_span_mode_t mode)236 void lv_spangroup_set_mode(lv_obj_t * obj, lv_span_mode_t mode)
237 {
238     LV_ASSERT_OBJ(obj, MY_CLASS);
239     lv_spangroup_t * spans = (lv_spangroup_t *)obj;
240 
241     if(mode >= LV_SPAN_MODE_LAST) return;
242 
243     spans->mode = mode;
244     lv_spangroup_refr_mode(obj);
245 }
246 
lv_spangroup_set_max_lines(lv_obj_t * obj,int32_t lines)247 void lv_spangroup_set_max_lines(lv_obj_t * obj, int32_t lines)
248 {
249     LV_ASSERT_OBJ(obj, MY_CLASS);
250     lv_spangroup_t * spans = (lv_spangroup_t *)obj;
251     spans->lines = lines;
252     lv_spangroup_refr_mode(obj);
253 }
254 
255 /*=====================
256  * Getter functions
257  *====================*/
258 
lv_span_get_style(lv_span_t * span)259 lv_style_t * lv_span_get_style(lv_span_t * span)
260 {
261     return &span->style;
262 }
263 
lv_span_get_text(lv_span_t * span)264 const char * lv_span_get_text(lv_span_t * span)
265 {
266     return span->txt;
267 }
268 
lv_spangroup_get_child(const lv_obj_t * obj,int32_t id)269 lv_span_t * lv_spangroup_get_child(const lv_obj_t * obj, int32_t id)
270 {
271     if(obj == NULL) {
272         return NULL;
273     }
274 
275     LV_ASSERT_OBJ(obj, MY_CLASS);
276     lv_spangroup_t * spans = (lv_spangroup_t *)obj;
277     lv_ll_t * linked_list = &spans->child_ll;
278 
279     bool traverse_forwards = (id >= 0);
280     int32_t cur_idx = 0;
281     lv_ll_node_t * cur_node = linked_list->head;
282 
283     /*If using a negative index, start from the tail and use cur -1 to indicate the end*/
284     if(!traverse_forwards) {
285         cur_idx = -1;
286         cur_node = linked_list->tail;
287     }
288 
289     while(cur_node != NULL) {
290         if(cur_idx == id) {
291             return (lv_span_t *) cur_node;
292         }
293         if(traverse_forwards) {
294             cur_node = (lv_ll_node_t *) lv_ll_get_next(linked_list, cur_node);
295             cur_idx++;
296         }
297         else {
298             cur_node = (lv_ll_node_t *) lv_ll_get_prev(linked_list, cur_node);
299             cur_idx--;
300         }
301     }
302 
303     return NULL;
304 }
305 
lv_spangroup_get_span_count(const lv_obj_t * obj)306 uint32_t lv_spangroup_get_span_count(const lv_obj_t * obj)
307 {
308     LV_ASSERT_OBJ(obj, MY_CLASS);
309 
310     if(obj == NULL) {
311         return 0;
312     }
313 
314     LV_ASSERT_OBJ(obj, MY_CLASS);
315     lv_spangroup_t * spans = (lv_spangroup_t *)obj;
316     return lv_ll_get_len(&(spans->child_ll));
317 }
318 
lv_spangroup_get_align(lv_obj_t * obj)319 lv_text_align_t lv_spangroup_get_align(lv_obj_t * obj)
320 {
321     return lv_obj_get_style_text_align(obj, LV_PART_MAIN);
322 }
323 
lv_spangroup_get_overflow(lv_obj_t * obj)324 lv_span_overflow_t lv_spangroup_get_overflow(lv_obj_t * obj)
325 {
326     LV_ASSERT_OBJ(obj, MY_CLASS);
327     lv_spangroup_t * spans = (lv_spangroup_t *)obj;
328     return spans->overflow;
329 }
330 
lv_spangroup_get_indent(lv_obj_t * obj)331 int32_t lv_spangroup_get_indent(lv_obj_t * obj)
332 {
333     LV_ASSERT_OBJ(obj, MY_CLASS);
334     lv_spangroup_t * spans = (lv_spangroup_t *)obj;
335     return spans->indent;
336 }
337 
lv_spangroup_get_mode(lv_obj_t * obj)338 lv_span_mode_t lv_spangroup_get_mode(lv_obj_t * obj)
339 {
340     LV_ASSERT_OBJ(obj, MY_CLASS);
341     lv_spangroup_t * spans = (lv_spangroup_t *)obj;
342     return spans->mode;
343 }
344 
lv_spangroup_get_max_lines(lv_obj_t * obj)345 int32_t lv_spangroup_get_max_lines(lv_obj_t * obj)
346 {
347     LV_ASSERT_OBJ(obj, MY_CLASS);
348     lv_spangroup_t * spans = (lv_spangroup_t *)obj;
349     return spans->lines;
350 }
351 
lv_spangroup_refr_mode(lv_obj_t * obj)352 void lv_spangroup_refr_mode(lv_obj_t * obj)
353 {
354     LV_ASSERT_OBJ(obj, MY_CLASS);
355     lv_spangroup_t * spans = (lv_spangroup_t *)obj;
356 
357     if(spans->mode == LV_SPAN_MODE_EXPAND) {
358         lv_obj_set_width(obj, LV_SIZE_CONTENT);
359         lv_obj_set_height(obj, LV_SIZE_CONTENT);
360     }
361     else if(spans->mode == LV_SPAN_MODE_BREAK) {
362         if(lv_obj_get_style_width(obj, LV_PART_MAIN) == LV_SIZE_CONTENT) {
363             lv_obj_set_width(obj, 100);
364         }
365         lv_obj_set_height(obj, LV_SIZE_CONTENT);
366     }
367     else if(spans->mode == LV_SPAN_MODE_FIXED) {
368         /* use this mode, The user needs to set the size. */
369         /* This is just to prevent an infinite loop. */
370         if(lv_obj_get_style_width(obj, LV_PART_MAIN) == LV_SIZE_CONTENT) {
371             lv_obj_set_width(obj, 100);
372         }
373         if(lv_obj_get_style_height(obj, LV_PART_MAIN) == LV_SIZE_CONTENT) {
374             int32_t width = lv_obj_get_style_width(obj, LV_PART_MAIN);
375             if(LV_COORD_IS_PCT(width)) {
376                 width = 100;
377             }
378             int32_t height = lv_spangroup_get_expand_height(obj, width);
379             lv_obj_set_content_height(obj, height);
380         }
381     }
382 
383     refresh_self_size(obj);
384 }
385 
lv_spangroup_get_max_line_height(lv_obj_t * obj)386 int32_t lv_spangroup_get_max_line_height(lv_obj_t * obj)
387 {
388     LV_ASSERT_OBJ(obj, MY_CLASS);
389     lv_spangroup_t * spans = (lv_spangroup_t *)obj;
390 
391     int32_t max_line_h = 0;
392     lv_span_t * cur_span;
393     LV_LL_READ(&spans->child_ll, cur_span) {
394         const lv_font_t * font = lv_span_get_style_text_font(obj, cur_span);
395         int32_t line_h = lv_font_get_line_height(font);
396         if(line_h > max_line_h) {
397             max_line_h = line_h;
398         }
399     }
400 
401     return max_line_h;
402 }
403 
lv_spangroup_get_expand_width(lv_obj_t * obj,uint32_t max_width)404 uint32_t lv_spangroup_get_expand_width(lv_obj_t * obj, uint32_t max_width)
405 {
406     LV_ASSERT_OBJ(obj, MY_CLASS);
407     lv_spangroup_t * spans = (lv_spangroup_t *)obj;
408 
409     if(lv_ll_get_head(&spans->child_ll) == NULL) {
410         return 0;
411     }
412 
413     uint32_t width = LV_COORD_IS_PCT(spans->indent) ? 0 : spans->indent;
414     lv_span_t * cur_span;
415     int32_t letter_space = 0;
416     LV_LL_READ(&spans->child_ll, cur_span) {
417         const lv_font_t * font = lv_span_get_style_text_font(obj, cur_span);
418         letter_space = lv_span_get_style_text_letter_space(obj, cur_span);
419         uint32_t j = 0;
420         const char * cur_txt = cur_span->txt;
421         span_text_check(&cur_txt);
422         while(cur_txt[j] != '\0') {
423             if(max_width > 0 && width >= max_width) {
424                 return max_width;
425             }
426             uint32_t letter      = lv_text_encoded_next(cur_txt, &j);
427             uint32_t letter_next = lv_text_encoded_next(&cur_txt[j], NULL);
428             uint32_t letter_w = lv_font_get_glyph_width(font, letter, letter_next);
429             width = width + letter_w + letter_space;
430         }
431     }
432 
433     return width - letter_space;
434 }
435 
lv_spangroup_get_expand_height(lv_obj_t * obj,int32_t width)436 int32_t lv_spangroup_get_expand_height(lv_obj_t * obj, int32_t width)
437 {
438     LV_ASSERT_OBJ(obj, MY_CLASS);
439     lv_spangroup_t * spans = (lv_spangroup_t *)obj;
440     if(lv_ll_get_head(&spans->child_ll) == NULL || width <= 0) {
441         return 0;
442     }
443 
444     /* init draw variable */
445     lv_text_flag_t txt_flag = LV_TEXT_FLAG_NONE;
446     int32_t line_space = lv_obj_get_style_text_line_space(obj, LV_PART_MAIN);
447     int32_t max_width = width;
448     int32_t indent = convert_indent_pct(obj, max_width);
449     int32_t max_w  = max_width - indent; /* first line need minus indent */
450 
451     /* coords of draw span-txt */
452     lv_point_t txt_pos;
453     lv_point_set(&txt_pos, indent, 0); /* first line need add indent */
454 
455     lv_span_t * cur_span = lv_ll_get_head(&spans->child_ll);
456     const char * cur_txt = cur_span->txt;
457     span_text_check(&cur_txt);
458     uint32_t cur_txt_ofs = 0;
459     lv_snippet_t snippet;   /* use to save cur_span info and push it to stack */
460     lv_memset(&snippet, 0, sizeof(snippet));
461 
462     lv_span_t * prev_span = cur_span;
463     int32_t line_cnt = 0;
464     int32_t lines = spans->lines < 0 ? INT32_MAX : spans->lines;
465     /* the loop control how many lines need to draw */
466     while(cur_span) {
467         int snippet_cnt = 0;
468         int32_t max_line_h = 0;  /* the max height of span-font when a line have a lot of span */
469 
470         /* the loop control to find a line and push the relevant span info into stack  */
471         while(1) {
472             /* switch to the next span when current is end */
473             if(cur_txt[cur_txt_ofs] == '\0') {
474                 cur_span->trailing_pos = txt_pos;
475 
476                 cur_span = lv_ll_get_next(&spans->child_ll, cur_span);
477                 if(cur_span == NULL) break;
478                 cur_txt = cur_span->txt;
479                 span_text_check(&cur_txt);
480                 cur_txt_ofs = 0;
481                 /* maybe also cur_txt[cur_txt_ofs] == '\0' */
482                 continue;
483             }
484 
485             /* init span info to snippet. */
486             if(cur_txt_ofs == 0) {
487                 snippet.span = cur_span;
488                 snippet.font = lv_span_get_style_text_font(obj, cur_span);
489                 snippet.letter_space = lv_span_get_style_text_letter_space(obj, cur_span);
490                 snippet.line_h = lv_font_get_line_height(snippet.font) + line_space;
491             }
492 
493             /* get current span text line info */
494             uint32_t next_ofs = 0;
495             int32_t use_width = 0;
496             bool isfill = lv_text_get_snippet(&cur_txt[cur_txt_ofs], snippet.font, snippet.letter_space,
497                                               max_w, txt_flag, &use_width, &next_ofs);
498             if(isfill) txt_pos.x = 0;
499             else txt_pos.x += use_width;
500 
501             /* break word deal width */
502             if(isfill && next_ofs > 0 && snippet_cnt > 0) {
503                 int32_t drawn_width = use_width;
504                 if(lv_ll_get_next(&spans->child_ll, cur_span) == NULL) {
505                     drawn_width -= snippet.letter_space;
506                 }
507                 if(max_w < drawn_width) {
508                     break;
509                 }
510 
511                 uint32_t tmp_ofs = next_ofs;
512                 uint32_t letter = lv_text_encoded_prev(&cur_txt[cur_txt_ofs], &tmp_ofs);
513                 uint32_t letter_next = lv_text_encoded_next(&cur_txt[cur_txt_ofs + next_ofs], NULL);
514                 if(!(letter == '\0' || letter == '\n' || letter == '\r' || lv_text_is_break_char(letter) ||
515                      lv_text_is_a_word(letter) || lv_text_is_a_word(letter_next))) {
516                     if(!(letter_next == '\0' || letter_next == '\n'  || letter_next == '\r' || lv_text_is_break_char(letter_next))) {
517                         break;
518                     }
519                 }
520             }
521 
522             snippet.txt = &cur_txt[cur_txt_ofs];
523             snippet.bytes = next_ofs;
524             snippet.txt_w = use_width;
525             cur_txt_ofs += next_ofs;
526             if(max_line_h < snippet.line_h) {
527                 max_line_h = snippet.line_h;
528             }
529             snippet_cnt ++;
530             max_w = max_w - use_width;
531             if(isfill  || max_w <= 0) {
532                 break;
533             }
534         }
535 
536         /* next line init */
537         txt_pos.y += max_line_h;
538 
539         /* iterate all the spans in the current line and set the trailing height to the max line height */
540         for(lv_span_t * tmp_span = prev_span;
541             tmp_span && tmp_span != cur_span;
542             tmp_span = lv_ll_get_next(&spans->child_ll, tmp_span))
543             tmp_span->trailing_height = max_line_h;
544 
545         prev_span = cur_span;
546 
547         max_w = max_width;
548         line_cnt += 1;
549         if(line_cnt >= lines) {
550             break;
551         }
552     }
553     txt_pos.y -= line_space;
554 
555     return txt_pos.y;
556 }
557 
lv_spangroup_get_span_coords(lv_obj_t * obj,const lv_span_t * span)558 lv_span_coords_t lv_spangroup_get_span_coords(lv_obj_t * obj, const lv_span_t * span)
559 {
560     /* find previous span */
561     const lv_spangroup_t * spangroup = (lv_spangroup_t *)obj;
562     const lv_ll_t * spans = &spangroup->child_ll;
563     const int32_t width = lv_obj_get_content_width(obj);
564     const int32_t indent = lv_spangroup_get_indent(obj);
565 
566     if(obj == NULL || span == NULL || lv_ll_get_head(spans) == NULL) return (lv_span_coords_t) {
567         0
568     };
569 
570     lv_span_t * prev_span = NULL;
571     lv_span_t * curr_span;
572     LV_LL_READ(spans, curr_span) {
573         if(curr_span == span) break;
574         prev_span = curr_span;
575     }
576 
577     const uint32_t border_width = lv_obj_get_style_border_width(obj, LV_PART_MAIN);
578     return make_span_coords(prev_span, curr_span, width, (lv_area_t) {
579         .x1 = lv_obj_get_style_pad_left(obj, LV_PART_MAIN) + border_width,
580         .y1 = lv_obj_get_style_pad_top(obj, LV_PART_MAIN) + border_width,
581         .x2 = lv_obj_get_style_pad_right(obj, LV_PART_MAIN) + border_width, .y2 = 0
582     },
583     indent);
584 }
585 
lv_spangroup_get_span_by_point(lv_obj_t * obj,const lv_point_t * p)586 lv_span_t * lv_spangroup_get_span_by_point(lv_obj_t * obj, const lv_point_t * p)
587 {
588     const lv_spangroup_t * spangroup = (lv_spangroup_t *)obj;
589     const lv_ll_t * spans = &spangroup->child_ll;
590     const int32_t width = lv_obj_get_content_width(obj);
591     const int32_t indent = lv_spangroup_get_indent(obj);
592 
593     if(obj == NULL || p == NULL || lv_ll_get_head(spans) == NULL) return NULL;
594 
595     lv_point_t point;
596     point.x = p->x - obj->coords.x1;
597     point.y = p->y - obj->coords.y1;
598 
599     /* find previous span */
600 
601     const lv_span_t * prev_span = NULL;
602     lv_span_t * curr_span;
603     LV_LL_READ(spans, curr_span) {
604         lv_span_coords_t coords = make_span_coords(prev_span, curr_span, width, (lv_area_t) {
605             .x1 = lv_obj_get_style_pad_left(obj, LV_PART_MAIN),
606             .y1 = lv_obj_get_style_pad_top(obj, LV_PART_MAIN),
607             .x2 = lv_obj_get_style_pad_right(obj, LV_PART_MAIN),
608             .y2 = 0
609         },
610         indent);
611         if(lv_area_is_point_on(&coords.heading,  &point, 0) ||
612            lv_area_is_point_on(&coords.middle,   &point, 0) ||
613            lv_area_is_point_on(&coords.trailing, &point, 0)) {
614             return curr_span;
615         }
616         prev_span = curr_span;
617     }
618     return NULL;
619 }
620 
621 /**********************
622  *   STATIC FUNCTIONS
623  **********************/
624 
lv_spangroup_constructor(const lv_obj_class_t * class_p,lv_obj_t * obj)625 static void lv_spangroup_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj)
626 {
627     LV_UNUSED(class_p);
628     lv_spangroup_t * spans = (lv_spangroup_t *)obj;
629     lv_ll_init(&spans->child_ll, sizeof(lv_span_t));
630     spans->indent = 0;
631     spans->lines = -1;
632     spans->mode = LV_SPAN_MODE_EXPAND;
633     spans->overflow = LV_SPAN_OVERFLOW_CLIP;
634     spans->cache_w = 0;
635     spans->cache_h = 0;
636     spans->refresh = 1;
637 }
638 
lv_spangroup_destructor(const lv_obj_class_t * class_p,lv_obj_t * obj)639 static void lv_spangroup_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj)
640 {
641     LV_UNUSED(class_p);
642     lv_spangroup_t * spans = (lv_spangroup_t *)obj;
643     lv_span_t * cur_span = lv_ll_get_head(&spans->child_ll);
644     while(cur_span) {
645         lv_ll_remove(&spans->child_ll, cur_span);
646         if(cur_span->txt && cur_span->static_flag == 0) {
647             lv_free(cur_span->txt);
648             cur_span->txt = NULL;
649         }
650         lv_style_reset(&cur_span->style);
651         lv_free(cur_span);
652         cur_span = lv_ll_get_head(&spans->child_ll);
653     }
654 }
655 
lv_spangroup_event(const lv_obj_class_t * class_p,lv_event_t * e)656 static void lv_spangroup_event(const lv_obj_class_t * class_p, lv_event_t * e)
657 {
658     LV_UNUSED(class_p);
659 
660     /* Call the ancestor's event handler */
661     if(lv_obj_event_base(MY_CLASS, e) != LV_RESULT_OK) return;
662 
663     lv_event_code_t code = lv_event_get_code(e);
664     lv_obj_t * obj = lv_event_get_current_target(e);
665     lv_spangroup_t * spans = (lv_spangroup_t *)obj;
666 
667     if(code == LV_EVENT_DRAW_MAIN) {
668         draw_main(e);
669     }
670     else if(code == LV_EVENT_STYLE_CHANGED) {
671         refresh_self_size(obj);
672     }
673     else if(code == LV_EVENT_SIZE_CHANGED) {
674         refresh_self_size(obj);
675     }
676     else if(code == LV_EVENT_GET_SELF_SIZE) {
677         int32_t width = 0;
678         int32_t height = 0;
679         lv_point_t * self_size = lv_event_get_param(e);
680 
681         if(spans->mode == LV_SPAN_MODE_EXPAND) {
682             if(spans->refresh) {
683                 spans->cache_w = (int32_t)lv_spangroup_get_expand_width(obj, 0);
684                 spans->cache_h = lv_spangroup_get_max_line_height(obj);
685                 spans->refresh = 0;
686             }
687             width = spans->cache_w;
688             height = spans->cache_h;
689         }
690         else if(spans->mode == LV_SPAN_MODE_BREAK) {
691             width = lv_obj_get_content_width(obj);
692             if(self_size->y >= 0) {
693                 if(width != spans->cache_w || spans->refresh) {
694                     height = lv_spangroup_get_expand_height(obj, width);
695                     spans->cache_w = width;
696                     spans->cache_h = height;
697                     spans->refresh = 0;
698                 }
699                 else {
700                     height = spans->cache_h;
701                 }
702             }
703         }
704         else if(spans->mode == LV_SPAN_MODE_FIXED) {
705             width =  self_size->x >= 0 ? lv_obj_get_content_width(obj) : 0;
706             height = self_size->y >= 0 ? lv_obj_get_content_height(obj) : 0;
707         }
708         self_size->x = LV_MAX(self_size->x, width);
709         self_size->y = LV_MAX(self_size->y, height);
710     }
711 }
712 
draw_main(lv_event_t * e)713 static void draw_main(lv_event_t * e)
714 {
715     lv_obj_t * obj = lv_event_get_current_target(e);
716     lv_layer_t * layer = lv_event_get_layer(e);
717 
718     lv_draw_span(obj, layer);
719 }
720 
721 /**
722  * @return true for txt fill the max_width.
723  */
lv_text_get_snippet(const char * txt,const lv_font_t * font,int32_t letter_space,int32_t max_width,lv_text_flag_t flag,int32_t * use_width,uint32_t * end_ofs)724 static bool lv_text_get_snippet(const char * txt, const lv_font_t * font,
725                                 int32_t letter_space, int32_t max_width, lv_text_flag_t flag,
726                                 int32_t * use_width, uint32_t * end_ofs)
727 {
728     if(txt == NULL || txt[0] == '\0') {
729         *end_ofs = 0;
730         *use_width = 0;
731         return false;
732     }
733 
734     int32_t real_max_width = max_width;
735 #if !LV_USE_FONT_PLACEHOLDER
736     /* fix incomplete text display when disable the placeholder. */
737     /* workaround by: https://github.com/lvgl/lvgl/issues/3685 */
738     real_max_width++;
739 #endif
740 
741     uint32_t ofs = lv_text_get_next_line(txt, LV_TEXT_LEN_MAX, font, letter_space, real_max_width, use_width, flag);
742     *end_ofs = ofs;
743 
744     if(txt[ofs] == '\0' && *use_width < max_width && !(ofs && (txt[ofs - 1] == '\n' || txt[ofs - 1] == '\r'))) {
745         return false;
746     }
747     else {
748         return true;
749     }
750 }
751 
lv_snippet_push(lv_snippet_t * item)752 static void lv_snippet_push(lv_snippet_t * item)
753 {
754     struct _snippet_stack * stack_p = snippet_stack;
755     if(stack_p->index < LV_SPAN_SNIPPET_STACK_SIZE) {
756         lv_memcpy(&stack_p->stack[stack_p->index], item, sizeof(lv_snippet_t));
757         stack_p->index++;
758     }
759     else {
760         LV_LOG_ERROR("span draw stack overflow, please set LV_SPAN_SNIPPET_STACK_SIZE too larger");
761     }
762 }
763 
lv_get_snippet_count(void)764 static uint32_t lv_get_snippet_count(void)
765 {
766     return snippet_stack->index;
767 }
768 
lv_get_snippet(uint32_t index)769 static lv_snippet_t * lv_get_snippet(uint32_t index)
770 {
771     return &snippet_stack->stack[index];
772 }
773 
lv_snippet_clear(void)774 static void lv_snippet_clear(void)
775 {
776     snippet_stack->index = 0;
777 }
778 
lv_span_get_style_text_font(lv_obj_t * par,lv_span_t * span)779 static const lv_font_t * lv_span_get_style_text_font(lv_obj_t * par, lv_span_t * span)
780 {
781     const lv_font_t * font;
782     lv_style_value_t value;
783     lv_style_res_t res = lv_style_get_prop(&span->style, LV_STYLE_TEXT_FONT, &value);
784     if(res != LV_STYLE_RES_FOUND) {
785         font = lv_obj_get_style_text_font(par, LV_PART_MAIN);
786     }
787     else {
788         font = (const lv_font_t *)value.ptr;
789     }
790     return font;
791 }
792 
lv_span_get_style_text_letter_space(lv_obj_t * par,lv_span_t * span)793 static int32_t lv_span_get_style_text_letter_space(lv_obj_t * par, lv_span_t * span)
794 {
795     int32_t letter_space;
796     lv_style_value_t value;
797     lv_style_res_t res = lv_style_get_prop(&span->style, LV_STYLE_TEXT_LETTER_SPACE, &value);
798     if(res != LV_STYLE_RES_FOUND) {
799         letter_space = lv_obj_get_style_text_letter_space(par, LV_PART_MAIN);
800     }
801     else {
802         letter_space = (int32_t)value.num;
803     }
804     return letter_space;
805 }
806 
lv_span_get_style_text_color(lv_obj_t * par,lv_span_t * span)807 static lv_color_t lv_span_get_style_text_color(lv_obj_t * par, lv_span_t * span)
808 {
809     lv_style_value_t value;
810     lv_style_res_t res = lv_style_get_prop(&span->style, LV_STYLE_TEXT_COLOR, &value);
811     if(res != LV_STYLE_RES_FOUND) {
812         value.color = lv_obj_get_style_text_color(par, LV_PART_MAIN);
813     }
814     return value.color;
815 }
816 
lv_span_get_style_text_opa(lv_obj_t * par,lv_span_t * span)817 static lv_opa_t lv_span_get_style_text_opa(lv_obj_t * par, lv_span_t * span)
818 {
819     lv_opa_t opa;
820     lv_style_value_t value;
821     lv_style_res_t res = lv_style_get_prop(&span->style, LV_STYLE_TEXT_OPA, &value);
822     if(res != LV_STYLE_RES_FOUND) {
823         opa = (lv_opa_t)lv_obj_get_style_text_opa(par, LV_PART_MAIN);
824     }
825     else {
826         opa = (lv_opa_t)value.num;
827     }
828     return opa;
829 }
830 
lv_span_get_style_text_blend_mode(lv_obj_t * par,lv_span_t * span)831 static lv_blend_mode_t lv_span_get_style_text_blend_mode(lv_obj_t * par, lv_span_t * span)
832 {
833     lv_blend_mode_t mode;
834     lv_style_value_t value;
835     lv_style_res_t res = lv_style_get_prop(&span->style, LV_STYLE_BLEND_MODE, &value);
836     if(res != LV_STYLE_RES_FOUND) {
837         mode = (lv_blend_mode_t)lv_obj_get_style_blend_mode(par, LV_PART_MAIN);
838     }
839     else {
840         mode = (lv_blend_mode_t)value.num;
841     }
842     return mode;
843 }
844 
lv_span_get_style_text_decor(lv_obj_t * par,lv_span_t * span)845 static int32_t lv_span_get_style_text_decor(lv_obj_t * par, lv_span_t * span)
846 {
847     int32_t decor;
848     lv_style_value_t value;
849     lv_style_res_t res = lv_style_get_prop(&span->style, LV_STYLE_TEXT_DECOR, &value);
850     if(res != LV_STYLE_RES_FOUND) {
851         decor = (lv_text_decor_t)lv_obj_get_style_text_decor(par, LV_PART_MAIN);;
852     }
853     else {
854         decor = (int32_t)value.num;
855     }
856     return decor;
857 }
858 
span_text_check(const char ** text)859 static inline void span_text_check(const char ** text)
860 {
861     if(*text == NULL) {
862         *text = "";
863         LV_LOG_ERROR("occur an error that span text == NULL");
864     }
865 }
866 
convert_indent_pct(lv_obj_t * obj,int32_t width)867 static int32_t convert_indent_pct(lv_obj_t * obj, int32_t width)
868 {
869     lv_spangroup_t * spans = (lv_spangroup_t *)obj;
870 
871     int32_t indent = spans->indent;
872     if(LV_COORD_IS_PCT(spans->indent)) {
873         if(spans->mode == LV_SPAN_MODE_EXPAND) {
874             indent = 0;
875         }
876         else {
877             indent = (width * LV_COORD_GET_PCT(spans->indent)) / 100;
878         }
879     }
880 
881     return indent;
882 }
883 
884 /**
885  * draw span group
886  * @param spans obj handle
887  * @param coords coordinates of the label
888  * @param mask the label will be drawn only in this area
889  */
lv_draw_span(lv_obj_t * obj,lv_layer_t * layer)890 static void lv_draw_span(lv_obj_t * obj, lv_layer_t * layer)
891 {
892 
893     lv_area_t coords;
894     lv_obj_get_content_coords(obj, &coords);
895 
896     lv_spangroup_t * spans = (lv_spangroup_t *)obj;
897 
898     /* return if not span */
899     if(lv_ll_get_head(&spans->child_ll) == NULL) {
900         return;
901     }
902 
903     /* return if no draw area */
904     lv_area_t clip_area;
905     if(!lv_area_intersect(&clip_area, &coords, &layer->_clip_area))  return;
906     const lv_area_t clip_area_ori = layer->_clip_area;
907     layer->_clip_area = clip_area;
908 
909     /* init draw variable */
910     lv_text_flag_t txt_flag = LV_TEXT_FLAG_NONE;
911     int32_t line_space = lv_obj_get_style_text_line_space(obj, LV_PART_MAIN);;
912     int32_t max_width = lv_area_get_width(&coords);
913     int32_t indent = convert_indent_pct(obj, max_width);
914     int32_t max_w  = max_width - indent; /* first line need minus indent */
915     lv_opa_t obj_opa = lv_obj_get_style_opa_recursive(obj, LV_PART_MAIN);
916 
917     /* coords of draw span-txt */
918     lv_point_t txt_pos;
919     txt_pos.y = coords.y1;
920     txt_pos.x = coords.x1 + indent; /* first line need add indent */
921 
922     lv_span_t * cur_span = lv_ll_get_head(&spans->child_ll);
923     const char * cur_txt = cur_span->txt;
924     span_text_check(&cur_txt);
925     uint32_t cur_txt_ofs = 0;
926     lv_snippet_t snippet;   /* use to save cur_span info and push it to stack */
927     lv_memzero(&snippet, sizeof(snippet));
928 
929     lv_draw_label_dsc_t label_draw_dsc;
930     lv_draw_label_dsc_init(&label_draw_dsc);
931 
932     bool is_first_line = true;
933     /* the loop control how many lines need to draw */
934     while(cur_span) {
935         bool is_end_line = false;
936         bool ellipsis_valid = false;
937         int32_t max_line_h = 0;  /* the max height of span-font when a line have a lot of span */
938         int32_t max_baseline = 0; /*baseline of the highest span*/
939         lv_snippet_clear();
940 
941         /* the loop control to find a line and push the relevant span info into stack  */
942         while(1) {
943             /* switch to the next span when current is end */
944             if(cur_txt[cur_txt_ofs] == '\0') {
945                 cur_span = lv_ll_get_next(&spans->child_ll, cur_span);
946                 if(cur_span == NULL) break;
947                 cur_txt = cur_span->txt;
948                 span_text_check(&cur_txt);
949                 cur_txt_ofs = 0;
950                 /* maybe also cur_txt[cur_txt_ofs] == '\0' */
951                 continue;
952             }
953 
954             /* init span info to snippet. */
955             if(cur_txt_ofs == 0) {
956                 snippet.span = cur_span;
957                 snippet.font = lv_span_get_style_text_font(obj, cur_span);
958                 snippet.letter_space = lv_span_get_style_text_letter_space(obj, cur_span);
959                 snippet.line_h = lv_font_get_line_height(snippet.font) + line_space;
960             }
961 
962             /* get current span text line info */
963             uint32_t next_ofs = 0;
964             int32_t use_width = 0;
965             bool isfill = lv_text_get_snippet(&cur_txt[cur_txt_ofs], snippet.font, snippet.letter_space,
966                                               max_w, txt_flag, &use_width, &next_ofs);
967 
968             if(isfill) {
969                 if(next_ofs > 0 && lv_get_snippet_count() > 0) {
970                     int32_t drawn_width = use_width;
971                     if(lv_ll_get_next(&spans->child_ll, cur_span) == NULL) {
972                         drawn_width -= snippet.letter_space;
973                     }
974                     /* To prevent infinite loops, the lv_text_get_next_line() may return incomplete words, */
975                     /* This phenomenon should be avoided when lv_get_snippet_count() > 0 */
976                     if(max_w < drawn_width) {
977                         break;
978                     }
979                     uint32_t tmp_ofs = next_ofs;
980                     uint32_t letter = lv_text_encoded_prev(&cur_txt[cur_txt_ofs], &tmp_ofs);
981                     uint32_t letter_next = lv_text_encoded_next(&cur_txt[cur_txt_ofs + next_ofs], NULL);
982                     if(!(letter == '\0' || letter == '\n' || letter == '\r' || lv_text_is_break_char(letter) ||
983                          lv_text_is_a_word(letter) || lv_text_is_a_word(letter_next))) {
984                         if(!(letter_next == '\0' || letter_next == '\n'  || letter_next == '\r' || lv_text_is_break_char(letter_next))) {
985                             break;
986                         }
987                     }
988                 }
989             }
990 
991             snippet.txt = &cur_txt[cur_txt_ofs];
992             snippet.bytes = next_ofs;
993             snippet.txt_w = use_width;
994             cur_txt_ofs += next_ofs;
995             if(max_line_h < snippet.line_h) {
996                 max_line_h = snippet.line_h;
997                 max_baseline = snippet.font->base_line;
998             }
999 
1000             lv_snippet_push(&snippet);
1001             max_w = max_w - use_width;
1002             if(isfill || max_w <= 0) {
1003                 break;
1004             }
1005         }
1006 
1007         /* start current line deal with */
1008 
1009         uint32_t item_cnt = lv_get_snippet_count();
1010         if(item_cnt == 0) {     /* break if stack is empty */
1011             break;
1012         }
1013 
1014         /* Whether the current line is the end line and does overflow processing */
1015         {
1016             lv_snippet_t * last_snippet = lv_get_snippet(item_cnt - 1);
1017             int32_t next_line_h = last_snippet->line_h;
1018             if(last_snippet->txt[last_snippet->bytes] == '\0') {
1019                 next_line_h = 0;
1020                 lv_span_t * next_span = lv_ll_get_next(&spans->child_ll, last_snippet->span);
1021                 if(next_span && next_span->txt && next_span->txt[0]) { /* have the next line */
1022                     next_line_h = lv_font_get_line_height(lv_span_get_style_text_font(obj, next_span)) + line_space;
1023                 }
1024             }
1025             if(txt_pos.y + max_line_h + next_line_h - line_space > coords.y2 + 1) { /* for overflow if is end line. */
1026                 ellipsis_valid = spans->overflow == LV_SPAN_OVERFLOW_ELLIPSIS;
1027                 is_end_line = true;
1028             }
1029         }
1030 
1031         /*Go the first visible line*/
1032         if(txt_pos.y + max_line_h < clip_area.y1) {
1033             goto Next_line_init;
1034         }
1035 
1036         /* align deal with */
1037         lv_text_align_t align = lv_obj_get_style_text_align(obj, LV_PART_MAIN);
1038         if(align == LV_TEXT_ALIGN_CENTER || align == LV_TEXT_ALIGN_RIGHT) {
1039             int32_t align_ofs = 0;
1040             int32_t txts_w = is_first_line ? indent : 0;
1041             uint32_t i;
1042             for(i = 0; i < item_cnt; i++) {
1043                 lv_snippet_t * pinfo = lv_get_snippet(i);
1044                 if(ellipsis_valid && i == item_cnt - 1) {
1045                     uint32_t n_ofs = 0;
1046                     lv_text_get_snippet(pinfo->txt, pinfo->font, pinfo->letter_space, max_width - txts_w,
1047                                         LV_TEXT_FLAG_BREAK_ALL, &pinfo->txt_w, &n_ofs);
1048                     pinfo->bytes = n_ofs;
1049                 }
1050                 txts_w = txts_w + pinfo->txt_w;
1051             }
1052             txts_w -= lv_get_snippet(item_cnt - 1)->letter_space;
1053             align_ofs = max_width > txts_w ? max_width - txts_w : 0;
1054             if(align == LV_TEXT_ALIGN_CENTER) {
1055                 align_ofs = align_ofs >> 1;
1056             }
1057             txt_pos.x += align_ofs;
1058         }
1059 
1060         /* draw line letters */
1061         uint32_t i;
1062         for(i = 0; i < item_cnt; i++) {
1063             lv_snippet_t * pinfo = lv_get_snippet(i);
1064 
1065             /* bidi deal with:todo */
1066             const char * bidi_txt = pinfo->txt;
1067 
1068             lv_point_t pos;
1069             pos.x = txt_pos.x;
1070             pos.y = txt_pos.y + max_line_h - pinfo->line_h - (max_baseline - pinfo->font->base_line);
1071             label_draw_dsc.color = lv_span_get_style_text_color(obj, pinfo->span);
1072             label_draw_dsc.opa = lv_span_get_style_text_opa(obj, pinfo->span);
1073             label_draw_dsc.font = lv_span_get_style_text_font(obj, pinfo->span);
1074             label_draw_dsc.blend_mode = lv_span_get_style_text_blend_mode(obj, pinfo->span);
1075             if(obj_opa < LV_OPA_MAX) {
1076                 label_draw_dsc.opa = LV_OPA_MIX2(label_draw_dsc.opa, obj_opa);
1077             }
1078             uint32_t txt_bytes = pinfo->bytes;
1079 
1080             if(pos.x > clip_area.x2) {
1081                 continue;
1082             }
1083 
1084             label_draw_dsc.text = bidi_txt;
1085             label_draw_dsc.text_length = txt_bytes;
1086             label_draw_dsc.letter_space = pinfo->letter_space;
1087             label_draw_dsc.decor = lv_span_get_style_text_decor(obj, pinfo->span);
1088             lv_area_t a;
1089             a.x1 = pos.x;
1090             a.y1 = pos.y;
1091             a.x2 = a.x1 + pinfo->txt_w;
1092             a.y2 = a.y1 + pinfo->line_h;
1093 
1094             bool need_draw_ellipsis = false;
1095             uint32_t dot_width = 0;
1096             /* deal overflow */
1097             if(ellipsis_valid) {
1098                 uint32_t dot_letter_w = lv_font_get_glyph_width(pinfo->font, '.', '.');
1099                 dot_width = dot_letter_w * 3;
1100 
1101                 label_draw_dsc.flag = LV_TEXT_FLAG_BREAK_ALL;
1102                 uint32_t next_ofs;
1103                 need_draw_ellipsis = lv_text_get_snippet(pinfo->txt, pinfo->font, pinfo->letter_space, coords.x2 - a.x1 - dot_width,
1104                                                          label_draw_dsc.flag, &pinfo->txt_w, &next_ofs);
1105                 a.x2 = a.x1 + pinfo->txt_w;
1106                 label_draw_dsc.text_length = next_ofs + 1;
1107             }
1108 
1109             lv_draw_label(layer, &label_draw_dsc, &a);
1110 
1111             if(need_draw_ellipsis) {
1112                 label_draw_dsc.text = "...";
1113                 a.x1 = a.x2;
1114                 a.x2 = a.x1 + dot_width;
1115                 lv_draw_label(layer, &label_draw_dsc, &a);
1116             }
1117 
1118             txt_pos.x = a.x2;
1119         }
1120 
1121 Next_line_init:
1122         /* next line init */
1123         is_first_line = false;
1124         txt_pos.x = coords.x1;
1125         txt_pos.y += max_line_h;
1126         if(is_end_line || txt_pos.y > clip_area.y2 + 1) {
1127             layer->_clip_area = clip_area_ori;
1128             return;
1129         }
1130         max_w = max_width;
1131     }
1132     layer->_clip_area = clip_area_ori;
1133 }
1134 
refresh_self_size(lv_obj_t * obj)1135 static void refresh_self_size(lv_obj_t * obj)
1136 {
1137     lv_spangroup_t * spans = (lv_spangroup_t *)obj;
1138     spans->refresh = 1;
1139     lv_obj_invalidate(obj);
1140     lv_obj_refresh_self_size(obj);
1141 }
1142 
make_span_coords(const lv_span_t * prev_span,const lv_span_t * curr_span,const int32_t width,const lv_area_t padding,const int32_t indent)1143 static lv_span_coords_t make_span_coords(const lv_span_t * prev_span, const lv_span_t * curr_span, const int32_t width,
1144                                          const lv_area_t padding, const int32_t indent)
1145 {
1146     lv_span_coords_t coords = { 0 };
1147 
1148     if(curr_span == NULL) return coords;
1149 
1150     /* first line */
1151     if(prev_span == NULL) {
1152         lv_area_set(&coords.heading, padding.x1 + indent, padding.y1, width + padding.x1,
1153                     curr_span->trailing_pos.y + padding.y1);
1154         lv_area_set(&coords.middle, coords.heading.x1, coords.heading.y2, curr_span->trailing_pos.x + padding.x1,
1155                     coords.heading.y2 + curr_span->trailing_height);
1156         lv_area_set(&coords.trailing, 0, 0, 0, 0);
1157 
1158         return coords;
1159     }
1160 
1161     /* start and end on the same line */
1162     const bool is_same_line = prev_span->trailing_pos.y == curr_span->trailing_pos.y;
1163     if(is_same_line == true) {
1164         lv_area_set(&coords.heading,
1165                     prev_span->trailing_pos.x + padding.x1, prev_span->trailing_pos.y + padding.y1,
1166                     curr_span->trailing_pos.x + padding.x1, curr_span->trailing_pos.y + curr_span->trailing_height + padding.y1);
1167         return coords;
1168     }
1169 
1170     /* common case */
1171     const lv_point_t pre_trailing_pos = prev_span->trailing_pos;
1172     const int32_t pre_trailing_height = prev_span->trailing_height;
1173 
1174     lv_area_set(&coords.heading,
1175                 pre_trailing_pos.x + padding.x1, pre_trailing_pos.y + padding.y1,
1176                 width + padding.x1, pre_trailing_pos.y + pre_trailing_height + padding.y1);
1177     /* When it happens to be two lines of text,
1178     * the y2 of the middle area is exactly the y1 + line height of the first line of text,
1179     * so the area of the middle area is empty.
1180     * */
1181     lv_area_set(&coords.middle,
1182                 padding.x1, coords.heading.y2,
1183                 width + padding.x1, curr_span->trailing_pos.y + padding.y1);
1184     lv_area_set(&coords.trailing,
1185                 coords.middle.x1, coords.middle.y2,
1186                 curr_span->trailing_pos.x + padding.x1, curr_span->trailing_pos.y + curr_span->trailing_height + padding.y1);
1187 
1188     return coords;
1189 }
1190 
1191 #endif
1192