1 /**
2  * @file lv_draw_label.c
3  *
4  */
5 
6 /*********************
7  *      INCLUDES
8  *********************/
9 #include "lv_draw.h"
10 #include "lv_draw_label.h"
11 #include "../misc/lv_math.h"
12 #include "../hal/lv_hal_disp.h"
13 #include "../core/lv_refr.h"
14 #include "../misc/lv_bidi.h"
15 #include "../misc/lv_assert.h"
16 
17 /*********************
18  *      DEFINES
19  *********************/
20 #define LABEL_RECOLOR_PAR_LENGTH 6
21 #define LV_LABEL_HINT_UPDATE_TH 1024 /*Update the "hint" if the label's y coordinates have changed more then this*/
22 
23 /**********************
24  *      TYPEDEFS
25  **********************/
26 enum {
27     CMD_STATE_WAIT,
28     CMD_STATE_PAR,
29     CMD_STATE_IN,
30 };
31 typedef uint8_t cmd_state_t;
32 
33 /**********************
34  *  STATIC PROTOTYPES
35  **********************/
36 
37 static uint8_t hex_char_to_num(char hex);
38 
39 /**********************
40  *  STATIC VARIABLES
41  **********************/
42 
43 /**********************
44  *  GLOBAL VARIABLES
45  **********************/
46 
47 /**********************
48  *      MACROS
49  **********************/
50 
51 /**********************
52  *   GLOBAL FUNCTIONS
53  **********************/
54 
lv_draw_label_dsc_init(lv_draw_label_dsc_t * dsc)55 void lv_draw_label_dsc_init(lv_draw_label_dsc_t * dsc)
56 {
57     lv_memset_00(dsc, sizeof(lv_draw_label_dsc_t));
58     dsc->opa = LV_OPA_COVER;
59     dsc->color = lv_color_black();
60     dsc->font = LV_FONT_DEFAULT;
61     dsc->sel_start = LV_DRAW_LABEL_NO_TXT_SEL;
62     dsc->sel_end = LV_DRAW_LABEL_NO_TXT_SEL;
63     dsc->sel_color = lv_color_black();
64     dsc->sel_bg_color = lv_palette_main(LV_PALETTE_BLUE);
65     dsc->bidi_dir = LV_BASE_DIR_LTR;
66 }
67 
68 /**
69  * Write a text
70  * @param coords coordinates of the label
71  * @param mask the label will be drawn only in this area
72  * @param dsc pointer to draw descriptor
73  * @param txt `\0` terminated text to write
74  * @param hint pointer to a `lv_draw_label_hint_t` variable.
75  * It is managed by the draw to speed up the drawing of very long texts (thousands of lines).
76  */
lv_draw_label(lv_draw_ctx_t * draw_ctx,const lv_draw_label_dsc_t * dsc,const lv_area_t * coords,const char * txt,lv_draw_label_hint_t * hint)77 LV_ATTRIBUTE_FAST_MEM void lv_draw_label(lv_draw_ctx_t * draw_ctx, const lv_draw_label_dsc_t * dsc,
78                                          const lv_area_t * coords, const char * txt, lv_draw_label_hint_t * hint)
79 {
80     if(dsc->opa <= LV_OPA_MIN) return;
81     if(dsc->font == NULL) {
82         LV_LOG_WARN("dsc->font == NULL");
83         return;
84     }
85 
86     if(draw_ctx->draw_letter == NULL) {
87         LV_LOG_WARN("draw->draw_letter == NULL (there is no function to draw letters)");
88         return;
89     }
90 
91     lv_draw_label_dsc_t dsc_mod = *dsc;
92 
93     const lv_font_t * font = dsc->font;
94     int32_t w;
95 
96     /*No need to waste processor time if string is empty*/
97     if(txt == NULL || txt[0] == '\0')
98         return;
99 
100     lv_area_t clipped_area;
101     bool clip_ok = _lv_area_intersect(&clipped_area, coords, draw_ctx->clip_area);
102     if(!clip_ok) return;
103 
104     lv_text_align_t align = dsc->align;
105     lv_base_dir_t base_dir = dsc->bidi_dir;
106 
107     lv_bidi_calculate_align(&align, &base_dir, txt);
108 
109     if((dsc->flag & LV_TEXT_FLAG_EXPAND) == 0) {
110         /*Normally use the label's width as width*/
111         w = lv_area_get_width(coords);
112     }
113     else {
114         /*If EXPAND is enabled then not limit the text's width to the object's width*/
115         lv_point_t p;
116         lv_txt_get_size(&p, txt, dsc->font, dsc->letter_space, dsc->line_space, LV_COORD_MAX,
117                         dsc->flag);
118         w = p.x;
119     }
120 
121     int32_t line_height_font = lv_font_get_line_height(font);
122     int32_t line_height = line_height_font + dsc->line_space;
123 
124     /*Init variables for the first line*/
125     int32_t line_width = 0;
126     lv_point_t pos;
127     pos.x = coords->x1;
128     pos.y = coords->y1;
129 
130     int32_t x_ofs = 0;
131     int32_t y_ofs = 0;
132     x_ofs = dsc->ofs_x;
133     y_ofs = dsc->ofs_y;
134     pos.y += y_ofs;
135 
136     uint32_t line_start     = 0;
137     int32_t last_line_start = -1;
138 
139     /*Check the hint to use the cached info*/
140     if(hint && y_ofs == 0 && coords->y1 < 0) {
141         /*If the label changed too much recalculate the hint.*/
142         if(LV_ABS(hint->coord_y - coords->y1) > LV_LABEL_HINT_UPDATE_TH - 2 * line_height) {
143             hint->line_start = -1;
144         }
145         last_line_start = hint->line_start;
146     }
147 
148     /*Use the hint if it's valid*/
149     if(hint && last_line_start >= 0) {
150         line_start = last_line_start;
151         pos.y += hint->y;
152     }
153 
154     uint32_t line_end = line_start + _lv_txt_get_next_line(&txt[line_start], font, dsc->letter_space, w, NULL, dsc->flag);
155 
156     /*Go the first visible line*/
157     while(pos.y + line_height_font < draw_ctx->clip_area->y1) {
158         /*Go to next line*/
159         line_start = line_end;
160         line_end += _lv_txt_get_next_line(&txt[line_start], font, dsc->letter_space, w, NULL, dsc->flag);
161         pos.y += line_height;
162 
163         /*Save at the threshold coordinate*/
164         if(hint && pos.y >= -LV_LABEL_HINT_UPDATE_TH && hint->line_start < 0) {
165             hint->line_start = line_start;
166             hint->y          = pos.y - coords->y1;
167             hint->coord_y    = coords->y1;
168         }
169 
170         if(txt[line_start] == '\0') return;
171     }
172 
173     /*Align to middle*/
174     if(align == LV_TEXT_ALIGN_CENTER) {
175         line_width = lv_txt_get_width(&txt[line_start], line_end - line_start, font, dsc->letter_space, dsc->flag);
176 
177         pos.x += (lv_area_get_width(coords) - line_width) / 2;
178 
179     }
180     /*Align to the right*/
181     else if(align == LV_TEXT_ALIGN_RIGHT) {
182         line_width = lv_txt_get_width(&txt[line_start], line_end - line_start, font, dsc->letter_space, dsc->flag);
183         pos.x += lv_area_get_width(coords) - line_width;
184     }
185     uint32_t sel_start = dsc->sel_start;
186     uint32_t sel_end = dsc->sel_end;
187     if(sel_start > sel_end) {
188         uint32_t tmp = sel_start;
189         sel_start = sel_end;
190         sel_end = tmp;
191     }
192     lv_draw_line_dsc_t line_dsc;
193 
194     if((dsc->decor & LV_TEXT_DECOR_UNDERLINE) || (dsc->decor & LV_TEXT_DECOR_STRIKETHROUGH)) {
195         lv_draw_line_dsc_init(&line_dsc);
196         line_dsc.color = dsc->color;
197         line_dsc.width = font->underline_thickness ? font->underline_thickness : 1;
198         line_dsc.opa = dsc->opa;
199         line_dsc.blend_mode = dsc->blend_mode;
200     }
201 
202     cmd_state_t cmd_state = CMD_STATE_WAIT;
203     uint32_t i;
204     uint32_t par_start = 0;
205     lv_color_t recolor;
206     lv_color_t color = lv_color_black();
207     int32_t letter_w;
208 
209     lv_draw_rect_dsc_t draw_dsc_sel;
210     lv_draw_rect_dsc_init(&draw_dsc_sel);
211     draw_dsc_sel.bg_color = dsc->sel_bg_color;
212 
213     int32_t pos_x_start = pos.x;
214     /*Write out all lines*/
215     while(txt[line_start] != '\0') {
216         pos.x += x_ofs;
217 
218         /*Write all letter of a line*/
219         cmd_state = CMD_STATE_WAIT;
220         i         = 0;
221 #if LV_USE_BIDI
222         char * bidi_txt = lv_mem_buf_get(line_end - line_start + 1);
223         _lv_bidi_process_paragraph(txt + line_start, bidi_txt, line_end - line_start, base_dir, NULL, 0);
224 #else
225         const char * bidi_txt = txt + line_start;
226 #endif
227 
228         while(i < line_end - line_start) {
229             uint32_t logical_char_pos = 0;
230             if(sel_start != 0xFFFF && sel_end != 0xFFFF) {
231 #if LV_USE_BIDI
232                 logical_char_pos = _lv_txt_encoded_get_char_id(txt, line_start);
233                 uint32_t t = _lv_txt_encoded_get_char_id(bidi_txt, i);
234                 logical_char_pos += _lv_bidi_get_logical_pos(bidi_txt, NULL, line_end - line_start, base_dir, t, NULL);
235 #else
236                 logical_char_pos = _lv_txt_encoded_get_char_id(txt, line_start + i);
237 #endif
238             }
239 
240             uint32_t letter;
241             uint32_t letter_next;
242             _lv_txt_encoded_letter_next_2(bidi_txt, &letter, &letter_next, &i);
243             /*Handle the re-color command*/
244             if((dsc->flag & LV_TEXT_FLAG_RECOLOR) != 0) {
245                 if(letter == (uint32_t)LV_TXT_COLOR_CMD[0]) {
246                     if(cmd_state == CMD_STATE_WAIT) { /*Start char*/
247                         par_start = i;
248                         cmd_state = CMD_STATE_PAR;
249                         continue;
250                     }
251                     else if(cmd_state == CMD_STATE_PAR) {   /*Other start char in parameter escaped cmd. char*/
252                         cmd_state = CMD_STATE_WAIT;
253                     }
254                     else if(cmd_state == CMD_STATE_IN) {   /*Command end*/
255                         cmd_state = CMD_STATE_WAIT;
256                         continue;
257                     }
258                 }
259 
260                 /*Skip the color parameter and wait the space after it*/
261                 if(cmd_state == CMD_STATE_PAR) {
262                     if(letter == ' ') {
263                         /*Get the parameter*/
264                         if(i - par_start == LABEL_RECOLOR_PAR_LENGTH + 1) {
265                             char buf[LABEL_RECOLOR_PAR_LENGTH + 1];
266                             lv_memcpy_small(buf, &bidi_txt[par_start], LABEL_RECOLOR_PAR_LENGTH);
267                             buf[LABEL_RECOLOR_PAR_LENGTH] = '\0';
268                             int r, g, b;
269                             r       = (hex_char_to_num(buf[0]) << 4) + hex_char_to_num(buf[1]);
270                             g       = (hex_char_to_num(buf[2]) << 4) + hex_char_to_num(buf[3]);
271                             b       = (hex_char_to_num(buf[4]) << 4) + hex_char_to_num(buf[5]);
272                             recolor = lv_color_make(r, g, b);
273                         }
274                         else {
275                             recolor.full = dsc->color.full;
276                         }
277                         cmd_state = CMD_STATE_IN; /*After the parameter the text is in the command*/
278                     }
279                     continue;
280                 }
281             }
282 
283             color = dsc->color;
284 
285             if(cmd_state == CMD_STATE_IN) color = recolor;
286 
287             letter_w = lv_font_get_glyph_width(font, letter, letter_next);
288 
289             if(sel_start != 0xFFFF && sel_end != 0xFFFF) {
290                 if(logical_char_pos >= sel_start && logical_char_pos < sel_end) {
291                     lv_area_t sel_coords;
292                     sel_coords.x1 = pos.x;
293                     sel_coords.y1 = pos.y;
294                     sel_coords.x2 = pos.x + letter_w + dsc->letter_space - 1;
295                     sel_coords.y2 = pos.y + line_height - 1;
296                     lv_draw_rect(draw_ctx, &draw_dsc_sel, &sel_coords);
297                     color = dsc->sel_color;
298                 }
299             }
300 
301             dsc_mod.color = color;
302             lv_draw_letter(draw_ctx, &dsc_mod, &pos, letter);
303 
304             if(letter_w > 0) {
305                 pos.x += letter_w + dsc->letter_space;
306             }
307         }
308 
309         if(dsc->decor & LV_TEXT_DECOR_STRIKETHROUGH) {
310             lv_point_t p1;
311             lv_point_t p2;
312             p1.x = pos_x_start;
313             p1.y = pos.y + (dsc->font->line_height / 2)  + line_dsc.width / 2;
314             p2.x = pos.x;
315             p2.y = p1.y;
316             line_dsc.color = color;
317             lv_draw_line(draw_ctx, &line_dsc, &p1, &p2);
318         }
319 
320         if(dsc->decor  & LV_TEXT_DECOR_UNDERLINE) {
321             lv_point_t p1;
322             lv_point_t p2;
323             p1.x = pos_x_start;
324             p1.y = pos.y + dsc->font->line_height - dsc->font->base_line - font->underline_position;
325             p2.x = pos.x;
326             p2.y = p1.y;
327             line_dsc.color = color;
328             lv_draw_line(draw_ctx, &line_dsc, &p1, &p2);
329         }
330 
331 #if LV_USE_BIDI
332         lv_mem_buf_release(bidi_txt);
333         bidi_txt = NULL;
334 #endif
335         /*Go to next line*/
336         line_start = line_end;
337         line_end += _lv_txt_get_next_line(&txt[line_start], font, dsc->letter_space, w, NULL, dsc->flag);
338 
339         pos.x = coords->x1;
340         /*Align to middle*/
341         if(align == LV_TEXT_ALIGN_CENTER) {
342             line_width =
343                 lv_txt_get_width(&txt[line_start], line_end - line_start, font, dsc->letter_space, dsc->flag);
344 
345             pos.x += (lv_area_get_width(coords) - line_width) / 2;
346 
347         }
348         /*Align to the right*/
349         else if(align == LV_TEXT_ALIGN_RIGHT) {
350             line_width =
351                 lv_txt_get_width(&txt[line_start], line_end - line_start, font, dsc->letter_space, dsc->flag);
352             pos.x += lv_area_get_width(coords) - line_width;
353         }
354 
355         /*Go the next line position*/
356         pos.y += line_height;
357 
358         if(pos.y > draw_ctx->clip_area->y2) return;
359     }
360 
361     LV_ASSERT_MEM_INTEGRITY();
362 }
363 
lv_draw_letter(lv_draw_ctx_t * draw_ctx,const lv_draw_label_dsc_t * dsc,const lv_point_t * pos_p,uint32_t letter)364 void lv_draw_letter(lv_draw_ctx_t * draw_ctx, const lv_draw_label_dsc_t * dsc,  const lv_point_t * pos_p,
365                     uint32_t letter)
366 {
367     draw_ctx->draw_letter(draw_ctx, dsc, pos_p, letter);
368 }
369 
370 
371 /**********************
372  *   STATIC FUNCTIONS
373  **********************/
374 
375 /**
376  * Convert a hexadecimal characters to a number (0..15)
377  * @param hex Pointer to a hexadecimal character (0..9, A..F)
378  * @return the numerical value of `hex` or 0 on error
379  */
hex_char_to_num(char hex)380 static uint8_t hex_char_to_num(char hex)
381 {
382     uint8_t result = 0;
383 
384     if(hex >= '0' && hex <= '9') {
385         result = hex - '0';
386     }
387     else {
388         if(hex >= 'a') hex -= 'a' - 'A'; /*Convert to upper case*/
389 
390         switch(hex) {
391             case 'A':
392                 result = 10;
393                 break;
394             case 'B':
395                 result = 11;
396                 break;
397             case 'C':
398                 result = 12;
399                 break;
400             case 'D':
401                 result = 13;
402                 break;
403             case 'E':
404                 result = 14;
405                 break;
406             case 'F':
407                 result = 15;
408                 break;
409             default:
410                 result = 0;
411                 break;
412         }
413     }
414 
415     return result;
416 }
417 
418