Added truecolor support to web portal.

This commit is contained in:
mike 2024-04-07 00:16:59 -07:00
parent de09f7a71c
commit 0cac9bf872
4 changed files with 92 additions and 19 deletions

View file

@ -69,9 +69,9 @@ from django.conf import settings
from evennia.utils import logger, utils
from evennia.utils.utils import to_str
from evennia.utils.hex_colors import HexToTruecolor
from evennia.utils.hex_colors import HexColors
hex_sub = HexToTruecolor.hex_sub
hex_sub = HexColors.hex_sub
MXP_ENABLED = settings.MXP_ENABLED
@ -470,7 +470,7 @@ class ANSIParser(object):
string = self.brightbg_sub.sub(self.sub_brightbg, string)
def do_truecolor(part: re.Match, truecolor=truecolor):
hex2truecolor = HexToTruecolor()
hex2truecolor = HexColors()
return hex2truecolor.sub_truecolor(part, truecolor)
def do_xterm256_fg(part):

View file

@ -1,7 +1,7 @@
import re
class HexToTruecolor:
class HexColors:
"""
This houses a method for converting hex codes to xterm truecolor codes
or falls back to evennia xterm256 codes to be handled by sub_xterm256
@ -14,19 +14,18 @@ class HexToTruecolor:
_RE_FG_OR_BG = '\|\[?#'
_RE_HEX_LONG = '[0-9a-fA-F]{6}'
_RE_HEX_SHORT = '[0-9a-fA-F]{3}'
_RE_BYTE = '[0-2][0-9][0-9]'
_RE_3_BYTES = f'({_RE_BYTE})({_RE_BYTE})({_RE_BYTE})'
_RE_BYTE = '[0-2]?[0-9]?[0-9]'
_RE_XTERM_TRUECOLOR = rf'\[([34])8;2;({_RE_BYTE});({_RE_BYTE});({_RE_BYTE})m'
# Used in hex_sub
_RE_HEX_PATTERN = f'({_RE_FG_OR_BG})({_RE_HEX_LONG}|{_RE_HEX_SHORT})'
# Used for truecolor_sub
_RE_24_BIT_RGB_FG = f'{_RE_FG}{_RE_3_BYTES}'
_RE_24_BIT_RGB_BG = f'{_RE_BG}{_RE_3_BYTES}'
# Used for greyscale
_GREYS = "abcdefghijklmnopqrstuvwxyz"
TRUECOLOR_FG = f'\x1b\[38;2;{_RE_BYTE};{_RE_BYTE};{_RE_BYTE}m'
TRUECOLOR_BG = f'\x1b\[48;2;{_RE_BYTE};{_RE_BYTE};{_RE_BYTE}m'
# Our matchers for use with ANSIParser and ANSIString
hex_sub = re.compile(rf'{_RE_HEX_PATTERN}', re.DOTALL)
@ -73,6 +72,35 @@ class HexToTruecolor:
xtag += f"8;2;{r};{g};{b}m"
return xtag
def xterm_truecolor_to_html_style(self, fg="", bg="") -> str:
"""
Converts xterm truecolor to an html style property
Args:
fg: xterm truecolor
bg: xterm truecolor
Returns: style='color and or background-color'
"""
prop = 'style="'
if fg != '':
res = re.search(self._RE_XTERM_TRUECOLOR, fg, re.DOTALL)
fg_bg, r, g, b = res.groups()
r = hex(int(r))[2:].zfill(2)
g = hex(int(g))[2:].zfill(2)
b = hex(int(b))[2:].zfill(2)
prop += f"color: #{r}{g}{b};"
if bg != '':
res = re.search(self._RE_XTERM_TRUECOLOR, bg, re.DOTALL)
fg_bg, r, g, b = res.groups()
r = hex(int(r))[2:].zfill(2)
g = hex(int(g))[2:].zfill(2)
b = hex(int(b))[2:].zfill(2)
prop += f"background-color: #{r}{g}{b};"
prop += f'"'
return prop
def _split_hex_to_bytes(self, tag: str) -> tuple[str, str, str]:
"""
Splits hex string into separate bytes:

View file

@ -46,6 +46,14 @@ class TestText2Html(TestCase):
parser.format_styles("a " + ansi.ANSI_INVERSE + "red" + ansi.ANSI_NORMAL + "foo"),
)
# True Color
self.assertEqual(
'<span class="" style="color: #ff0000;">red</span>foo',
parser.format_styles(
f'\x1b[38;2;255;0;0m' + "red" + ansi.ANSI_NORMAL + "foo"
),
)
def test_remove_bells(self):
parser = text2html.HTML_PARSER
self.assertEqual("foo", parser.remove_bells("foo"))

