diff --git a/lv_conf_template.h b/lv_conf_template.h index 2560a586f..935a65962 100644 --- a/lv_conf_template.h +++ b/lv_conf_template.h @@ -290,6 +290,16 @@ typedef void * lv_font_user_data_t; /*Can break (wrap) texts on these chars*/ #define LV_TXT_BREAK_CHARS " ,.;:-_" +/* If a character is at least this long, will break wherever "prettiest" */ +#define LV_TXT_LINE_BREAK_LONG_LEN 12 + +/* Minimum number of characters of a word to put on a line before a break */ +#define LV_TXT_LINE_BREAK_LONG_PRE_MIN_LEN 3 + +/* Minimum number of characters of a word to put on a line after a break */ +#define LV_TXT_LINE_BREAK_LONG_POST_MIN_LEN 3 + + /*=================== * LV_OBJ SETTINGS *==================*/ diff --git a/src/lv_conf_checker.h b/src/lv_conf_checker.h index d8274e258..afa9dab50 100644 --- a/src/lv_conf_checker.h +++ b/src/lv_conf_checker.h @@ -405,6 +405,21 @@ #define LV_TXT_BREAK_CHARS " ,.;:-_" #endif +/* If a character is at least this long, will break wherever "prettiest" */ +#ifndef LV_TXT_LINE_BREAK_LONG_LEN +#define LV_TXT_LINE_BREAK_LONG_LEN 12 +#endif + +/* Minimum number of characters of a word to put on a line before a break */ +#ifndef LV_TXT_LINE_BREAK_LONG_PRE_MIN_LEN +#define LV_TXT_LINE_BREAK_LONG_PRE_MIN_LEN 3 +#endif + +/* Minimum number of characters of a word to put on a line after a break */ +#ifndef LV_TXT_LINE_BREAK_LONG_POST_MIN_LEN +#define LV_TXT_LINE_BREAK_LONG_POST_MIN_LEN 3 +#endif + /*=================== * LV_OBJ SETTINGS *==================*/ diff --git a/src/lv_core/lv_obj.c b/src/lv_core/lv_obj.c index 84e27aa30..afca08f83 100644 --- a/src/lv_core/lv_obj.c +++ b/src/lv_core/lv_obj.c @@ -13,6 +13,7 @@ #include "lv_disp.h" #include "../lv_themes/lv_theme.h" #include "../lv_draw/lv_draw.h" +#include "../lv_draw/lv_img_cache.h" #include "../lv_misc/lv_anim.h" #include "../lv_misc/lv_task.h" #include "../lv_misc/lv_fs.h" diff --git a/src/lv_misc/lv_txt.c b/src/lv_misc/lv_txt.c index 0fdbf22bc..249c11d87 100644 --- a/src/lv_misc/lv_txt.c +++ b/src/lv_misc/lv_txt.c @@ -130,6 +130,133 @@ void lv_txt_get_size(lv_point_t * size_res, const char * text, const lv_font_t * size_res->y -= line_space; } +/** + * Get the next word of text. A word is delimited by break characters. + * + * If the word cannot fit in the max_width space, obey LV_TXT_LINE_BREAK_LONG_* rules. + * + * If the next word cannot fit anything, return 0. + * + * If the first character is a break character, returns the next index. + * + * Example calls from lv_txt_get_next_line() assuming sufficent max_width and + * txt = "Test text\n" + * 0123456789 + * + * Calls would be as follows: + * 1. Return i=4, pointing at breakchar ' ', for the string "Test" + * 2. Return i=5, since i=4 was a breakchar. + * 3. Return i=9, pointing at breakchar '\n' + * 4. Parenting lv_txt_get_next_line() would detect subsequent '\0' + * + * @param txt a '\0' terminated string + * @param font pointer to a font + * @param letter_space letter space + * @param max_width max with of the text (break the lines to fit this size) Set CORD_MAX to avoid line breaks + * @param flags settings for the text from 'txt_flag_type' enum + * @param[out] word_w_ptr width (in pixels) of the parsed word. May be NULL. + * @return the index of the first char of the next word (in byte index not letter index. With UTF-8 they are different) + */ +static uint16_t lv_txt_get_next_word(const char * txt, const lv_font_t * font, + lv_coord_t letter_space, lv_coord_t max_width, + lv_txt_flag_t flag, uint32_t *word_w_ptr) +{ + if(txt == NULL || txt[0] == '\0') return 0; + if(font == NULL) return 0; + + if(flag & LV_TXT_FLAG_EXPAND) max_width = LV_COORD_MAX; + + uint32_t i = 0, i_next = 0, i_next_next = 0; /* Iterating index into txt */ + lv_txt_cmd_state_t cmd_state = LV_TXT_CMD_STATE_WAIT; + uint32_t letter = 0; /* Letter at i */ + uint32_t letter_next = 0; /* Letter at i_next */ + lv_coord_t letter_w; + lv_coord_t cur_w = 0; /* Pixel Width of transversed string */ + uint32_t word_len = 0; /* Number of characters in the transversed word */ + uint32_t break_index = NO_BREAK_FOUND; /* only used for "long" words */ + uint32_t break_letter_count = 0; /* Number of characters up to the long word break point */ + + letter = lv_txt_encoded_next(txt, &i_next); + i_next_next = i_next; + + while(txt[i] != '\0') { + letter_next = lv_txt_encoded_next(txt, &i_next_next); + word_len++; + + /*Handle the recolor command*/ + if((flag & LV_TXT_FLAG_RECOLOR) != 0) { + if(lv_txt_is_cmd(&cmd_state, letter) != false) { + continue; /*Skip the letter is it is part of a command*/ + } + } + + letter_w = lv_font_get_glyph_width(font, letter, letter_next); + cur_w += letter_w; + + + /* Test if this character fits within max_width */ + if( break_index == NO_BREAK_FOUND && cur_w > max_width) { + break_index = i; + if(break_index > 0) { /* zero is possible if first character doesn't fit in width */ + lv_txt_encoded_prev(txt, &break_index); + } + /* break_index is now pointing at the character that doesn't fit */ + break_letter_count = word_len - 1; + } + + /*Check for new line chars and breakchars*/ + if(letter == '\n' || letter == '\r' || is_break_char(letter)) { + /* Update the output width on the first character if it fits. + * Must do this here incase first letter is a break character. */ + if(i == 0 && break_index == NO_BREAK_FOUND && word_w_ptr != NULL) *word_w_ptr = cur_w; + word_len--; + break; + } + + /* Update the output width */ + if( word_w_ptr != NULL && break_index == NO_BREAK_FOUND ) *word_w_ptr = cur_w; + + if(letter_w > 0) { + cur_w += letter_space; + } + + i = i_next; + i_next = i_next_next; + letter = letter_next; + } + + /* Entire Word fits in the provided space */ + if( break_index == NO_BREAK_FOUND ) { + if( word_len == 0 || (letter == '\r' && letter_next == '\n') ) i = i_next; + return i; + } + + /* Word doesn't fit in provided space, but isn't "long" */ + if(word_len < LV_TXT_LINE_BREAK_LONG_LEN) { + if(word_w_ptr != NULL) *word_w_ptr = 0; + return 0; + } + + /* Word is "long," but insufficient amounts can fit in provided space */ + if(break_letter_count < LV_TXT_LINE_BREAK_LONG_PRE_MIN_LEN) { + if(word_w_ptr != NULL) *word_w_ptr = 0; + return 0; + } + + /* Word is a "long", but letters may need to be better distributed */ + { + i = break_index; + int32_t n_move = LV_TXT_LINE_BREAK_LONG_POST_MIN_LEN - (word_len - break_letter_count); + /* Move pointer "i" backwards */ + for(;n_move>0; n_move--){ + lv_txt_encoded_prev(txt, &i); + // todo: it would be appropriate to update the returned word width here + // However, in current usage, this doesn't impact anything. + } + } + + return i; +} /** * Get the next line of text. Check line length and break chars too. @@ -148,65 +275,32 @@ uint16_t lv_txt_get_next_line(const char * txt, const lv_font_t * font, if(flag & LV_TXT_FLAG_EXPAND) max_width = LV_COORD_MAX; - uint32_t i = 0; - uint32_t i_next = 0; - lv_coord_t cur_w = 0; - uint32_t last_break = NO_BREAK_FOUND; - lv_txt_cmd_state_t cmd_state = LV_TXT_CMD_STATE_WAIT; - uint32_t letter_w; - uint32_t letter = 0; - uint32_t letter_next = 0; + uint32_t i = 0; /* Iterating index into txt */ - letter_next = lv_txt_encoded_next(txt, &i_next); + while(txt[i] != '\0' && max_width > 0) { + uint32_t word_w = 0; + uint32_t advance = lv_txt_get_next_word(&txt[i], font, letter_space, max_width, flag, &word_w); + max_width -= word_w; - while(txt[i] != '\0') { - letter = letter_next; - i = i_next; - letter_next = lv_txt_encoded_next(txt, &i_next); - - /*Handle the recolor command*/ - if((flag & LV_TXT_FLAG_RECOLOR) != 0) { - if(lv_txt_is_cmd(&cmd_state, letter) != false) { - continue; /*Skip the letter is it is part of a command*/ - } + if( advance == 0 ){ + if(i == 0) lv_txt_encoded_next(txt, &i); // prevent inf loops + break; } - /*Check for new line chars*/ - if(letter == '\n' || letter == '\r') { - /*Return with the first letter of the next line*/ - if(letter == '\r' && letter_next == '\n') return i_next; - else return i; - } else { /*Check the actual length*/ - letter_w = lv_font_get_glyph_width(font, letter, letter_next); - cur_w += letter_w; + i += advance; - /*If the txt is too long then finish, this is the line end*/ - if(cur_w > max_width) { - /*If a break character was already found break there*/ - if(last_break != NO_BREAK_FOUND) { - i = last_break; - } else { - /* Now this character is out of the area so it will be first character of the next line*/ - /* But 'i' already points to the next character (because of lv_txt_utf8_next) step beck one*/ - lv_txt_encoded_prev(txt, &i); - } + if(txt[i] == '\n') break; + } - /* Do not let to return without doing nothing. - * Find at least one character (Avoid infinite loop )*/ - if(i == 0) lv_txt_encoded_next(txt, &i); - - return i; - } - /*If this char still can fit to this line then check if - * txt can be broken here later */ - else if(is_break_char(letter)) { - last_break = i; /*Save the first char index after break*/ - } - } - - if(letter_w > 0) { - cur_w += letter_space; - } + /* If this is the last of the string, make sure pointer is at NULL-terminator. + * This catches the case, for example of a string ending in "\n" */ + if(txt[i] != '\0'){ + uint32_t i_next = i; + int tmp; + uint32_t letter_next = lv_txt_encoded_next(txt, &i_next); /*Gets current character*/ + tmp = i_next; + letter_next = lv_txt_encoded_next(txt, &i_next); /*Gets subsequent character*/ + if(letter_next == '\0') i = tmp; } return i;