diff --git a/gfx/gfx_animation.c b/gfx/gfx_animation.c index 77ac05ca0a..638d2c897f 100644 --- a/gfx/gfx_animation.c +++ b/gfx/gfx_animation.c @@ -1832,6 +1832,7 @@ end: bool gfx_animation_line_ticker(gfx_animation_ctx_line_ticker_t *line_ticker) { char *wrapped_str = NULL; + size_t wrapped_str_len = 0; struct string_list lines = {0}; size_t line_offset = 0; bool success = false; @@ -1848,15 +1849,18 @@ bool gfx_animation_line_ticker(gfx_animation_ctx_line_ticker_t *line_ticker) goto end; /* Line wrap input string */ - wrapped_str = (char*)malloc((strlen(line_ticker->str) + 1) * sizeof(char)); + wrapped_str_len = strlen(line_ticker->str) + 1 + 10; /* 10 bytes use for inserting '\n' */ + wrapped_str = (char*)malloc(wrapped_str_len); if (!wrapped_str) goto end; + wrapped_str[0] = '\0'; word_wrap( wrapped_str, + wrapped_str_len, line_ticker->str, (int)line_ticker->line_len, - true, 0); + 100, 0); if (string_is_empty(wrapped_str)) goto end; @@ -1923,6 +1927,7 @@ end: bool gfx_animation_line_ticker_smooth(gfx_animation_ctx_line_ticker_smooth_t *line_ticker) { char *wrapped_str = NULL; + size_t wrapped_str_len = 0; struct string_list lines = {0}; int glyph_width = 0; int glyph_height = 0; @@ -1936,6 +1941,11 @@ bool gfx_animation_line_ticker_smooth(gfx_animation_ctx_line_ticker_smooth_t *li bool success = false; bool is_active = false; gfx_animation_t *p_anim = anim_get_ptr(); + const char *wideglyph_str = msg_hash_get_wideglyph_str(); + int wideglyph_width = 100; + void (*word_wrap_func)(char *dst, size_t dst_size, const char *src, + int line_width, int wideglyph_width, unsigned max_lines) + = wideglyph_str ? word_wrap_wideglyph : word_wrap; /* Sanity check */ if (!line_ticker) @@ -1962,6 +1972,18 @@ bool gfx_animation_line_ticker_smooth(gfx_animation_ctx_line_ticker_smooth_t *li if (glyph_width <= 0) goto end; + if (wideglyph_str) + { + wideglyph_width = font_driver_get_message_width( + line_ticker->font, wideglyph_str, strlen(wideglyph_str), + line_ticker->font_scale); + + if (wideglyph_width > 0) + wideglyph_width = wideglyph_width * 100 / glyph_width; + else + wideglyph_width = 100; + } + /* > Height */ glyph_height = font_driver_get_line_height( line_ticker->font, line_ticker->font_scale); @@ -1977,15 +1999,18 @@ bool gfx_animation_line_ticker_smooth(gfx_animation_ctx_line_ticker_smooth_t *li goto end; /* Line wrap input string */ - wrapped_str = (char*)malloc((strlen(line_ticker->src_str) + 1) * sizeof(char)); + wrapped_str_len = strlen(line_ticker->src_str) + 1 + 10; /* 10 bytes use for inserting '\n' */ + wrapped_str = (char*)malloc(wrapped_str_len); if (!wrapped_str) goto end; + wrapped_str[0] = '\0'; - word_wrap( + (word_wrap_func)( wrapped_str, + wrapped_str_len, line_ticker->src_str, (int)line_len, - true, 0); + wideglyph_width, 0); if (string_is_empty(wrapped_str)) goto end; diff --git a/gfx/gfx_widgets.c b/gfx/gfx_widgets.c index 39e8ddcebc..f3a650f9e3 100644 --- a/gfx/gfx_widgets.c +++ b/gfx/gfx_widgets.c @@ -361,7 +361,8 @@ void gfx_widgets_msg_queue_push( /* Single line text > two lines text > two lines * text with expanded width */ unsigned title_length = (unsigned)strlen(title); - char *msg = strdup(title); + char *msg = NULL; + size_t msg_len = 0; unsigned width = menu_is_alive ? p_dispwidget->msg_queue_default_rect_width_menu_alive : p_dispwidget->msg_queue_default_rect_width; @@ -372,6 +373,12 @@ void gfx_widgets_msg_queue_push( 1); msg_widget->text_height = p_dispwidget->gfx_widget_fonts.msg_queue.line_height; + msg_len = title_length + 1 + 1; /* 1 byte uses for inserting '\n' */ + msg = (char *)malloc(msg_len); + if (!msg) + return; + msg[0] = '\0'; + /* Text is too wide, split it into two lines */ if (text_width > width) { @@ -381,13 +388,16 @@ void gfx_widgets_msg_queue_push( if ((text_width - (text_width >> 2)) < width) width = text_width - (text_width >> 2); - word_wrap(msg, msg, (title_length * width) / text_width, - false, 2); + word_wrap(msg, msg_len, title, (title_length * width) / text_width, + 100, 2); msg_widget->text_height *= 2; } else + { width = text_width; + strlcpy(msg, title, msg_len); + } msg_widget->msg = msg; msg_widget->msg_len = (unsigned)strlen(msg); diff --git a/intl/msg_hash_chs.c b/intl/msg_hash_chs.c index afa21e3758..a53f9872dc 100644 --- a/intl/msg_hash_chs.c +++ b/intl/msg_hash_chs.c @@ -1811,3 +1811,8 @@ const char *msg_hash_to_str_chs(enum msg_hash_enums msg) return "null"; } + +const char *msg_hash_get_wideglyph_str_chs(void) +{ + return "菜"; +} diff --git a/intl/msg_hash_cht.c b/intl/msg_hash_cht.c index 9e1fd3b895..e9a49d79e1 100644 --- a/intl/msg_hash_cht.c +++ b/intl/msg_hash_cht.c @@ -1867,3 +1867,8 @@ const char *msg_hash_to_str_cht(enum msg_hash_enums msg) return "null"; } + +const char *msg_hash_get_wideglyph_str_cht(void) +{ + return "主"; +} diff --git a/intl/msg_hash_ja.c b/intl/msg_hash_ja.c index 5763d40b80..c760519431 100644 --- a/intl/msg_hash_ja.c +++ b/intl/msg_hash_ja.c @@ -2319,3 +2319,8 @@ const char *msg_hash_to_str_jp(enum msg_hash_enums msg) { return "null"; } + +const char *msg_hash_get_wideglyph_str_jp(void) +{ + return "漢"; +} diff --git a/intl/msg_hash_ko.c b/intl/msg_hash_ko.c index 9dabb59c81..99dd62dbc4 100644 --- a/intl/msg_hash_ko.c +++ b/intl/msg_hash_ko.c @@ -2320,3 +2320,8 @@ const char *msg_hash_to_str_ko(enum msg_hash_enums msg) { return "null"; } + +const char *msg_hash_get_wideglyph_str_ko(void) +{ + return "메"; +} diff --git a/libretro-common/include/string/stdstring.h b/libretro-common/include/string/stdstring.h index c85fea6ae9..e7087d6e06 100644 --- a/libretro-common/include/string/stdstring.h +++ b/libretro-common/include/string/stdstring.h @@ -148,9 +148,61 @@ char *string_trim_whitespace_right(char *const s); /* Remove leading and trailing whitespaces */ char *string_trim_whitespace(char *const s); -/* max_lines == 0 means no limit */ -void word_wrap(char *dst, const char *src, - int line_width, bool unicode, unsigned max_lines); +/* + * Wraps string specified by 'src' to destination buffer + * specified by 'dst' and 'dst_size'. + * This function assumes that all glyphs in the string + * have an on-screen pixel width similar to that of + * regular Latin characters - i.e. it will not wrap + * correctly any text containing so-called 'wide' Unicode + * characters (e.g. CJK languages, emojis, etc.). + * + * @param dst pointer to destination buffer. + * @param dst_size size of destination buffer. + * @param src pointer to input string. + * @param line_width max number of characters per line. + * @param wideglyph_width not used, but is necessary to keep + * compatibility with word_wrap_wideglyph(). + * @param max_lines max lines of destination string. + * 0 means no limit. + */ +void word_wrap(char *dst, size_t dst_size, const char *src, + int line_width, int wideglyph_width, unsigned max_lines); + +/* + * Wraps string specified by 'src' to destination buffer + * specified by 'dst' and 'dst_size'. + * This function assumes that all glyphs in the string + * are: + * - EITHER 'non-wide' Unicode glyphs, with an on-screen + * pixel width similar to that of regular Latin characters + * - OR 'wide' Unicode glyphs (e.g. CJK languages, emojis, etc.) + * with an on-screen pixel width defined by 'wideglyph_width' + * Note that wrapping may occur in inappropriate locations + * if 'src' string contains 'wide' Unicode characters whose + * on-screen pixel width deviates greatly from the set + * 'wideglyph_width' value. + * + * @param dst pointer to destination buffer. + * @param dst_size size of destination buffer. + * @param src pointer to input string. + * @param line_width max number of characters per line. + * @param wideglyph_width effective width of 'wide' Unicode glyphs. + * the value here is normalised relative to the + * typical on-screen pixel width of a regular + * Latin character: + * - a regular Latin character is defined to + * have an effective width of 100 + * - wideglyph_width = 100 * (wide_character_pixel_width / latin_character_pixel_width) + * - e.g. if 'wide' Unicode characters in 'src' + * have an on-screen pixel width twice that of + * regular Latin characters, wideglyph_width + * would be 200 + * @param max_lines max lines of destination string. + * 0 means no limit. + */ +void word_wrap_wideglyph(char *dst, size_t dst_size, const char *src, + int line_width, int wideglyph_width, unsigned max_lines); /* Splits string into tokens seperated by 'delim' * > Returned token string must be free()'d diff --git a/libretro-common/string/stdstring.c b/libretro-common/string/stdstring.c index 83347b00d8..d637988076 100644 --- a/libretro-common/string/stdstring.c +++ b/libretro-common/string/stdstring.c @@ -187,24 +187,23 @@ char *string_trim_whitespace(char *const s) return s; } -void word_wrap(char *dst, const char *src, int line_width, bool unicode, unsigned max_lines) +void word_wrap(char *dst, size_t dst_size, const char *src, int line_width, int wideglyph_width, unsigned max_lines) { char *lastspace = NULL; unsigned counter = 0; unsigned lines = 1; - int src_len = (int)strlen(src); + size_t src_len = strlen(src); const char *src_end = src + src_len; + /* Prevent buffer overflow */ + if (dst_size < src_len + 1) + return; + /* Early return if src string length is less * than line width */ if (src_len < line_width) { - /* TODO/FIXME: Would be better to use strcpy(), - * but the behaviour of this function is undefined - * if src and dst point to the same buffer */ - while (src_len--) - *dst++ = *src++; - *dst = '\0'; + strcpy(dst, src); return; } @@ -213,7 +212,7 @@ void word_wrap(char *dst, const char *src, int line_width, bool unicode, unsigne unsigned char_len; char_len = (unsigned)(utf8skip(src, 1) - src); - counter += unicode ? 1 : char_len; + counter++; if (*src == ' ') lastspace = dst; /* Remember the location of the whitespace */ @@ -226,13 +225,9 @@ void word_wrap(char *dst, const char *src, int line_width, bool unicode, unsigne /* Early return if remaining src string * length is less than line width */ - src_len = (int)(src_end - src); - - if (src_len <= line_width) + if (src_end - src <= line_width) { - while (src_len--) - *dst++ = *src++; - *dst = '\0'; + strcpy(dst, src); return; } } @@ -257,13 +252,135 @@ void word_wrap(char *dst, const char *src, int line_width, bool unicode, unsigne /* Early return if remaining src string * length is less than line width */ - src_len = (int)(src_end - src); - - if (src_len < line_width) + if (src_end - src < line_width) { - while (src_len--) - *dst++ = *src++; - *dst = '\0'; + strcpy(dst, src); + return; + } + } + } + } + + *dst = '\0'; +} + +void word_wrap_wideglyph(char *dst, size_t dst_size, const char *src, int line_width, int wideglyph_width, unsigned max_lines) +{ + char *lastspace = NULL; + char *lastwideglyph = NULL; + const char *src_end = src + strlen(src); + unsigned lines = 1; + /* 'line_width' means max numbers of characters per line, + * but this metric is only meaningful when dealing with + * 'regular' glyphs that have an on-screen pixel width + * similar to that of regular Latin characters. + * When handing so-called 'wide' Unicode glyphs, it is + * necessary to consider the actual on-screen pixel width + * of each character. + * In order to do this, we create a distinction between + * regular Latin 'non-wide' glyphs and 'wide' glyphs, and + * normalise all values relative to the on-screen pixel + * width of regular Latin characters: + * - Regular 'non-wide' glyphs have a normalised width of 100 + * - 'line_width' is therefore normalised to 100 * (width_in_characters) + * - 'wide' glyphs have a normalised width of + * 100 * (wide_character_pixel_width / latin_character_pixel_width) + * - When a character is detected, the position in the current + * line is incremented by the regular normalised width of 100 + * - If that character is then determined to be a 'wide' + * glyph, the position in the current line is further incremented + * by the difference between the normalised 'wide' and 'non-wide' + * width values */ + unsigned counter_normalized = 0; + int line_width_normalized = line_width * 100; + int additional_counter_normalized = wideglyph_width - 100; + + /* Early return if src string length is less + * than line width */ + if (src_end - src < line_width) + { + strlcpy(dst, src, dst_size); + return; + } + + while (*src != '\0') + { + unsigned char_len; + + char_len = (unsigned)(utf8skip(src, 1) - src); + counter_normalized += 100; + + /* Prevent buffer overflow */ + if (char_len >= dst_size) + break; + + if (*src == ' ') + lastspace = dst; /* Remember the location of the whitespace */ + else if (*src == '\n') + { + /* If newlines embedded in the input, + * reset the index */ + lines++; + counter_normalized = 0; + + /* Early return if remaining src string + * length is less than line width */ + if (src_end - src <= line_width) + { + strlcpy(dst, src, dst_size); + return; + } + } + else if (char_len >= 3) + { + /* Remember the location of the first byte + * whose length as UTF-8 >= 3*/ + lastwideglyph = dst; + counter_normalized += additional_counter_normalized; + } + + dst_size -= char_len; + while (char_len--) + *dst++ = *src++; + + if (counter_normalized >= (unsigned)line_width_normalized) + { + counter_normalized = 0; + + if (max_lines != 0 && lines >= max_lines) + continue; + else if (lastwideglyph && (!lastspace || lastwideglyph > lastspace)) + { + /* Insert newline character */ + *lastwideglyph = '\n'; + lines++; + src -= dst - lastwideglyph; + dst = lastwideglyph + 1; + lastwideglyph = NULL; + + /* Early return if remaining src string + * length is less than line width */ + if (src_end - src <= line_width) + { + strlcpy(dst, src, dst_size); + return; + } + } + else if (lastspace) + { + /* Replace nearest (previous) whitespace + * with newline character */ + *lastspace = '\n'; + lines++; + src -= dst - lastspace - 1; + dst = lastspace + 1; + lastspace = NULL; + + /* Early return if remaining src string + * length is less than line width */ + if (src_end - src < line_width) + { + strlcpy(dst, src, dst_size); return; } } diff --git a/libretro-common/test/string/test_stdstring.c b/libretro-common/test/string/test_stdstring.c index 1d40fb6941..cba443bcd6 100644 --- a/libretro-common/test/string/test_stdstring.c +++ b/libretro-common/test/string/test_stdstring.c @@ -196,7 +196,7 @@ START_TEST (test_word_wrap) char output[1024]; - word_wrap(output, testtxt, 40, true, 10); + word_wrap(output, sizeof(output), testtxt, 40, 100, 10); ck_assert(!strcmp(output, expected)); } END_TEST diff --git a/menu/drivers/materialui.c b/menu/drivers/materialui.c index 4bde4f1726..74ba54ff9d 100644 --- a/menu/drivers/materialui.c +++ b/menu/drivers/materialui.c @@ -1515,6 +1515,7 @@ typedef struct font_data_t *font; video_font_raster_block_t raster_block; /* ptr alignment */ unsigned glyph_width; + unsigned wideglyph_width; int line_height; int line_ascender; int line_centre_offset; @@ -1606,6 +1607,9 @@ typedef struct materialui_handle materialui_font_data_t hint; /* ptr alignment */ } font_data; + void (*word_wrap)(char *dst, size_t dst_size, const char *src, + int line_width, int wideglyph_width, unsigned max_lines); + /* Thumbnail helpers */ gfx_thumbnail_path_data_t *thumbnail_path_data; @@ -2591,10 +2595,10 @@ static void materialui_render_messagebox( return; /* Split message into lines */ - word_wrap( - wrapped_message, message, + (mui->word_wrap)( + wrapped_message, sizeof(wrapped_message), message, usable_width / (int)mui->font_data.list.glyph_width, - true, 0); + mui->font_data.list.wideglyph_width, 0); string_list_initialize(&list); if ( @@ -2753,10 +2757,10 @@ static unsigned materialui_count_sublabel_lines( sublabel_width_max = usable_width - (int)mui->sublabel_padding - (has_icon ? (int)mui->icon_size : 0); - word_wrap( - wrapped_sublabel_str, entry.sublabel, + (mui->word_wrap)( + wrapped_sublabel_str, sizeof(wrapped_sublabel_str), entry.sublabel, sublabel_width_max / (int)mui->font_data.hint.glyph_width, - true, 0); + mui->font_data.hint.wideglyph_width, 0); /* Return number of lines in wrapped string */ return materialui_count_lines(wrapped_sublabel_str); @@ -3484,6 +3488,13 @@ static bool (*materialui_render_process_entry)( * materialui_render_process_entry() END * ============================== */ +static void materialui_init_font( + gfx_display_t *p_disp, + materialui_font_data_t *font_data, + int font_size, + bool video_is_threaded, + const char *str_latin); + static void materialui_layout( materialui_handle_t *mui, gfx_display_t *p_disp, @@ -4039,9 +4050,9 @@ static void materialui_render_menu_entry_default( sublabel_y = entry_y + vertical_margin + mui->font_data.list.line_height + (int)mui->sublabel_gap + mui->font_data.hint.line_ascender; /* Wrap sublabel string */ - word_wrap(wrapped_sublabel, entry->sublabel, + (mui->word_wrap)(wrapped_sublabel, sizeof(wrapped_sublabel), entry->sublabel, (int)((usable_width - (int)mui->sublabel_padding) / mui->font_data.hint.glyph_width), - true, 0); + mui->font_data.hint.wideglyph_width, 0); /* Draw sublabel string * > Note: We must allow text to be drawn off-screen @@ -4368,9 +4379,9 @@ static void materialui_render_menu_entry_playlist_list( sublabel_y = entry_y + vertical_margin + mui->font_data.list.line_height + (int)mui->sublabel_gap + mui->font_data.hint.line_ascender; /* Wrap sublabel string */ - word_wrap(wrapped_sublabel, entry->sublabel, + (mui->word_wrap)(wrapped_sublabel, sizeof(wrapped_sublabel), entry->sublabel, (int)((usable_width - (int)mui->sublabel_padding) / mui->font_data.hint.glyph_width), - true, 0); + mui->font_data.hint.wideglyph_width, 0); /* Draw sublabel string * > Note: We must allow text to be drawn off-screen @@ -7558,6 +7569,57 @@ static void materialui_update_list_view(materialui_handle_t *mui, settings_t *se mui->need_compute = true; } +static void materialui_init_font( + gfx_display_t *p_disp, + materialui_font_data_t *font_data, + int font_size, + bool video_is_threaded, + const char *str_latin + ) +{ + const char *wideglyph_str = msg_hash_get_wideglyph_str(); + + /* We assume the average glyph aspect ratio is close to 3:4 */ + font_data->glyph_width = (int)((font_size * (3.0f / 4.0f)) + 0.5f); + + if (font_data->font) + { + gfx_display_font_free(font_data->font); + font_data->font = NULL; + } + + font_data->font = gfx_display_font( + p_disp, + APPLICATION_SPECIAL_DIRECTORY_ASSETS_MATERIALUI_FONT, + font_size, video_is_threaded); + + if (font_data->font) + { + /* Calculate a more realistic ticker_limit */ + int char_width = + font_driver_get_message_width(font_data->font, str_latin, 1, 1); + + if (char_width > 0) + font_data->glyph_width = (unsigned)char_width; + + font_data->wideglyph_width = 100; + + if (wideglyph_str) + { + int wideglyph_width = + font_driver_get_message_width(font_data->font, wideglyph_str, strlen(wideglyph_str), 1); + + if (wideglyph_width > 0 && char_width > 0) + font_data->wideglyph_width = wideglyph_width * 100 / char_width; + } + + /* Get line metrics */ + font_data->line_height = font_driver_get_line_height(font_data->font, 1.0f); + font_data->line_ascender = font_driver_get_line_ascender(font_data->font, 1.0f); + font_data->line_centre_offset = font_driver_get_line_centre_offset(font_data->font, 1.0f); + } +} + /* Compute the positions of the widgets */ static void materialui_layout( materialui_handle_t *mui, @@ -7635,88 +7697,11 @@ static void materialui_layout( mui->nav_bar_layout_height = mui->nav_bar.width; } - /* We assume the average glyph aspect ratio is close to 3:4 */ - mui->font_data.title.glyph_width = (int)((title_font_size * (3.0f / 4.0f)) + 0.5f); - mui->font_data.list.glyph_width = (int)((list_font_size * (3.0f / 4.0f)) + 0.5f); - mui->font_data.hint.glyph_width = (int)((hint_font_size * (3.0f / 4.0f)) + 0.5f); - p_disp->header_height = new_header_height; - if (mui->font_data.title.font) - { - gfx_display_font_free(mui->font_data.title.font); - mui->font_data.title.font = NULL; - } - if (mui->font_data.list.font) - { - gfx_display_font_free(mui->font_data.list.font); - mui->font_data.list.font = NULL; - } - if (mui->font_data.hint.font) - { - gfx_display_font_free(mui->font_data.hint.font); - mui->font_data.hint.font = NULL; - } - - mui->font_data.title.font = gfx_display_font( - p_disp, - APPLICATION_SPECIAL_DIRECTORY_ASSETS_MATERIALUI_FONT, - title_font_size, video_is_threaded); - - mui->font_data.list.font = gfx_display_font( - p_disp, - APPLICATION_SPECIAL_DIRECTORY_ASSETS_MATERIALUI_FONT, - list_font_size, video_is_threaded); - - mui->font_data.hint.font = gfx_display_font( - p_disp, - APPLICATION_SPECIAL_DIRECTORY_ASSETS_MATERIALUI_FONT, - hint_font_size, video_is_threaded); - - if (mui->font_data.title.font) - { - /* Calculate a more realistic ticker_limit */ - int title_char_width = - font_driver_get_message_width(mui->font_data.title.font, "a", 1, 1); - - if (title_char_width > 0) - mui->font_data.title.glyph_width = (unsigned)title_char_width; - - /* Get line metrics */ - mui->font_data.title.line_height = font_driver_get_line_height(mui->font_data.title.font, 1.0f); - mui->font_data.title.line_ascender = font_driver_get_line_ascender(mui->font_data.title.font, 1.0f); - mui->font_data.title.line_centre_offset = font_driver_get_line_centre_offset(mui->font_data.title.font, 1.0f); - } - - if (mui->font_data.list.font) - { - /* Calculate a more realistic ticker_limit */ - int list_char_width = - font_driver_get_message_width(mui->font_data.list.font, "a", 1, 1); - - if (list_char_width > 0) - mui->font_data.list.glyph_width = (unsigned)list_char_width; - - /* Get line metrics */ - mui->font_data.list.line_height = font_driver_get_line_height(mui->font_data.list.font, 1.0f); - mui->font_data.list.line_ascender = font_driver_get_line_ascender(mui->font_data.list.font, 1.0f); - mui->font_data.list.line_centre_offset = font_driver_get_line_centre_offset(mui->font_data.list.font, 1.0f); - } - - if (mui->font_data.hint.font) - { - /* Calculate a more realistic ticker_limit */ - int hint_char_width = - font_driver_get_message_width(mui->font_data.hint.font, "t", 1, 1); - - if (hint_char_width > 0) - mui->font_data.hint.glyph_width = (unsigned)hint_char_width; - - /* Get line metrics */ - mui->font_data.hint.line_height = font_driver_get_line_height(mui->font_data.hint.font, 1.0f); - mui->font_data.hint.line_ascender = font_driver_get_line_ascender(mui->font_data.hint.font, 1.0f); - mui->font_data.hint.line_centre_offset = font_driver_get_line_centre_offset(mui->font_data.hint.font, 1.0f); - } + materialui_init_font(p_disp, &mui->font_data.title, title_font_size, video_is_threaded, "a"); + materialui_init_font(p_disp, &mui->font_data.list, list_font_size, video_is_threaded, "a"); + materialui_init_font(p_disp, &mui->font_data.hint, hint_font_size, video_is_threaded, "t"); /* When updating the layout, the system bar * cache must be invalidated (since all text @@ -7908,6 +7893,9 @@ static void *materialui_init(void **userdata, bool video_is_threaded) p_anim->updatetime_cb = materialui_menu_animation_update_time; + /* set word_wrap function pointer */ + mui->word_wrap = msg_hash_get_wideglyph_str() ? word_wrap_wideglyph : word_wrap; + return menu; error: if (menu) diff --git a/menu/drivers/ozone/ozone.c b/menu/drivers/ozone/ozone.c index 5c099cdb35..64b300c937 100644 --- a/menu/drivers/ozone/ozone.c +++ b/menu/drivers/ozone/ozone.c @@ -869,6 +869,9 @@ static void *ozone_init(void **userdata, bool video_is_threaded) last_use_preferred_system_color_theme = settings->bools.menu_use_preferred_system_color_theme; p_anim->updatetime_cb = ozone_menu_animation_update_time; + /* set word_wrap function pointer */ + ozone->word_wrap = msg_hash_get_wideglyph_str() ? word_wrap_wideglyph : word_wrap; + return menu; error: @@ -985,6 +988,7 @@ static bool ozone_init_font( { int glyph_width = 0; gfx_display_t *p_disp = disp_get_ptr(); + const char *wideglyph_str = msg_hash_get_wideglyph_str(); /* Free existing */ if (font_data->font) @@ -1008,6 +1012,18 @@ static bool ozone_init_font( glyph_width = font_driver_get_message_width(font_data->font, "a", 1, 1.0f); if (glyph_width > 0) font_data->glyph_width = glyph_width; + + font_data->wideglyph_width = 100; + + if (wideglyph_str) + { + int wideglyph_width = + font_driver_get_message_width(font_data->font, wideglyph_str, strlen(wideglyph_str), 1.0f); + + if (wideglyph_width > 0 && glyph_width > 0) + font_data->wideglyph_width = wideglyph_width * 100 / glyph_width; + } + font_data->line_height = font_driver_get_line_height(font_data->font, 1.0f); font_data->line_ascender = font_driver_get_line_ascender(font_data->font, 1.0f); font_data->line_centre_offset = font_driver_get_line_centre_offset(font_data->font, 1.0f); @@ -4027,10 +4043,14 @@ void ozone_update_content_metadata(ozone_handle_t *ozone) /* Word wrap core name string, if required */ if (!scroll_content_metadata) { + char tmpstr[sizeof(ozone->selection_core_name)]; unsigned metadata_len = (ozone->dimensions.thumbnail_bar_width - ((ozone->dimensions.sidebar_entry_icon_padding * 2) * 2)) / ozone->fonts.footer.glyph_width; - word_wrap(ozone->selection_core_name, ozone->selection_core_name, metadata_len, true, 0); + + strlcpy(tmpstr, ozone->selection_core_name, sizeof(tmpstr)); + (ozone->word_wrap)(ozone->selection_core_name, sizeof(ozone->selection_core_name), + tmpstr, metadata_len, ozone->fonts.footer.wideglyph_width, 0); ozone->selection_core_name_lines = ozone_count_lines(ozone->selection_core_name); } else @@ -4076,7 +4096,10 @@ void ozone_update_content_metadata(ozone_handle_t *ozone) * formats. Last played strings are well defined, however * (unlike core names), so this should never overflow the * side bar */ - word_wrap(ozone->selection_lastplayed, ozone->selection_lastplayed, 30, true, 0); + char tmpstr[sizeof(ozone->selection_lastplayed)]; + + strlcpy(tmpstr, ozone->selection_lastplayed, sizeof(tmpstr)); + (ozone->word_wrap)(ozone->selection_lastplayed, sizeof(ozone->selection_lastplayed), tmpstr, 30, 100, 0); ozone->selection_lastplayed_lines = ozone_count_lines(ozone->selection_lastplayed); } else diff --git a/menu/drivers/ozone/ozone.h b/menu/drivers/ozone/ozone.h index 1a436d229a..d144109fad 100644 --- a/menu/drivers/ozone/ozone.h +++ b/menu/drivers/ozone/ozone.h @@ -99,6 +99,7 @@ typedef struct font_data_t *font; video_font_raster_block_t raster_block; /* ptr alignment */ int glyph_width; + int wideglyph_width; int line_height; int line_ascender; int line_centre_offset; @@ -132,6 +133,9 @@ struct ozone_handle ozone_font_data_t sidebar; } fonts; + void (*word_wrap)(char *dst, size_t dst_size, const char *src, + int line_width, int wideglyph_width, unsigned max_lines); + struct { ozone_footer_label_t ok; diff --git a/menu/drivers/ozone/ozone_display.c b/menu/drivers/ozone/ozone_display.c index 5f40e2505f..63ac0d1709 100644 --- a/menu/drivers/ozone/ozone_display.c +++ b/menu/drivers/ozone/ozone_display.c @@ -495,7 +495,9 @@ void ozone_draw_osk(ozone_handle_t *ozone, text_color = ozone_theme_light.text_sublabel_rgba; } - word_wrap(message, text, (video_width - margin*2 - padding*2) / ozone->fonts.entries_label.glyph_width, true, 0); + (ozone->word_wrap)(message, sizeof(message), text, + (video_width - margin*2 - padding*2) / ozone->fonts.entries_label.glyph_width, + ozone->fonts.entries_label.wideglyph_width, 0); string_list_initialize(&list); string_split_noalloc(&list, message, "\n"); @@ -602,10 +604,10 @@ void ozone_draw_messagebox( return; /* Split message into lines */ - word_wrap( - wrapped_message, message, + (ozone->word_wrap)( + wrapped_message, sizeof(wrapped_message), message, usable_width / (int)ozone->fonts.footer.glyph_width, - true, 0); + ozone->fonts.footer.wideglyph_width, 0); string_list_initialize(&list); if ( diff --git a/menu/drivers/ozone/ozone_entries.c b/menu/drivers/ozone/ozone_entries.c index 7f6f86b948..b0a0cffd7c 100644 --- a/menu/drivers/ozone/ozone_entries.c +++ b/menu/drivers/ozone/ozone_entries.c @@ -400,9 +400,10 @@ void ozone_compute_entries_position( sublabel_max_width -= ozone->dimensions.thumbnail_bar_width; } - word_wrap(wrapped_sublabel_str, entry.sublabel, + (ozone->word_wrap)(wrapped_sublabel_str, sizeof(wrapped_sublabel_str), entry.sublabel, sublabel_max_width / - ozone->fonts.entries_sublabel.glyph_width, false, 0); + ozone->fonts.entries_sublabel.glyph_width, + ozone->fonts.entries_sublabel.wideglyph_width, 0); node->sublabel_lines = ozone_count_lines(wrapped_sublabel_str); @@ -763,7 +764,9 @@ border_iterate: } wrapped_sublabel_str[0] = '\0'; - word_wrap(wrapped_sublabel_str, sublabel_str, sublabel_max_width / ozone->fonts.entries_sublabel.glyph_width, false, 0); + (ozone->word_wrap)(wrapped_sublabel_str, sizeof(wrapped_sublabel_str), + sublabel_str, sublabel_max_width / ozone->fonts.entries_sublabel.glyph_width, + ozone->fonts.entries_sublabel.wideglyph_width, 0); sublabel_str = wrapped_sublabel_str; } } diff --git a/menu/drivers/rgui.c b/menu/drivers/rgui.c index 833bd7d3f3..ff8631edeb 100644 --- a/menu/drivers/rgui.c +++ b/menu/drivers/rgui.c @@ -3932,9 +3932,9 @@ static void rgui_render_messagebox(rgui_t *rgui, const char *message, /* Split message into lines */ word_wrap( - wrapped_message, message, + wrapped_message, sizeof(wrapped_message), message, rgui->term_layout.width, - true, 0); + 100, 0); string_list_initialize(&list); if ( !string_split_noalloc(&list, wrapped_message, "\n") diff --git a/menu/drivers/stripes.c b/menu/drivers/stripes.c index 7c64a13d5d..78f90c0a9d 100644 --- a/menu/drivers/stripes.c +++ b/menu/drivers/stripes.c @@ -286,6 +286,9 @@ typedef struct stripes_handle font_data_t *font2; video_font_raster_block_t raster_block; video_font_raster_block_t raster_block2; + + void (*word_wrap)(char *dst, size_t dst_size, const char *src, + int line_width, int wideglyph_width, unsigned max_lines); } stripes_handle_t; float stripes_scale_mod[8] = { @@ -2406,7 +2409,8 @@ static int stripes_draw_item( label_offset = - stripes->margins_label_top; - word_wrap(entry_sublabel, entry->sublabel, 50 * stripes_scale_mod[3], true, 0); + (stripes->word_wrap)(entry_sublabel, sizeof(entry_sublabel), + entry->sublabel, 50 * stripes_scale_mod[3], 100, 0); stripes_draw_text(xmb_shadows_enable, stripes, entry_sublabel, node->x + stripes->margins_screen_left + @@ -3374,6 +3378,9 @@ static void *stripes_init(void **userdata, bool video_is_threaded) file_list_initialize(&stripes->horizontal_list); stripes_init_horizontal_list(stripes); + /* set word_wrap function pointer */ + stripes->word_wrap = msg_hash_get_wideglyph_str() ? word_wrap_wideglyph : word_wrap; + return menu; error: diff --git a/menu/drivers/xmb.c b/menu/drivers/xmb.c index c8725bbc39..196ceadd69 100644 --- a/menu/drivers/xmb.c +++ b/menu/drivers/xmb.c @@ -302,6 +302,9 @@ typedef struct xmb_handle video_font_raster_block_t raster_block; video_font_raster_block_t raster_block2; + void (*word_wrap)(char *dst, size_t dst_size, const char *src, + int line_width, int wideglyph_width, unsigned max_lines); + menu_screensaver_t *screensaver; gfx_thumbnail_path_data_t *thumbnail_path_data; @@ -326,6 +329,7 @@ typedef struct xmb_handle int old_depth; int icon_size; int cursor_size; + int wideglyph_width; unsigned categories_active_idx; unsigned categories_active_idx_old; @@ -976,10 +980,10 @@ static void xmb_render_messagebox_internal( return; /* Split message into lines */ - word_wrap( - wrapped_message, message, + (xmb->word_wrap)( + wrapped_message, sizeof(wrapped_message), message, usable_width / (xmb->font_size * 0.6f), - true, 0); + xmb->wideglyph_width, 0); string_list_initialize(&list); @@ -5771,6 +5775,9 @@ static void *xmb_init(void **userdata, bool video_is_threaded) p_anim->updatetime_cb = xmb_menu_animation_update_time; + /* set word_wrap function pointer */ + xmb->word_wrap = msg_hash_get_wideglyph_str() ? word_wrap_wideglyph : word_wrap; + return menu; error: @@ -6280,6 +6287,7 @@ static void xmb_context_reset_internal(xmb_handle_t *xmb, char iconpath[PATH_MAX_LENGTH]; char bg_file_path[PATH_MAX_LENGTH]; gfx_display_t *p_disp = disp_get_ptr(); + const char *wideglyph_str = msg_hash_get_wideglyph_str(); iconpath[0] = bg_file_path[0] = '\0'; fill_pathname_application_special(bg_file_path, @@ -6315,6 +6323,19 @@ static void xmb_context_reset_internal(xmb_handle_t *xmb, xmb->font2_size, is_threaded); + xmb->wideglyph_width = 100; + + if (wideglyph_str) + { + int char_width = + font_driver_get_message_width(xmb->font, "a", 1, 1); + int wideglyph_width = + font_driver_get_message_width(xmb->font, wideglyph_str, strlen(wideglyph_str), 1); + + if (wideglyph_width > 0 && char_width > 0) + xmb->wideglyph_width = wideglyph_width * 100 / char_width; + } + if (reinit_textures) { xmb_context_reset_textures(xmb, iconpath); diff --git a/msg_hash.c b/msg_hash.c index 22e88a4cca..abbc02a9b1 100644 --- a/msg_hash.c +++ b/msg_hash.c @@ -536,3 +536,22 @@ void msg_hash_set_uint(enum msg_hash_action type, unsigned val) break; } } + +const char *msg_hash_get_wideglyph_str(void) +{ + switch (uint_user_language) + { + case RETRO_LANGUAGE_CHINESE_SIMPLIFIED: + return msg_hash_get_wideglyph_str_chs(); + case RETRO_LANGUAGE_CHINESE_TRADITIONAL: + return msg_hash_get_wideglyph_str_cht(); + case RETRO_LANGUAGE_JAPANESE: + return msg_hash_get_wideglyph_str_jp(); + case RETRO_LANGUAGE_KOREAN: + return msg_hash_get_wideglyph_str_ko(); + default: + break; + } + + return NULL; +} diff --git a/msg_hash.h b/msg_hash.h index e57bfcf90e..dcfe33174d 100644 --- a/msg_hash.h +++ b/msg_hash.h @@ -3269,6 +3269,37 @@ unsigned *msg_hash_get_uint(enum msg_hash_action type); void msg_hash_set_uint(enum msg_hash_action type, unsigned val); +/* Latin languages typically consist of regular + * alpha numeric characters with a 'standard' + * on-screen pixel width. + * Non-Latin languages (e.g. CJK) typically consist + * of so-called 'wide' Unicode glyphs, which may have + * an on-screen pixel width several times that of Latin + * characters. + * In order to determine efficiently the on-screen width + * of a text string (e.g. when word wrapping), it is + * therefore necessary to: + * - Identify which languages make use of 'wide' Unicode + * glyphs + * - For each of these languages, provide a mechanism for + * measuring the typical on-screen pixel width of + * language-specific 'wide' Unicode glyphs + * As such, msg_hash_get_wideglyph_str() returns a pointer + * to a 'wide' Unicode character of typical on-screen pixel + * width for the currently set user language. + * - If msg_hash_get_wideglyph_str() returns NULL, the current + * language is assumed to be Latin-based, with no usage + * of 'wide' Unicode glyphs + * - If msg_hash_get_wideglyph_str() returns a valid pointer, + * actual 'wide' glyph width for the current language may + * be found by passing said pointer to the current font + * rendering implementation */ +const char *msg_hash_get_wideglyph_str(void); +const char *msg_hash_get_wideglyph_str_chs(void); +const char *msg_hash_get_wideglyph_str_cht(void); +const char *msg_hash_get_wideglyph_str_jp(void); +const char *msg_hash_get_wideglyph_str_ko(void); + uint32_t msg_hash_calculate(const char *s); const char *get_user_language_iso639_1(bool limit); diff --git a/ui/drivers/qt/qt_dialogs.cpp b/ui/drivers/qt/qt_dialogs.cpp index c38bf79244..8d8ca36f1c 100644 --- a/ui/drivers/qt/qt_dialogs.cpp +++ b/ui/drivers/qt/qt_dialogs.cpp @@ -1195,8 +1195,15 @@ void CoreOptionsDialog::buildLayout() if (!string_is_empty(option->info)) { - char *new_info = strdup(option->info); - word_wrap(new_info, new_info, 50, true, 0); + char *new_info; + size_t new_info_len = strlen(option->info) + 1; + + new_info = (char *)malloc(new_info_len); + if (!new_info) + return; + new_info[0] = '\0'; + + word_wrap(new_info, new_info_len, option->info, 50, 100, 0); descLabel->setToolTip(new_info); combo_box->setToolTip(new_info); free(new_info);