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_ATTRIBUTE_FAST_MEM 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 void LV_ATTRIBUTE_FAST_MEM 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 = lv_color_black();
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