Source code for evennia.utils.hex_colors

"""
Truecolor 24bit hex color support, on the form `|#00FF00`, `|[00FF00` or `|#0F0 or `|[#0F0`

"""

import re


[docs]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 Based on code from @InspectorCaracal """ _RE_FG = r"\|#" _RE_BG = r"\|\[#" _RE_FG_OR_BG = r"\|\[?#" _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_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 greyscale _GREYS = "abcdefghijklmnopqrstuvwxyz" TRUECOLOR_FG = rf"\x1b\[38;2;{_RE_BYTE};{_RE_BYTE};{_RE_BYTE}m" TRUECOLOR_BG = rf"\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) def _split_hex_to_bytes(self, tag: str) -> tuple[str, str, str]: """ Splits hex string into separate bytes: #00FF00 -> ('00', 'FF', '00') #CF3 -> ('CC', 'FF', '33') Args: tag (str): the tag to convert Returns: str: the text with converted tags """ strip_leading = re.compile(rf"{self._RE_FG_OR_BG}") tag = strip_leading.sub("", tag) if len(tag) == 6: # 6 digits r, g, b = (tag[i : i + 2] for i in range(0, 6, 2)) else: # 3 digits r, g, b = (tag[i : i + 1] * 2 for i in range(0, 3, 1)) return r, g, b def _grey_int(self, num: int) -> int: """ Returns a grey greyscale integer Returns: """ return round(max((int(num) - 8), 0) / 10) def _hue_int(self, num: int) -> int: return round(max((int(num) - 45), 0) / 40) def _hex_to_rgb_24_bit(self, hex_code: str) -> tuple[int, int, int]: """ Converts a hex color code (#000 or #000000) into a 3-int tuple (0, 255, 90) Args: hex_code (str): HTML hex color code Returns: 24-bit rgb tuple: (int, int, int) """ # Strip the leading indicator if present hex_code = re.sub(rf"{self._RE_FG_OR_BG}", "", hex_code) r, g, b = self._split_hex_to_bytes(hex_code) return int(r, 16), int(g, 16), int(b, 16) def _rgb_24_bit_to_256(self, r: int, g: int, b: int) -> tuple[int, int, int]: """ converts 0-255 hex color codes to 0-5 Args: r (int): red g (int): green b (int): blue Returns: 256 color rgb tuple: (int, int, int) """ return self._hue_int(r), self._hue_int(g), self._hue_int(b)
[docs] def sub_truecolor(self, match: re.Match, truecolor=False) -> str: """ Converts a hex string to xterm truecolor code, greyscale, or falls back to evennia xterm256 to be handled by sub_xterm256 Args: match (re.match): first group is the leading indicator, second is the tag truecolor (bool): return xterm truecolor or fallback Returns: Newly formatted indicator and tag (str) """ indicator, tag = match.groups() # Remove the # sign indicator = indicator.replace("#", "") r, g, b = self._hex_to_rgb_24_bit(tag) if not truecolor: # Fallback to xterm256 syntax r, g, b = self._rgb_24_bit_to_256(r, g, b) return f"{indicator}{r}{g}{b}" else: xtag = f"\033[" if "[" in indicator: # Background Color xtag += "4" else: xtag += "3" xtag += f"8;2;{r};{g};{b}m" return xtag
[docs] 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