View file

@ -13,11 +13,15 @@ from html import escape as html_escape
from .ansi import *
from .hex_colors import HexColors
# All xterm256 RGB equivalents
XTERM256_FG = "\033[38;5;{}m"
XTERM256_BG = "\033[48;5;{}m"
hex_colors = HexColors()
class TextToHTMLparser(object):
"""
@ -67,13 +71,11 @@ class TextToHTMLparser(object):
]
xterm_bg_codes = [XTERM256_BG.format(i + 16) for i in range(240)]
re_style = re.compile(
r"({})".format(
r"({}|{})".format(
"|".join(
style_codes + ansi_color_codes + xterm_fg_codes + ansi_bg_codes + xterm_bg_codes
).replace("[", r"\[")
)
).replace("[", r"\["), "|".join([HexColors.TRUECOLOR_FG, HexColors.TRUECOLOR_BG]))
)
colorlist = (
@ -244,6 +246,7 @@ class TextToHTMLparser(object):
# split out the ANSI codes and clean out any empty items
str_list = [substr for substr in self.re_style.split(text) if substr]
# initialize all the flags and classes
classes = []
clean = True
@ -253,6 +256,8 @@ class TextToHTMLparser(object):
fg = ANSI_WHITE
# default bg is black
bg = ANSI_BACK_BLACK
truecolor_fg = ''
truecolor_bg = ''
for i, substr in enumerate(str_list):
# reset all current styling
@ -266,6 +271,8 @@ class TextToHTMLparser(object):
hilight = ANSI_UNHILITE
fg = ANSI_WHITE
bg = ANSI_BACK_BLACK
truecolor_fg = ''
truecolor_bg = ''
# change color
elif substr in self.ansi_color_codes + self.xterm_fg_codes:
@ -281,6 +288,14 @@ class TextToHTMLparser(object):
# set new bg
bg = substr
elif re.match(hex_colors.TRUECOLOR_FG, substr):
str_list[i] = ''
truecolor_fg = substr
elif re.match(hex_colors.TRUECOLOR_BG, substr):
str_list[i] = ""
truecolor_bg = substr
# non-color codes
elif substr in self.style_codes:
# erase ANSI code from output
@ -319,9 +334,23 @@ class TextToHTMLparser(object):
color_index = self.colorlist.index(fg)
if inverse:
# inverse means swap fg and bg indices
bg_class = "bgcolor-{}".format(str(color_index).rjust(3, "0"))
color_class = "color-{}".format(str(bg_index).rjust(3, "0"))
if truecolor_fg != '' and truecolor_bg != '':
# True startcolor only
truecolor_fg, truecolor_bg = truecolor_bg, truecolor_fg
elif truecolor_fg != '' and truecolor_bg == '':
# Truecolor fg, class based bg
truecolor_bg = truecolor_fg
truecolor_fg = ''
color_class = "color-{}".format(str(bg_index).rjust(3, "0"))
elif truecolor_fg == '' and truecolor_bg != '':
# Truecolor bg, class based fg
truecolor_fg = truecolor_bg
truecolor_bg = ''
bg_class = "bgcolor-{}".format(str(color_index).rjust(3, "0"))
else:
# inverse means swap fg and bg indices
bg_class = "bgcolor-{}".format(str(color_index).rjust(3, "0"))
color_class = "color-{}".format(str(bg_index).rjust(3, "0"))
else:
# use fg and bg indices for classes
bg_class = "bgcolor-{}".format(str(bg_index).rjust(3, "0"))
@ -333,8 +362,16 @@ class TextToHTMLparser(object):
# light grey text is the default, don't explicitly style
if color_class != "color-007":
classes.append(color_class)
# define the new style span
prefix = '<span class="{}">'.format(" ".join(classes))
if truecolor_fg == '' and truecolor_bg == '':
prefix = f'<span class="{" ".join(classes)}">'
else:
# Classes can't be used for true color
prefix = (f'<span '
f'class="{" ".join(classes)}" '
f'{hex_colors.xterm_truecolor_to_html_style(fg=truecolor_fg, bg=truecolor_bg)}>')
# close any prior span
if not clean:
prefix = "</span>" + prefix
@ -366,7 +403,7 @@ class TextToHTMLparser(object):
"""
# parse everything to ansi first
text = parse_ansi(text, strip_ansi=strip_ansi, xterm256=True, mxp=True)
text = parse_ansi(text, strip_ansi=strip_ansi, xterm256=True, mxp=True, truecolor=True)
# convert all ansi to html
result = re.sub(self.re_string, self.sub_text, text)
result = re.sub(self.re_mxplink, self.sub_mxp_links, result)