From 0d7027ea4c7fb1862efeab2cc95e079ff97fc65d Mon Sep 17 00:00:00 2001 From: bruvzg <7645683+bruvzg@users.noreply.github.com> Date: Sat, 12 Oct 2024 16:48:49 +0300 Subject: [PATCH] Add support for 24-bit color escape sequences, simplify `print_rich` BBCode parsing. --- core/string/print_string.cpp | 236 +++++++++++++++++++++++++---------- doc/classes/@GlobalScope.xml | 1 - 2 files changed, 167 insertions(+), 70 deletions(-) diff --git a/core/string/print_string.cpp b/core/string/print_string.cpp index 0f019c405f0..adceb6365e6 100644 --- a/core/string/print_string.cpp +++ b/core/string/print_string.cpp @@ -93,80 +93,178 @@ void __print_line_rich(const String &p_string) { // Convert a subset of BBCode tags to ANSI escape codes for correct display in the terminal. // Support of those ANSI escape codes varies across terminal emulators, // especially for italic and strikethrough. - String p_string_ansi = p_string; - p_string_ansi = p_string_ansi.replace("[b]", "\u001b[1m"); - p_string_ansi = p_string_ansi.replace("[/b]", "\u001b[22m"); - p_string_ansi = p_string_ansi.replace("[i]", "\u001b[3m"); - p_string_ansi = p_string_ansi.replace("[/i]", "\u001b[23m"); - p_string_ansi = p_string_ansi.replace("[u]", "\u001b[4m"); - p_string_ansi = p_string_ansi.replace("[/u]", "\u001b[24m"); - p_string_ansi = p_string_ansi.replace("[s]", "\u001b[9m"); - p_string_ansi = p_string_ansi.replace("[/s]", "\u001b[29m"); + String output; + int pos = 0; + while (pos <= p_string.length()) { + int brk_pos = p_string.find_char('[', pos); - p_string_ansi = p_string_ansi.replace("[indent]", " "); - p_string_ansi = p_string_ansi.replace("[/indent]", ""); - p_string_ansi = p_string_ansi.replace("[code]", "\u001b[2m"); - p_string_ansi = p_string_ansi.replace("[/code]", "\u001b[22m"); - p_string_ansi = p_string_ansi.replace("[url]", ""); - p_string_ansi = p_string_ansi.replace("[/url]", ""); - p_string_ansi = p_string_ansi.replace("[center]", "\n\t\t\t"); - p_string_ansi = p_string_ansi.replace("[/center]", ""); - p_string_ansi = p_string_ansi.replace("[right]", "\n\t\t\t\t\t\t"); - p_string_ansi = p_string_ansi.replace("[/right]", ""); + if (brk_pos < 0) { + brk_pos = p_string.length(); + } - if (p_string_ansi.contains("[color")) { - p_string_ansi = p_string_ansi.replace("[color=black]", "\u001b[30m"); - p_string_ansi = p_string_ansi.replace("[color=red]", "\u001b[91m"); - p_string_ansi = p_string_ansi.replace("[color=green]", "\u001b[92m"); - p_string_ansi = p_string_ansi.replace("[color=lime]", "\u001b[92m"); - p_string_ansi = p_string_ansi.replace("[color=yellow]", "\u001b[93m"); - p_string_ansi = p_string_ansi.replace("[color=blue]", "\u001b[94m"); - p_string_ansi = p_string_ansi.replace("[color=magenta]", "\u001b[95m"); - p_string_ansi = p_string_ansi.replace("[color=pink]", "\u001b[38;5;218m"); - p_string_ansi = p_string_ansi.replace("[color=purple]", "\u001b[38;5;98m"); - p_string_ansi = p_string_ansi.replace("[color=cyan]", "\u001b[96m"); - p_string_ansi = p_string_ansi.replace("[color=white]", "\u001b[97m"); - p_string_ansi = p_string_ansi.replace("[color=orange]", "\u001b[38;5;208m"); - p_string_ansi = p_string_ansi.replace("[color=gray]", "\u001b[90m"); - p_string_ansi = p_string_ansi.replace("[/color]", "\u001b[39m"); - } - if (p_string_ansi.contains("[bgcolor")) { - p_string_ansi = p_string_ansi.replace("[bgcolor=black]", "\u001b[40m"); - p_string_ansi = p_string_ansi.replace("[bgcolor=red]", "\u001b[101m"); - p_string_ansi = p_string_ansi.replace("[bgcolor=green]", "\u001b[102m"); - p_string_ansi = p_string_ansi.replace("[bgcolor=lime]", "\u001b[102m"); - p_string_ansi = p_string_ansi.replace("[bgcolor=yellow]", "\u001b[103m"); - p_string_ansi = p_string_ansi.replace("[bgcolor=blue]", "\u001b[104m"); - p_string_ansi = p_string_ansi.replace("[bgcolor=magenta]", "\u001b[105m"); - p_string_ansi = p_string_ansi.replace("[bgcolor=pink]", "\u001b[48;5;218m"); - p_string_ansi = p_string_ansi.replace("[bgcolor=purple]", "\u001b[48;5;98m"); - p_string_ansi = p_string_ansi.replace("[bgcolor=cyan]", "\u001b[106m"); - p_string_ansi = p_string_ansi.replace("[bgcolor=white]", "\u001b[107m"); - p_string_ansi = p_string_ansi.replace("[bgcolor=orange]", "\u001b[48;5;208m"); - p_string_ansi = p_string_ansi.replace("[bgcolor=gray]", "\u001b[100m"); - p_string_ansi = p_string_ansi.replace("[/bgcolor]", "\u001b[49m"); - } - if (p_string_ansi.contains("[fgcolor")) { - p_string_ansi = p_string_ansi.replace("[fgcolor=black]", "\u001b[30;40m"); - p_string_ansi = p_string_ansi.replace("[fgcolor=red]", "\u001b[91;101m"); - p_string_ansi = p_string_ansi.replace("[fgcolor=green]", "\u001b[92;102m"); - p_string_ansi = p_string_ansi.replace("[fgcolor=lime]", "\u001b[92;102m"); - p_string_ansi = p_string_ansi.replace("[fgcolor=yellow]", "\u001b[93;103m"); - p_string_ansi = p_string_ansi.replace("[fgcolor=blue]", "\u001b[94;104m"); - p_string_ansi = p_string_ansi.replace("[fgcolor=magenta]", "\u001b[95;105m"); - p_string_ansi = p_string_ansi.replace("[fgcolor=pink]", "\u001b[38;5;218;48;5;218m"); - p_string_ansi = p_string_ansi.replace("[fgcolor=purple]", "\u001b[38;5;98;48;5;98m"); - p_string_ansi = p_string_ansi.replace("[fgcolor=cyan]", "\u001b[96;106m"); - p_string_ansi = p_string_ansi.replace("[fgcolor=white]", "\u001b[97;107m"); - p_string_ansi = p_string_ansi.replace("[fgcolor=orange]", "\u001b[38;5;208;48;5;208m"); - p_string_ansi = p_string_ansi.replace("[fgcolor=gray]", "\u001b[90;100m"); - p_string_ansi = p_string_ansi.replace("[/fgcolor]", "\u001b[39;49m"); + String txt = brk_pos > pos ? p_string.substr(pos, brk_pos - pos) : ""; + if (brk_pos == p_string.length()) { + output += txt; + break; + } + + int brk_end = p_string.find_char(']', brk_pos + 1); + + if (brk_end == -1) { + txt += p_string.substr(brk_pos, p_string.length() - brk_pos); + output += txt; + break; + } + pos = brk_end + 1; + output += txt; + + String tag = p_string.substr(brk_pos + 1, brk_end - brk_pos - 1); + if (tag == "b") { + output += "\u001b[1m"; + } else if (tag == "/b") { + output += "\u001b[22m"; + } else if (tag == "i") { + output += "\u001b[3m"; + } else if (tag == "/i") { + output += "\u001b[23m"; + } else if (tag == "u") { + output += "\u001b[4m"; + } else if (tag == "/u") { + output += "\u001b[24m"; + } else if (tag == "s") { + output += "\u001b[9m"; + } else if (tag == "/s") { + output += "\u001b[29m"; + } else if (tag == "indent") { + output += " "; + } else if (tag == "/indent") { + output += ""; + } else if (tag == "code") { + output += "\u001b[2m"; + } else if (tag == "/code") { + output += "\u001b[22m"; + } else if (tag == "url") { + output += ""; + } else if (tag == "/url") { + output += ""; + } else if (tag == "center") { + output += "\n\t\t\t"; + } else if (tag == "center") { + output += ""; + } else if (tag == "right") { + output += "\n\t\t\t\t\t\t"; + } else if (tag == "/right") { + output += ""; + } else if (tag.begins_with("color=")) { + String color_name = tag.trim_prefix("color="); + if (color_name == "black") { + output += "\u001b[30m"; + } else if (color_name == "red") { + output += "\u001b[91m"; + } else if (color_name == "green") { + output += "\u001b[92m"; + } else if (color_name == "lime") { + output += "\u001b[92m"; + } else if (color_name == "yellow") { + output += "\u001b[93m"; + } else if (color_name == "blue") { + output += "\u001b[94m"; + } else if (color_name == "magenta") { + output += "\u001b[95m"; + } else if (color_name == "pink") { + output += "\u001b[38;5;218m"; + } else if (color_name == "purple") { + output += "\u001b[38;5;98m"; + } else if (color_name == "cyan") { + output += "\u001b[96m"; + } else if (color_name == "white") { + output += "\u001b[97m"; + } else if (color_name == "orange") { + output += "\u001b[38;5;208m"; + } else if (color_name == "gray") { + output += "\u001b[90m"; + } else { + Color c = Color::from_string(color_name, Color()); + output += vformat("\u001b[38;2;%d;%d;%dm", c.r * 255, c.g * 255, c.b * 255); + } + } else if (tag == "/color") { + output += "\u001b[39m"; + } else if (tag.begins_with("bgcolor=")) { + String color_name = tag.trim_prefix("bgcolor="); + if (color_name == "black") { + output += "\u001b[40m"; + } else if (color_name == "red") { + output += "\u001b[101m"; + } else if (color_name == "green") { + output += "\u001b[102m"; + } else if (color_name == "lime") { + output += "\u001b[102m"; + } else if (color_name == "yellow") { + output += "\u001b[103m"; + } else if (color_name == "blue") { + output += "\u001b[104m"; + } else if (color_name == "magenta") { + output += "\u001b[105m"; + } else if (color_name == "pink") { + output += "\u001b[48;5;218m"; + } else if (color_name == "purple") { + output += "\u001b[48;5;98m"; + } else if (color_name == "cyan") { + output += "\u001b[106m"; + } else if (color_name == "white") { + output += "\u001b[107m"; + } else if (color_name == "orange") { + output += "\u001b[48;5;208m"; + } else if (color_name == "gray") { + output += "\u001b[100m"; + } else { + Color c = Color::from_string(color_name, Color()); + output += vformat("\u001b[48;2;%d;%d;%dm", c.r * 255, c.g * 255, c.b * 255); + } + } else if (tag == "/bgcolor") { + output += "\u001b[49m"; + } else if (tag.begins_with("fgcolor=")) { + String color_name = tag.trim_prefix("fgcolor="); + if (color_name == "black") { + output += "\u001b[30;40m"; + } else if (color_name == "red") { + output += "\u001b[91;101m"; + } else if (color_name == "green") { + output += "\u001b[92;102m"; + } else if (color_name == "lime") { + output += "\u001b[92;102m"; + } else if (color_name == "yellow") { + output += "\u001b[93;103m"; + } else if (color_name == "blue") { + output += "\u001b[94;104m"; + } else if (color_name == "magenta") { + output += "\u001b[95;105m"; + } else if (color_name == "pink") { + output += "\u001b[38;5;218;48;5;218m"; + } else if (color_name == "purple") { + output += "\u001b[38;5;98;48;5;98m"; + } else if (color_name == "cyan") { + output += "\u001b[96;106m"; + } else if (color_name == "white") { + output += "\u001b[97;107m"; + } else if (color_name == "orange") { + output += "\u001b[38;5;208;48;5;208m"; + } else if (color_name == "gray") { + output += "\u001b[90;100m"; + } else { + Color c = Color::from_string(color_name, Color()); + output += vformat("\u001b[38;2;%d;%d;%d;48;2;%d;%d;%dm", c.r * 255, c.g * 255, c.b * 255, c.r * 255, c.g * 255, c.b * 255); + } + } else if (tag == "/fgcolor") { + output += "\u001b[39;49m"; + } else { + output += vformat("[%s]", tag); + } } + output += "\u001b[0m"; // Reset. - p_string_ansi += "\u001b[0m"; // Reset. - - OS::get_singleton()->print_rich("%s\n", p_string_ansi.utf8().get_data()); + OS::get_singleton()->print_rich("%s\n", output.utf8().get_data()); _global_lock(); PrintHandlerList *l = print_handler_list; diff --git a/doc/classes/@GlobalScope.xml b/doc/classes/@GlobalScope.xml index 55d00b6cf90..0d0e0947edd 100644 --- a/doc/classes/@GlobalScope.xml +++ b/doc/classes/@GlobalScope.xml @@ -863,7 +863,6 @@ Converts one or more arguments of any type to string in the best way possible and prints them to the console. The following BBCode tags are supported: [code]b[/code], [code]i[/code], [code]u[/code], [code]s[/code], [code]indent[/code], [code]code[/code], [code]url[/code], [code]center[/code], [code]right[/code], [code]color[/code], [code]bgcolor[/code], [code]fgcolor[/code]. - Color tags only support the following named colors: [code]black[/code], [code]red[/code], [code]green[/code], [code]yellow[/code], [code]blue[/code], [code]magenta[/code], [code]pink[/code], [code]purple[/code], [code]cyan[/code], [code]white[/code], [code]orange[/code], [code]gray[/code]. Hexadecimal color codes are not supported. URL tags only support URLs wrapped by a URL tag, not URLs with a different title. When printing to standard output, the supported subset of BBCode is converted to ANSI escape codes for the terminal emulator to display. Support for ANSI escape codes varies across terminal emulators, especially for italic and strikethrough. In standard output, [code]code[/code] is represented with faint text but without any font change. Unsupported tags are left as-is in standard output. [codeblocks]