diff --git a/config.def.h b/config.def.h index fda9c97c41..3b779d0b4d 100644 --- a/config.def.h +++ b/config.def.h @@ -392,6 +392,7 @@ static bool rgui_full_width_layout = true; static unsigned rgui_aspect = RGUI_ASPECT_RATIO_4_3; static unsigned rgui_aspect_lock = RGUI_ASPECT_RATIO_LOCK_NONE; static bool rgui_shadows = false; +static bool rgui_extended_ascii = false; #else static bool default_block_config_read = false; diff --git a/configuration.c b/configuration.c index 491e3bfb65..d4bdb2f3ba 100644 --- a/configuration.c +++ b/configuration.c @@ -1512,6 +1512,7 @@ static struct config_bool_setting *populate_settings_bool(settings_t *settings, SETTING_BOOL("menu_rgui_full_width_layout", &settings->bools.menu_rgui_full_width_layout, true, rgui_full_width_layout, false); SETTING_BOOL("rgui_inline_thumbnails", &settings->bools.menu_rgui_inline_thumbnails, true, rgui_inline_thumbnails, false); SETTING_BOOL("rgui_swap_thumbnails", &settings->bools.menu_rgui_swap_thumbnails, true, rgui_swap_thumbnails, false); + SETTING_BOOL("rgui_extended_ascii", &settings->bools.menu_rgui_extended_ascii, true, rgui_extended_ascii, false); #endif #ifdef HAVE_XMB SETTING_BOOL("xmb_shadows_enable", &settings->bools.menu_xmb_shadows_enable, true, xmb_shadows_enable, false); diff --git a/configuration.h b/configuration.h index b53db75971..25852cac04 100644 --- a/configuration.h +++ b/configuration.h @@ -175,6 +175,7 @@ typedef struct settings bool menu_rgui_shadows; bool menu_rgui_inline_thumbnails; bool menu_rgui_swap_thumbnails; + bool menu_rgui_extended_ascii; bool menu_xmb_shadows_enable; bool menu_xmb_vertical_thumbnails; bool menu_content_show_settings; diff --git a/intl/msg_hash_lbl.h b/intl/msg_hash_lbl.h index b70d3fb6fa..832ce5fa61 100644 --- a/intl/msg_hash_lbl.h +++ b/intl/msg_hash_lbl.h @@ -1627,6 +1627,8 @@ MSG_HASH(MENU_ENUM_LABEL_MENU_RGUI_FULL_WIDTH_LAYOUT, "menu_rgui_full_width_layout") MSG_HASH(MENU_ENUM_LABEL_MENU_RGUI_SHADOWS, "menu_rgui_shadows") +MSG_HASH(MENU_ENUM_LABEL_MENU_RGUI_EXTENDED_ASCII, + "rgui_extended_ascii") MSG_HASH(MENU_ENUM_LABEL_CONTENT_SHOW_REWIND, "menu_show_rewind_settings") MSG_HASH(MENU_ENUM_LABEL_CONTENT_SHOW_LATENCY, diff --git a/intl/msg_hash_us.h b/intl/msg_hash_us.h index 252f8bcc99..7e4e9f057c 100644 --- a/intl/msg_hash_us.h +++ b/intl/msg_hash_us.h @@ -6916,6 +6916,14 @@ MSG_HASH( MENU_ENUM_SUBLABEL_MENU_RGUI_SHADOWS, "Enable drop shadows for menu text, borders and thumbnails. Has a modest performance impact." ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_MENU_RGUI_EXTENDED_ASCII, + "Extended ASCII Support" + ) +MSG_HASH( + MENU_ENUM_SUBLABEL_MENU_RGUI_EXTENDED_ASCII, + "Enable display of non-standard ASCII characters. Required for compatibility with certain non-English Western languages. Has a moderate performance impact." + ) MSG_HASH( MENU_ENUM_SUBLABEL_CRT_SWITCH_RESOLUTION, "For CRT displays only. Attempts to use exact core/game resolution and refresh rate." diff --git a/menu/cbs/menu_cbs_sublabel.c b/menu/cbs/menu_cbs_sublabel.c index f2cb33e60b..f05409021d 100644 --- a/menu/cbs/menu_cbs_sublabel.c +++ b/menu/cbs/menu_cbs_sublabel.c @@ -542,6 +542,7 @@ default_sublabel_macro(action_bind_sublabel_menu_ticker_speed, default_sublabel_macro(action_bind_sublabel_playlist_show_inline_core_name, MENU_ENUM_SUBLABEL_PLAYLIST_SHOW_INLINE_CORE_NAME) default_sublabel_macro(action_bind_sublabel_playlist_sort_alphabetical, MENU_ENUM_SUBLABEL_PLAYLIST_SORT_ALPHABETICAL) default_sublabel_macro(action_bind_sublabel_menu_rgui_full_width_layout, MENU_ENUM_SUBLABEL_MENU_RGUI_FULL_WIDTH_LAYOUT) +default_sublabel_macro(action_bind_sublabel_menu_rgui_extended_ascii, MENU_ENUM_SUBLABEL_MENU_RGUI_EXTENDED_ASCII) default_sublabel_macro(action_bind_sublabel_help_send_debug_info, MENU_ENUM_SUBLABEL_HELP_SEND_DEBUG_INFO) static int action_bind_sublabel_systeminfo_controller_entry( @@ -2467,6 +2468,9 @@ int menu_cbs_init_bind_sublabel(menu_file_list_cbs_t *cbs, case MENU_ENUM_LABEL_MENU_RGUI_FULL_WIDTH_LAYOUT: BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_menu_rgui_full_width_layout); break; + case MENU_ENUM_LABEL_MENU_RGUI_EXTENDED_ASCII: + BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_menu_rgui_extended_ascii); + break; case MENU_ENUM_LABEL_HELP_SEND_DEBUG_INFO: BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_help_send_debug_info); break; diff --git a/menu/drivers/rgui.c b/menu/drivers/rgui.c index 8e77f7d889..23ac36b82b 100644 --- a/menu/drivers/rgui.c +++ b/menu/drivers/rgui.c @@ -75,6 +75,9 @@ #define RGUI_TICKER_SPACER " | " +#define NUM_FONT_GLYPHS_REGULAR 128 +#define NUM_FONT_GLYPHS_EXTENDED 256 + typedef struct { unsigned start_x; @@ -479,6 +482,7 @@ typedef struct bool border_thickness; bool border_enable; bool shadow_enable; + bool extended_ascii_enable; float scroll_y; char *msgbox; unsigned color_theme; @@ -508,7 +512,7 @@ typedef struct static unsigned mini_thumbnail_max_width = 0; static unsigned mini_thumbnail_max_height = 0; -static bool font_lut[128][FONT_WIDTH * FONT_HEIGHT]; +static bool font_lut[NUM_FONT_GLYPHS_EXTENDED][FONT_WIDTH * FONT_HEIGHT]; typedef struct { @@ -1490,107 +1494,233 @@ static void prepare_rgui_colors(rgui_t *rgui, settings_t *settings) rgui->force_redraw = true; } -static void blit_line(int x, int y, - const char *message, uint16_t color, uint16_t shadow_color, bool draw_shadow) +/* ============================== + * blit_line() START + * ============================== */ + +/* NOTE: These functions are WET (Write Everything Twice). + * This is bad design and difficult to maintain, but we have + * no other choice here. blit_line() is so performance + * critical that we simply cannot afford to check user + * settings internally. */ + +static void blit_line_regular(int x, int y, + const char *message, uint16_t color, uint16_t shadow_color) { - unsigned fb_width = rgui_frame_buf.width; + unsigned fb_width = rgui_frame_buf.width; uint16_t *frame_buf_data = rgui_frame_buf.data; - /* Note: We can only display ASCII characters from 0-127. - * Everything else is rendered as garbage (incorrect glyphs - * from extended ASCII set). This is due to the fact that - * char arrays use a signed 8 bit data type [-128 to 127], - * so values greater than 127 are 'written' across multiple bytes. - * This is a UTF-8 encoding issue, and it's too difficult and - * too performance intensive to deal with here. - * Since drawing these multi-byte characters is a huge problem - * (we could end up with a string that exceeds the dimensions - * of the framebuffer, and get a segfault...) we shall - * skip drawing any character greater than 127 */ - - /* We're going to do some ugly loop unswitching here - * because rendering text is *very* expensive and I don't - * want to rely on the compiler to optimise this properly... */ - if (draw_shadow) + while (!string_is_empty(message)) { - /* Small performance hack... */ - uint32_t shadow_colour_32 = shadow_color; - shadow_colour_32 |= shadow_colour_32 << 16; + unsigned i, j; + uint8_t symbol = (uint8_t)*message++; - /* Drop shadow version */ - while (!string_is_empty(message)) + if (symbol >= NUM_FONT_GLYPHS_REGULAR) + continue; + + if (symbol != ' ') { - unsigned i, j; - uint8_t symbol = (uint8_t)*message++; - - if (symbol > 127) - continue; - - if (symbol != ' ') + for (j = 0; j < FONT_HEIGHT; j++) { - for (j = 0; j < FONT_HEIGHT; j++) + unsigned buff_offset = ((y + j) * fb_width) + x; + + for (i = 0; i < FONT_WIDTH; i++) { - unsigned buff_offset = ((y + j) * fb_width) + x; + if (font_lut[symbol][i + (j * FONT_WIDTH)]) + *(frame_buf_data + buff_offset + i) = color; + } + } + } - for (i = 0; i < FONT_WIDTH; i++) + x += FONT_WIDTH_STRIDE; + } +} + +static void blit_line_regular_shadow(int x, int y, + const char *message, uint16_t color, uint16_t shadow_color) +{ + unsigned fb_width = rgui_frame_buf.width; + uint16_t *frame_buf_data = rgui_frame_buf.data; + uint32_t shadow_colour_32 = shadow_color; + + /* Small performance hack... */ + shadow_colour_32 |= shadow_colour_32 << 16; + + while (!string_is_empty(message)) + { + unsigned i, j; + uint8_t symbol = (uint8_t)*message++; + + if (symbol >= NUM_FONT_GLYPHS_REGULAR) + continue; + + if (symbol != ' ') + { + for (j = 0; j < FONT_HEIGHT; j++) + { + unsigned buff_offset = ((y + j) * fb_width) + x; + + for (i = 0; i < FONT_WIDTH; i++) + { + if (font_lut[symbol][i + (j * FONT_WIDTH)]) { - if (font_lut[symbol][i + (j * FONT_WIDTH)]) - { - uint16_t *frame_buf_ptr = frame_buf_data + buff_offset + i; + uint16_t *frame_buf_ptr = frame_buf_data + buff_offset + i; - /* Text pixel */ - *frame_buf_ptr = color; + /* Text pixel */ + *frame_buf_ptr = color; - /* Shadow pixels */ - frame_buf_ptr++; - *frame_buf_ptr = shadow_color; - frame_buf_ptr += fb_width - 1; - /* Small performance hack... */ - *(uint32_t *)frame_buf_ptr = shadow_colour_32; - } + /* Shadow pixels */ + frame_buf_ptr++; + *frame_buf_ptr = shadow_color; + frame_buf_ptr += fb_width - 1; + /* Small performance hack... */ + *(uint32_t *)frame_buf_ptr = shadow_colour_32; } } } - - x += FONT_WIDTH_STRIDE; } + + x += FONT_WIDTH_STRIDE; + } +} + +static void blit_line_extended(int x, int y, + const char *message, uint16_t color, uint16_t shadow_color) +{ + unsigned fb_width = rgui_frame_buf.width; + uint16_t *frame_buf_data = rgui_frame_buf.data; + + while (!string_is_empty(message)) + { + /* Deal with spaces first, for efficiency */ + if (*message == ' ') + message++; + else + { + unsigned i, j; + uint32_t symbol = utf8_walk(&message); + + /* Stupid cretinous hack: 'oe' ligatures are not + * really standard extended ASCII, so we have to + * waste CPU cycles performing a conversion from + * the unicode values... + * (Note: This is only really required for msg_hash_fr.h) */ + if (symbol == 339) /* Latin small ligature oe */ + symbol = 156; + if (symbol == 338) /* Latin capital ligature oe */ + symbol = 140; + + if (symbol >= NUM_FONT_GLYPHS_EXTENDED) + continue; + + for (j = 0; j < FONT_HEIGHT; j++) + { + unsigned buff_offset = ((y + j) * fb_width) + x; + + for (i = 0; i < FONT_WIDTH; i++) + { + if (font_lut[symbol][i + (j * FONT_WIDTH)]) + *(frame_buf_data + buff_offset + i) = color; + } + } + } + + x += FONT_WIDTH_STRIDE; + } +} + +static void blit_line_extended_shadow(int x, int y, + const char *message, uint16_t color, uint16_t shadow_color) +{ + unsigned fb_width = rgui_frame_buf.width; + uint16_t *frame_buf_data = rgui_frame_buf.data; + uint32_t shadow_colour_32 = shadow_color; + + /* Small performance hack... */ + shadow_colour_32 |= shadow_colour_32 << 16; + + while (!string_is_empty(message)) + { + /* Deal with spaces first, for efficiency */ + if (*message == ' ') + message++; + else + { + unsigned i, j; + uint32_t symbol = utf8_walk(&message); + + /* Stupid cretinous hack: 'oe' ligatures are not + * really standard extended ASCII, so we have to + * waste CPU cycles performing a conversion from + * the unicode values... + * (Note: This is only really required for msg_hash_fr.h) */ + if (symbol == 339) /* Latin small ligature oe */ + symbol = 156; + if (symbol == 338) /* Latin capital ligature oe */ + symbol = 140; + + if (symbol >= NUM_FONT_GLYPHS_EXTENDED) + continue; + + for (j = 0; j < FONT_HEIGHT; j++) + { + unsigned buff_offset = ((y + j) * fb_width) + x; + + for (i = 0; i < FONT_WIDTH; i++) + { + if (font_lut[symbol][i + (j * FONT_WIDTH)]) + { + uint16_t *frame_buf_ptr = frame_buf_data + buff_offset + i; + + /* Text pixel */ + *frame_buf_ptr = color; + + /* Shadow pixels */ + frame_buf_ptr++; + *frame_buf_ptr = shadow_color; + frame_buf_ptr += fb_width - 1; + /* Small performance hack... */ + *(uint32_t *)frame_buf_ptr = shadow_colour_32; + } + } + } + } + + x += FONT_WIDTH_STRIDE; + } +} + +static void (*blit_line)(int x, int y, + const char *message, uint16_t color, uint16_t shadow_color) = blit_line_regular; + +static void rgui_set_blit_line_function(bool draw_shadow, bool extended_ascii) +{ + if (draw_shadow) + { + if (extended_ascii) + blit_line = blit_line_extended_shadow; + else + blit_line = blit_line_regular_shadow; } else { - /* Normal version */ - while (!string_is_empty(message)) - { - unsigned i, j; - uint8_t symbol = (uint8_t)*message++; - - if (symbol > 127) - continue; - - if (symbol != ' ') - { - for (j = 0; j < FONT_HEIGHT; j++) - { - unsigned buff_offset = ((y + j) * fb_width) + x; - - for (i = 0; i < FONT_WIDTH; i++) - { - if (font_lut[symbol][i + (j * FONT_WIDTH)]) - *(frame_buf_data + buff_offset + i) = color; - } - } - } - - x += FONT_WIDTH_STRIDE; - } + if (extended_ascii) + blit_line = blit_line_extended; + else + blit_line = blit_line_regular; } } +/* ============================== + * blit_line() END + * ============================== */ + static void rgui_init_font_lut(void) { unsigned symbol_index, i, j; /* Loop over all possible characters */ - for (symbol_index = 0; symbol_index < 128; symbol_index++) + for (symbol_index = 0; symbol_index < NUM_FONT_GLYPHS_EXTENDED; symbol_index++) { for (j = 0; j < FONT_HEIGHT; j++) { @@ -1714,7 +1844,7 @@ static void rgui_render_messagebox(rgui_t *rgui, const char *message) if (rgui_frame_buf.data) blit_line(x + 8 + offset_x, y + 8 + offset_y, msg, - rgui->colors.normal_color, rgui->colors.shadow_color, rgui->shadow_enable); + rgui->colors.normal_color, rgui->colors.shadow_color); } end: @@ -1903,7 +2033,7 @@ static void rgui_render(void *data, bool is_idle) /* Draw thumbnail title */ blit_line((int)title_x, 0, thumbnail_title_buf, - rgui->colors.hover_color, rgui->colors.shadow_color, rgui->shadow_enable); + rgui->colors.hover_color, rgui->colors.shadow_color); } } else @@ -1960,7 +2090,7 @@ static void rgui_render(void *data, bool is_idle) (int)(RGUI_TERM_START_X(fb_width) + (RGUI_TERM_WIDTH(fb_width) - utf8len(title_buf)) * FONT_WIDTH_STRIDE / 2), RGUI_TERM_START_Y(fb_height) - FONT_HEIGHT_STRIDE, - title_buf, rgui->colors.title_color, rgui->colors.shadow_color, rgui->shadow_enable); + title_buf, rgui->colors.title_color, rgui->colors.shadow_color); /* Print menu entries */ x = RGUI_TERM_START_X(fb_width); @@ -2099,7 +2229,7 @@ static void rgui_render(void *data, bool is_idle) blit_line(x, y, message, entry_selected ? rgui->colors.hover_color : rgui->colors.normal_color, - rgui->colors.shadow_color, rgui->shadow_enable); + rgui->colors.shadow_color); menu_entry_free(&entry); } @@ -2131,7 +2261,7 @@ static void rgui_render(void *data, bool is_idle) RGUI_TERM_START_X(fb_width) + FONT_WIDTH_STRIDE, (RGUI_TERM_HEIGHT(fb_height) * FONT_HEIGHT_STRIDE) + RGUI_TERM_START_Y(fb_height) + 2, sublabel_buf, - rgui->colors.hover_color, rgui->colors.shadow_color, rgui->shadow_enable); + rgui->colors.hover_color, rgui->colors.shadow_color); } else if (settings->bools.menu_core_enable) { @@ -2152,7 +2282,7 @@ static void rgui_render(void *data, bool is_idle) RGUI_TERM_START_X(fb_width) + FONT_WIDTH_STRIDE, (RGUI_TERM_HEIGHT(fb_height) * FONT_HEIGHT_STRIDE) + RGUI_TERM_START_Y(fb_height) + 2, core_title_buf, - rgui->colors.hover_color, rgui->colors.shadow_color, rgui->shadow_enable); + rgui->colors.hover_color, rgui->colors.shadow_color); } } @@ -2181,7 +2311,7 @@ static void rgui_render(void *data, bool is_idle) timedate_x, (RGUI_TERM_HEIGHT(fb_height) * FONT_HEIGHT_STRIDE) + RGUI_TERM_START_Y(fb_height) + 2, timedate, - rgui->colors.hover_color, rgui->colors.shadow_color, rgui->shadow_enable); + rgui->colors.hover_color, rgui->colors.shadow_color); } } } @@ -2578,14 +2708,19 @@ static void *rgui_init(void **userdata, bool video_is_threaded) rgui_init_font_lut(); - rgui->bg_thickness = settings->bools.menu_rgui_background_filler_thickness_enable; - rgui->border_thickness = settings->bools.menu_rgui_border_filler_thickness_enable; - rgui->border_enable = settings->bools.menu_rgui_border_filler_enable; - rgui->shadow_enable = settings->bools.menu_rgui_shadows; + rgui->bg_thickness = settings->bools.menu_rgui_background_filler_thickness_enable; + rgui->border_thickness = settings->bools.menu_rgui_border_filler_thickness_enable; + rgui->border_enable = settings->bools.menu_rgui_border_filler_enable; + rgui->shadow_enable = settings->bools.menu_rgui_shadows; + rgui->extended_ascii_enable = settings->bools.menu_rgui_extended_ascii; rgui->last_width = rgui_frame_buf.width; rgui->last_height = rgui_frame_buf.height; + /* Set initial 'blit_line' function */ + rgui_set_blit_line_function( + settings->bools.menu_rgui_shadows, settings->bools.menu_rgui_extended_ascii); + rgui->thumbnail_path_data = menu_thumbnail_path_init(); if (!rgui->thumbnail_path_data) goto error; @@ -2647,21 +2782,33 @@ static void rgui_frame(void *data, video_frame_info_t *video_info) { rgui->border_thickness = settings->bools.menu_rgui_border_filler_thickness_enable; rgui->bg_modified = true; - rgui->force_redraw = true; + rgui->force_redraw = true; } if (settings->bools.menu_rgui_border_filler_enable != rgui->border_enable) { rgui->border_enable = settings->bools.menu_rgui_border_filler_enable; rgui->bg_modified = true; - rgui->force_redraw = true; + rgui->force_redraw = true; } if (settings->bools.menu_rgui_shadows != rgui->shadow_enable) { + rgui_set_blit_line_function( + settings->bools.menu_rgui_shadows, settings->bools.menu_rgui_extended_ascii); + rgui->shadow_enable = settings->bools.menu_rgui_shadows; rgui->bg_modified = true; - rgui->force_redraw = true; + rgui->force_redraw = true; + } + + if (settings->bools.menu_rgui_extended_ascii != rgui->extended_ascii_enable) + { + rgui_set_blit_line_function( + settings->bools.menu_rgui_shadows, settings->bools.menu_rgui_extended_ascii); + + rgui->extended_ascii_enable = settings->bools.menu_rgui_extended_ascii; + rgui->force_redraw = true; } if (settings->uints.menu_rgui_color_theme != rgui->color_theme) diff --git a/menu/menu_displaylist.c b/menu/menu_displaylist.c index 0aa0b97034..6edd0d548a 100644 --- a/menu/menu_displaylist.c +++ b/menu/menu_displaylist.c @@ -6246,6 +6246,10 @@ bool menu_displaylist_ctl(enum menu_displaylist_ctl_state type, menu_displaylist MENU_ENUM_LABEL_MENU_TICKER_SPEED, PARSE_ONLY_FLOAT, false) == 0) count++; + if (menu_displaylist_parse_settings_enum(menu, info, + MENU_ENUM_LABEL_MENU_RGUI_EXTENDED_ASCII, + PARSE_ONLY_BOOL, false) == 0) + count++; if (count == 0) menu_entries_append_enum(info->list, diff --git a/menu/menu_setting.c b/menu/menu_setting.c index d374ff1873..4971b40221 100644 --- a/menu/menu_setting.c +++ b/menu/menu_setting.c @@ -8526,6 +8526,21 @@ static bool setting_append_list( general_write_handler, general_read_handler, SD_FLAG_NONE); + + CONFIG_BOOL( + list, list_info, + &settings->bools.menu_rgui_extended_ascii, + MENU_ENUM_LABEL_MENU_RGUI_EXTENDED_ASCII, + MENU_ENUM_LABEL_VALUE_MENU_RGUI_EXTENDED_ASCII, + rgui_extended_ascii, + MENU_ENUM_LABEL_VALUE_OFF, + MENU_ENUM_LABEL_VALUE_ON, + &group_info, + &subgroup_info, + parent_group, + general_write_handler, + general_read_handler, + SD_FLAG_NONE); } if (string_is_equal(settings->arrays.menu_driver, "xmb")) diff --git a/msg_hash.h b/msg_hash.h index 6b840ec10c..2b5d897b7d 100644 --- a/msg_hash.h +++ b/msg_hash.h @@ -872,6 +872,7 @@ enum msg_hash_enums MENU_LABEL(MENU_RGUI_ASPECT_RATIO_LOCK), MENU_LABEL(MENU_RGUI_FULL_WIDTH_LAYOUT), MENU_LABEL(MENU_RGUI_SHADOWS), + MENU_LABEL(MENU_RGUI_EXTENDED_ASCII), MENU_LABEL(MENU_LINEAR_FILTER), MENU_LABEL(MENU_HORIZONTAL_ANIMATION), MENU_LABEL(NAVIGATION_WRAPAROUND),