1 /**
2  * @file lv_draw_label.c
3  *
4  */
5 
6 /*********************
7  *      INCLUDES
8  *********************/
9 #include "lv_draw_label.h"
10 #include "../lv_misc/lv_math.h"
11 
12 /*********************
13  *      DEFINES
14  *********************/
15 #define LABEL_RECOLOR_PAR_LENGTH 6
16 #define LV_LABEL_HINT_UPDATE_TH 1024 /*Update the "hint" if the label's y coordinates have changed more then this*/
17 
18 /**********************
19  *      TYPEDEFS
20  **********************/
21 enum {
22     CMD_STATE_WAIT,
23     CMD_STATE_PAR,
24     CMD_STATE_IN,
25 };
26 typedef uint8_t cmd_state_t;
27 
28 /**********************
29  *  STATIC PROTOTYPES
30  **********************/
31 static uint8_t hex_char_to_num(char hex);
32 
33 /**********************
34  *  STATIC VARIABLES
35  **********************/
36 
37 /**********************
38  *      MACROS
39  **********************/
40 
41 /**********************
42  *   GLOBAL FUNCTIONS
43  **********************/
44 
45 /**
46  * Write a text
47  * @param coords coordinates of the label
48  * @param mask the label will be drawn only in this area
49  * @param style pointer to a style
50  * @param opa_scale scale down all opacities by the factor
51  * @param txt 0 terminated text to write
52  * @param flag settings for the text from 'txt_flag_t' enum
53  * @param offset text offset in x and y direction (NULL if unused)
54  * @param sel_start start index of selected area (`LV_LABEL_TXT_SEL_OFF` if none)
55  * @param sel_end end index of selected area (`LV_LABEL_TXT_SEL_OFF` if none)
56  */
lv_draw_label(const lv_area_t * coords,const lv_area_t * mask,const lv_style_t * style,lv_opa_t opa_scale,const char * txt,lv_txt_flag_t flag,lv_point_t * offset,uint16_t sel_start,uint16_t sel_end,lv_draw_label_hint_t * hint)57 void lv_draw_label(const lv_area_t * coords, const lv_area_t * mask, const lv_style_t * style, lv_opa_t opa_scale,
58                    const char * txt, lv_txt_flag_t flag, lv_point_t * offset, uint16_t sel_start, uint16_t sel_end,
59                    lv_draw_label_hint_t * hint)
60 {
61     const lv_font_t * font = style->text.font;
62     lv_coord_t w;
63     if((flag & LV_TXT_FLAG_EXPAND) == 0) {
64         /*Normally use the label's width as width*/
65         w = lv_area_get_width(coords);
66     } else {
67         /*If EXAPND is enabled then not limit the text's width to the object's width*/
68         lv_point_t p;
69         lv_txt_get_size(&p, txt, style->text.font, style->text.letter_space, style->text.line_space, LV_COORD_MAX,
70                         flag);
71         w = p.x;
72     }
73 
74     lv_coord_t line_height = lv_font_get_line_height(font) + style->text.line_space;
75 
76     /*Init variables for the first line*/
77     lv_coord_t line_width = 0;
78     lv_point_t pos;
79     pos.x = coords->x1;
80     pos.y = coords->y1;
81 
82     lv_coord_t x_ofs = 0;
83     lv_coord_t y_ofs = 0;
84     if(offset != NULL) {
85         x_ofs = offset->x;
86         y_ofs = offset->y;
87         pos.y += y_ofs;
88     }
89 
90     uint32_t line_start     = 0;
91     int32_t last_line_start = -1;
92 
93     /*Check the hint to use the cached info*/
94     if(hint && y_ofs == 0 && coords->y1 < 0) {
95         /*If the label changed too much recalculate the hint.*/
96         if(LV_MATH_ABS(hint->coord_y - coords->y1) > LV_LABEL_HINT_UPDATE_TH - 2 * line_height) {
97             hint->line_start = -1;
98         }
99         last_line_start = hint->line_start;
100     }
101 
102     /*Use the hint if it's valid*/
103     if(hint && last_line_start >= 0) {
104         line_start = last_line_start;
105         pos.y += hint->y;
106     }
107 
108     uint32_t line_end = line_start + lv_txt_get_next_line(&txt[line_start], font, style->text.letter_space, w, flag);
109 
110     /*Go the first visible line*/
111     while(pos.y + line_height < mask->y1) {
112         /*Go to next line*/
113         line_start = line_end;
114         line_end += lv_txt_get_next_line(&txt[line_start], font, style->text.letter_space, w, flag);
115         pos.y += line_height;
116 
117         /*Save at the threshold coordinate*/
118         if(hint && pos.y >= -LV_LABEL_HINT_UPDATE_TH && hint->line_start < 0) {
119             hint->line_start = line_start;
120             hint->y          = pos.y - coords->y1;
121             hint->coord_y    = coords->y1;
122         }
123 
124         if(txt[line_start] == '\0') return;
125     }
126 
127     /*Align to middle*/
128     if(flag & LV_TXT_FLAG_CENTER) {
129         line_width = lv_txt_get_width(&txt[line_start], line_end - line_start, font, style->text.letter_space, flag);
130 
131         pos.x += (lv_area_get_width(coords) - line_width) / 2;
132 
133     }
134     /*Align to the right*/
135     else if(flag & LV_TXT_FLAG_RIGHT) {
136         line_width = lv_txt_get_width(&txt[line_start], line_end - line_start, font, style->text.letter_space, flag);
137         pos.x += lv_area_get_width(coords) - line_width;
138     }
139 
140     lv_opa_t opa = opa_scale == LV_OPA_COVER ? style->text.opa : (uint16_t)((uint16_t)style->text.opa * opa_scale) >> 8;
141 
142     cmd_state_t cmd_state = CMD_STATE_WAIT;
143     uint32_t i;
144     uint16_t par_start = 0;
145     lv_color_t recolor;
146     lv_coord_t letter_w;
147     lv_style_t sel_style;
148     lv_style_copy(&sel_style, &lv_style_plain_color);
149     sel_style.body.main_color = sel_style.body.grad_color = style->text.sel_color;
150 
151     /*Write out all lines*/
152     while(txt[line_start] != '\0') {
153         if(offset != NULL) {
154             pos.x += x_ofs;
155         }
156         /*Write all letter of a line*/
157         cmd_state = CMD_STATE_WAIT;
158         i         = line_start;
159         uint32_t letter;
160         uint32_t letter_next;
161         while(i < line_end) {
162             letter      = lv_txt_encoded_next(txt, &i);
163             letter_next = lv_txt_encoded_next(&txt[i], NULL);
164 
165             /*Handle the re-color command*/
166             if((flag & LV_TXT_FLAG_RECOLOR) != 0) {
167                 if(letter == (uint32_t)LV_TXT_COLOR_CMD[0]) {
168                     if(cmd_state == CMD_STATE_WAIT) { /*Start char*/
169                         par_start = i;
170                         cmd_state = CMD_STATE_PAR;
171                         continue;
172                     } else if(cmd_state == CMD_STATE_PAR) { /*Other start char in parameter escaped cmd. char */
173                         cmd_state = CMD_STATE_WAIT;
174                     } else if(cmd_state == CMD_STATE_IN) { /*Command end */
175                         cmd_state = CMD_STATE_WAIT;
176                         continue;
177                     }
178                 }
179 
180                 /*Skip the color parameter and wait the space after it*/
181                 if(cmd_state == CMD_STATE_PAR) {
182                     if(letter == ' ') {
183                         /*Get the parameter*/
184                         if(i - par_start == LABEL_RECOLOR_PAR_LENGTH + 1) {
185                             char buf[LABEL_RECOLOR_PAR_LENGTH + 1];
186                             memcpy(buf, &txt[par_start], LABEL_RECOLOR_PAR_LENGTH);
187                             buf[LABEL_RECOLOR_PAR_LENGTH] = '\0';
188                             int r, g, b;
189                             r       = (hex_char_to_num(buf[0]) << 4) + hex_char_to_num(buf[1]);
190                             g       = (hex_char_to_num(buf[2]) << 4) + hex_char_to_num(buf[3]);
191                             b       = (hex_char_to_num(buf[4]) << 4) + hex_char_to_num(buf[5]);
192                             recolor = lv_color_make(r, g, b);
193                         } else {
194                             recolor.full = style->text.color.full;
195                         }
196                         cmd_state = CMD_STATE_IN; /*After the parameter the text is in the command*/
197                     }
198                     continue;
199                 }
200             }
201 
202             lv_color_t color = style->text.color;
203 
204             if(cmd_state == CMD_STATE_IN) color = recolor;
205 
206             letter_w = lv_font_get_glyph_width(font, letter, letter_next);
207 
208             if(sel_start != 0xFFFF && sel_end != 0xFFFF) {
209                 int char_ind = lv_encoded_get_char_id(txt, i);
210                 /*Do not draw the rectangle on the character at `sel_start`.*/
211                 if(char_ind > sel_start && char_ind <= sel_end) {
212                     lv_area_t sel_coords;
213                     sel_coords.x1 = pos.x;
214                     sel_coords.y1 = pos.y;
215                     sel_coords.x2 = pos.x + letter_w + style->text.letter_space - 1;
216                     sel_coords.y2 = pos.y + line_height - 1;
217                     lv_draw_rect(&sel_coords, mask, &sel_style, opa);
218                 }
219             }
220             lv_draw_letter(&pos, mask, font, letter, color, opa);
221 
222             if(letter_w > 0) {
223                 pos.x += letter_w + style->text.letter_space;
224             }
225         }
226         /*Go to next line*/
227         line_start = line_end;
228         line_end += lv_txt_get_next_line(&txt[line_start], font, style->text.letter_space, w, flag);
229 
230         pos.x = coords->x1;
231         /*Align to middle*/
232         if(flag & LV_TXT_FLAG_CENTER) {
233             line_width =
234                 lv_txt_get_width(&txt[line_start], line_end - line_start, font, style->text.letter_space, flag);
235 
236             pos.x += (lv_area_get_width(coords) - line_width) / 2;
237 
238         }
239         /*Align to the right*/
240         else if(flag & LV_TXT_FLAG_RIGHT) {
241             line_width =
242                 lv_txt_get_width(&txt[line_start], line_end - line_start, font, style->text.letter_space, flag);
243             pos.x += lv_area_get_width(coords) - line_width;
244         }
245 
246         /*Go the next line position*/
247         pos.y += line_height;
248 
249         if(pos.y > mask->y2) return;
250     }
251 }
252 
253 /**********************
254  *   STATIC FUNCTIONS
255  **********************/
256 
257 /**
258  * Convert a hexadecimal characters to a number (0..15)
259  * @param hex Pointer to a hexadecimal character (0..9, A..F)
260  * @return the numerical value of `hex` or 0 on error
261  */
hex_char_to_num(char hex)262 static uint8_t hex_char_to_num(char hex)
263 {
264     uint8_t result = 0;
265 
266     if(hex >= '0' && hex <= '9') {
267         result = hex - '0';
268     } else {
269         if(hex >= 'a') hex -= 'a' - 'A'; /*Convert to upper case*/
270 
271         switch(hex) {
272             case 'A': result = 10; break;
273             case 'B': result = 11; break;
274             case 'C': result = 12; break;
275             case 'D': result = 13; break;
276             case 'E': result = 14; break;
277             case 'F': result = 15; break;
278             default: result = 0; break;
279         }
280     }
281 
282     return result;
283 }
284