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