Implement to and from html color parsing.

This commit is contained in:
poljar (Damir Jelić) 2018-01-25 12:33:14 +01:00
parent 3351e3b4dd
commit 918489fb1f

View file

@ -10,6 +10,7 @@ import datetime
import pprint
import re
import sys
import webcolors
# pylint: disable=redefined-builtin
from builtins import bytes, str
@ -588,7 +589,7 @@ Default_format_attributes = {
}
def line_color_to_sgr(color_string):
def color_line_to_weechat(color_string):
# type: (str) -> str
line_colors = {
"0": "white",
@ -698,12 +699,396 @@ def line_color_to_sgr(color_string):
return line_colors[color_string]
def html_color_to_sgr(color):
pass
# The functions colour_dist_sq(), colour_to_6cube(), and colour_find_rgb
# are python ports of the same named functions from the tmux
# source, they are under the copyright of Nicholas Marriott, and Avi Halachmi
# under the ISC license.
# More info: https://github.com/tmux/tmux/blob/master/colour.c
def colour_dist_sq(R, G, B, r, g, b):
# pylint: disable=invalid-name,too-many-arguments
# type: (int, int, int, int, int, int) -> int
return (R - r) * (R - r) + (G - g) * (G - g) + (B - b) * (B - b)
def sgr_to_rgb(color):
pass
def colour_to_6cube(v):
# pylint: disable=invalid-name
# type: (int) -> int
if v < 48:
return 0
if v < 114:
return 1
return (v - 35) // 40
def colour_find_rgb(r, g, b):
"""Convert an RGB triplet to the xterm(1) 256 colour palette.
xterm provides a 6x6x6 colour cube (16 - 231) and 24 greys (232 - 255).
We map our RGB colour to the closest in the cube, also work out the
closest grey, and use the nearest of the two.
Note that the xterm has much lower resolution for darker colours (they
are not evenly spread out), so our 6 levels are not evenly spread: 0x0,
0x5f (95), 0x87 (135), 0xaf (175), 0xd7 (215) and 0xff (255). Greys are
more evenly spread (8, 18, 28 ... 238).
"""
# pylint: disable=invalid-name
# type: (int, int, int) -> int
q2c = [0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff]
# Map RGB to 6x6x6 cube.
qr = colour_to_6cube(r)
qg = colour_to_6cube(g)
qb = colour_to_6cube(b)
cr = q2c[qr]
cg = q2c[qg]
cb = q2c[qb]
# If we have hit the colour exactly, return early.
if (cr == r and cg == g and cb == b):
return 16 + (36 * qr) + (6 * qg) + qb
# Work out the closest grey (average of RGB).
grey_avg = (r + g + b) // 3
if grey_avg > 238:
grey_idx = 23
else:
grey_idx = (grey_avg - 3) // 10
grey = 8 + (10 * grey_idx)
# Is grey or 6x6x6 colour closest?
d = colour_dist_sq(cr, cg, cb, r, g, b)
if colour_dist_sq(grey, grey, grey, r, g, b) < d:
idx = 232 + grey_idx
else:
idx = 16 + (36 * qr) + (6 * qg) + qb
return idx
def color_html_to_weechat(color):
# type: (str) -> str
first_16 = {
(0, 0, 0): "black", # 0
(128, 0, 0): "red", # 1
(0, 128, 0): "green", # 2
(128, 128, 0): "brown", # 3
(0, 0, 128): "blue", # 4
(128, 0, 128): "magenta", # 5
(0, 128, 128): "cyan", # 6
(192, 192, 192): "default", # 7
(128, 128, 128): "gray", # 8
(255, 0, 0): "lightred", # 9
(0, 255, 0): "lightgreen", # 11
(255, 255, 0): "yellow", # 12
(0, 0, 255): "lightblue", # 13
(255, 0, 255): "lightmagenta", # 14
(0, 255, 255): "lightcyan", # 15
}
try:
rgb_color = webcolors.html5_parse_legacy_color(color)
except ValueError:
return None
if rgb_color in first_16:
return first_16[rgb_color]
return str(colour_find_rgb(*rgb_color))
def color_weechat_to_html(color):
first_16 = {
"black": "black", # 0
"red": "maroon", # 1
"green": "green", # 2
"brown": "olive", # 3
"blue": "navy", # 4
"magenta": "purple", # 5
"cyan": "teal", # 6
"default": "silver", # 7
"gray": "grey", # 8
"lightred": "red", # 9
"lightgreen": "lime", # 11
"yellow": "yellow", # 12
"lightblue": "fuchsia", # 13
"lightmagenta": "aqua", # 14
"lightcyan": "white", # 15
}
hex_colors = {
"0": "#000000",
"1": "#800000",
"2": "#008000",
"3": "#808000",
"4": "#000080",
"5": "#800080",
"6": "#008080",
"7": "#c0c0c0",
"8": "#808080",
"9": "#ff0000",
"10": "#00ff00",
"11": "#ffff00",
"12": "#0000ff",
"13": "#ff00ff",
"14": "#00ffff",
"15": "#ffffff",
"16": "#000000",
"17": "#00005f",
"18": "#000087",
"19": "#0000af",
"20": "#0000d7",
"21": "#0000ff",
"22": "#005f00",
"23": "#005f5f",
"24": "#005f87",
"25": "#005faf",
"26": "#005fd7",
"27": "#005fff",
"28": "#008700",
"29": "#00875f",
"30": "#008787",
"31": "#0087af",
"32": "#0087d7",
"33": "#0087ff",
"34": "#00af00",
"35": "#00af5f",
"36": "#00af87",
"37": "#00afaf",
"38": "#00afd7",
"39": "#00afff",
"40": "#00d700",
"41": "#00d75f",
"42": "#00d787",
"43": "#00d7af",
"44": "#00d7d7",
"45": "#00d7ff",
"46": "#00ff00",
"47": "#00ff5f",
"48": "#00ff87",
"49": "#00ffaf",
"50": "#00ffd7",
"51": "#00ffff",
"52": "#5f0000",
"53": "#5f005f",
"54": "#5f0087",
"55": "#5f00af",
"56": "#5f00d7",
"57": "#5f00ff",
"58": "#5f5f00",
"59": "#5f5f5f",
"60": "#5f5f87",
"61": "#5f5faf",
"62": "#5f5fd7",
"63": "#5f5fff",
"64": "#5f8700",
"65": "#5f875f",
"66": "#5f8787",
"67": "#5f87af",
"68": "#5f87d7",
"69": "#5f87ff",
"70": "#5faf00",
"71": "#5faf5f",
"72": "#5faf87",
"73": "#5fafaf",
"74": "#5fafd7",
"75": "#5fafff",
"76": "#5fd700",
"77": "#5fd75f",
"78": "#5fd787",
"79": "#5fd7af",
"80": "#5fd7d7",
"81": "#5fd7ff",
"82": "#5fff00",
"83": "#5fff5f",
"84": "#5fff87",
"85": "#5fffaf",
"86": "#5fffd7",
"87": "#5fffff",
"88": "#870000",
"89": "#87005f",
"90": "#870087",
"91": "#8700af",
"92": "#8700d7",
"93": "#8700ff",
"94": "#875f00",
"95": "#875f5f",
"96": "#875f87",
"97": "#875faf",
"98": "#875fd7",
"99": "#875fff",
"100": "#878700",
"101": "#87875f",
"102": "#878787",
"103": "#8787af",
"104": "#8787d7",
"105": "#8787ff",
"106": "#87af00",
"107": "#87af5f",
"108": "#87af87",
"109": "#87afaf",
"110": "#87afd7",
"111": "#87afff",
"112": "#87d700",
"113": "#87d75f",
"114": "#87d787",
"115": "#87d7af",
"116": "#87d7d7",
"117": "#87d7ff",
"118": "#87ff00",
"119": "#87ff5f",
"120": "#87ff87",
"121": "#87ffaf",
"122": "#87ffd7",
"123": "#87ffff",
"124": "#af0000",
"125": "#af005f",
"126": "#af0087",
"127": "#af00af",
"128": "#af00d7",
"129": "#af00ff",
"130": "#af5f00",
"131": "#af5f5f",
"132": "#af5f87",
"133": "#af5faf",
"134": "#af5fd7",
"135": "#af5fff",
"136": "#af8700",
"137": "#af875f",
"138": "#af8787",
"139": "#af87af",
"140": "#af87d7",
"141": "#af87ff",
"142": "#afaf00",
"143": "#afaf5f",
"144": "#afaf87",
"145": "#afafaf",
"146": "#afafd7",
"147": "#afafff",
"148": "#afd700",
"149": "#afd75f",
"150": "#afd787",
"151": "#afd7af",
"152": "#afd7d7",
"153": "#afd7ff",
"154": "#afff00",
"155": "#afff5f",
"156": "#afff87",
"157": "#afffaf",
"158": "#afffd7",
"159": "#afffff",
"160": "#d70000",
"161": "#d7005f",
"162": "#d70087",
"163": "#d700af",
"164": "#d700d7",
"165": "#d700ff",
"166": "#d75f00",
"167": "#d75f5f",
"168": "#d75f87",
"169": "#d75faf",
"170": "#d75fd7",
"171": "#d75fff",
"172": "#d78700",
"173": "#d7875f",
"174": "#d78787",
"175": "#d787af",
"176": "#d787d7",
"177": "#d787ff",
"178": "#d7af00",
"179": "#d7af5f",
"180": "#d7af87",
"181": "#d7afaf",
"182": "#d7afd7",
"183": "#d7afff",
"184": "#d7d700",
"185": "#d7d75f",
"186": "#d7d787",
"187": "#d7d7af",
"188": "#d7d7d7",
"189": "#d7d7ff",
"190": "#d7ff00",
"191": "#d7ff5f",
"192": "#d7ff87",
"193": "#d7ffaf",
"194": "#d7ffd7",
"195": "#d7ffff",
"196": "#ff0000",
"197": "#ff005f",
"198": "#ff0087",
"199": "#ff00af",
"200": "#ff00d7",
"201": "#ff00ff",
"202": "#ff5f00",
"203": "#ff5f5f",
"204": "#ff5f87",
"205": "#ff5faf",
"206": "#ff5fd7",
"207": "#ff5fff",
"208": "#ff8700",
"209": "#ff875f",
"210": "#ff8787",
"211": "#ff87af",
"212": "#ff87d7",
"213": "#ff87ff",
"214": "#ffaf00",
"215": "#ffaf5f",
"216": "#ffaf87",
"217": "#ffafaf",
"218": "#ffafd7",
"219": "#ffafff",
"220": "#ffd700",
"221": "#ffd75f",
"222": "#ffd787",
"223": "#ffd7af",
"224": "#ffd7d7",
"225": "#ffd7ff",
"226": "#ffff00",
"227": "#ffff5f",
"228": "#ffff87",
"229": "#ffffaf",
"230": "#ffffd7",
"231": "#ffffff",
"232": "#080808",
"233": "#121212",
"234": "#1c1c1c",
"235": "#262626",
"236": "#303030",
"237": "#3a3a3a",
"238": "#444444",
"239": "#4e4e4e",
"240": "#585858",
"241": "#626262",
"242": "#6c6c6c",
"243": "#767676",
"244": "#808080",
"245": "#8a8a8a",
"246": "#949494",
"247": "#9e9e9e",
"248": "#a8a8a8",
"249": "#b2b2b2",
"250": "#bcbcbc",
"251": "#c6c6c6",
"252": "#d0d0d0",
"253": "#dadada",
"254": "#e4e4e4",
"255": "#eeeeee"
}
if color in first_16:
return first_16[color]
hex_color = hex_colors[color]
try:
return webcolors.hex_to_name(hex_color)
except ValueError:
return hex_color
# TODO reverse video
@ -745,7 +1130,7 @@ def parse_input_line(line):
color_string = color_string + line[i]
i = i + 1
attributes["fgcolor"] = line_color_to_sgr(color_string)
attributes["fgcolor"] = color_line_to_weechat(color_string)
else:
attributes["fgcolor"] = None
@ -761,7 +1146,7 @@ def parse_input_line(line):
color_string = color_string + line[i]
i = i + 1
attributes["bgcolor"] = line_color_to_sgr(color_string)
attributes["bgcolor"] = color_line_to_weechat(color_string)
else:
attributes["bgcolor"] = None
# Reset
@ -861,7 +1246,6 @@ def formatted_to_weechat(strings):
def formatted_to_html(strings):
# type: (List[FormattedString]) -> str
# TODO BG COLOR
# TODO color conversion SGR -> HTML
def add_attribute(string, name, value):
if name == "bold" and value:
return "{bold_on}{text}{bold_off}".format(
@ -890,7 +1274,9 @@ def formatted_to_html(strings):
quote_off="</blockquote>")
elif name == "fgcolor" and value:
return "{color_on}{text}{color_off}".format(
color_on="<font color={color}>".format(color=value),
color_on="<font color={color}>".format(
color=color_weechat_to_html(value)
),
text=string,
color_off="</font>")
@ -928,7 +1314,7 @@ def formatted_to_plain(strings):
class MatrixHtmlParser(HTMLParser):
# TODO colors
# TODO bg color
# TODO bullets
def __init__(self):
HTMLParser.__init__(self)
@ -957,6 +1343,20 @@ class MatrixHtmlParser(HTMLParser):
self._toggle_attribute("quote")
elif tag == "blockquote":
self._toggle_attribute("quote")
elif tag == "font":
for key, value in attrs:
if key == "color":
color = color_html_to_weechat(value)
if not color:
continue
if self.text:
self.substrings.append(
FormattedString(self.text, self.attributes.copy())
)
self.text = ""
self.attributes["fgcolor"] = color
else:
W.prnt("", "Unhandled tag {t}".format(t=tag))
@ -971,6 +1371,13 @@ class MatrixHtmlParser(HTMLParser):
self._toggle_attribute("strikethrough")
elif tag == "blockquote":
self._toggle_attribute("quote")
elif tag == "font":
if self.text:
self.substrings.append(
FormattedString(self.text, self.attributes.copy())
)
self.text = ""
self.attributes["fgcolor"] = None
else:
pass