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