mirror of
https://github.com/evennia/evennia.git
synced 2026-03-16 21:06:30 +01:00
Added truecolor support to web portal.
This commit is contained in:
parent
de09f7a71c
commit
0cac9bf872
4 changed files with 92 additions and 19 deletions
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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"))
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue