|
|
|
@ -1,5 +1,5 @@
|
|
|
|
|
#!/usr/bin/python
|
|
|
|
|
#vim: encoding=utf-8
|
|
|
|
|
# vim: encoding=utf-8
|
|
|
|
|
# pylint: disable=wrong-import-position,wrong-import-order,redefined-builtin
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
@ -39,17 +39,17 @@ CHAR_WIDTH = 8
|
|
|
|
|
CHAR_HEIGHT = 14
|
|
|
|
|
FONT_SIZE = 13
|
|
|
|
|
FONT_CAT = {
|
|
|
|
|
'default': "/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf",
|
|
|
|
|
'Cyrillic': "/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf",
|
|
|
|
|
'Greek': "/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf",
|
|
|
|
|
'Arabic': "/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf",
|
|
|
|
|
'Hebrew': "/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf",
|
|
|
|
|
'Han': "/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc",
|
|
|
|
|
'Hiragana': "/usr/share/fonts/truetype/motoya-l-cedar/MTLc3m.ttf",
|
|
|
|
|
'Katakana': "/usr/share/fonts/truetype/motoya-l-cedar/MTLc3m.ttf",
|
|
|
|
|
'Hangul': "/usr/share/fonts/truetype/lexi/LexiGulim.ttf",
|
|
|
|
|
'Braille': "/usr/share/fonts/truetype/ancient-scripts/Symbola_hint.ttf",
|
|
|
|
|
'Emoji': "/usr/share/fonts/truetype/ancient-scripts/Symbola_hint.ttf",
|
|
|
|
|
"default": "/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf",
|
|
|
|
|
"Cyrillic": "/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf",
|
|
|
|
|
"Greek": "/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf",
|
|
|
|
|
"Arabic": "/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf",
|
|
|
|
|
"Hebrew": "/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf",
|
|
|
|
|
"Han": "/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc",
|
|
|
|
|
"Hiragana": "/usr/share/fonts/truetype/motoya-l-cedar/MTLc3m.ttf",
|
|
|
|
|
"Katakana": "/usr/share/fonts/truetype/motoya-l-cedar/MTLc3m.ttf",
|
|
|
|
|
"Hangul": "/usr/share/fonts/truetype/lexi/LexiGulim.ttf",
|
|
|
|
|
"Braille": "/usr/share/fonts/truetype/ancient-scripts/Symbola_hint.ttf",
|
|
|
|
|
"Emoji": "/usr/share/fonts/truetype/ancient-scripts/Symbola_hint.ttf",
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#
|
|
|
|
@ -66,6 +66,7 @@ FONT_CAT = {
|
|
|
|
|
# * fonts-symbola (Braille/Emoji)
|
|
|
|
|
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def render_ansi(text, options=None):
|
|
|
|
|
"""Render `text` (terminal sequence) in a PNG file
|
|
|
|
|
paying attention to passed command line `options`.
|
|
|
|
@ -85,24 +86,22 @@ def render_ansi(text, options=None):
|
|
|
|
|
|
|
|
|
|
return _gen_term(buf, graphemes, options=options)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _color_mapping(color, inverse=False):
|
|
|
|
|
"""Convert pyte color to PIL color
|
|
|
|
|
|
|
|
|
|
Return: tuple of color values (R,G,B)
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
if color == 'default':
|
|
|
|
|
if color == "default":
|
|
|
|
|
if inverse:
|
|
|
|
|
return 'black'
|
|
|
|
|
return 'lightgray'
|
|
|
|
|
return "black"
|
|
|
|
|
return "lightgray"
|
|
|
|
|
|
|
|
|
|
if color in ['green', 'black', 'cyan', 'blue', 'brown']:
|
|
|
|
|
if color in ["green", "black", "cyan", "blue", "brown"]:
|
|
|
|
|
return color
|
|
|
|
|
try:
|
|
|
|
|
return (
|
|
|
|
|
int(color[0:2], 16),
|
|
|
|
|
int(color[2:4], 16),
|
|
|
|
|
int(color[4:6], 16))
|
|
|
|
|
return (int(color[0:2], 16), int(color[2:4], 16), int(color[4:6], 16))
|
|
|
|
|
except (ValueError, IndexError):
|
|
|
|
|
# if we do not know this color and it can not be decoded as RGB,
|
|
|
|
|
# print it and return it as it is (will be displayed as black)
|
|
|
|
@ -110,6 +109,7 @@ def _color_mapping(color, inverse=False):
|
|
|
|
|
return color
|
|
|
|
|
return color
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _strip_buf(buf):
|
|
|
|
|
"""Strips empty spaces from behind and from the right side.
|
|
|
|
|
(from the right side is not yet implemented)
|
|
|
|
@ -117,13 +117,13 @@ def _strip_buf(buf):
|
|
|
|
|
|
|
|
|
|
def empty_line(line):
|
|
|
|
|
"Returns True if the line consists from spaces"
|
|
|
|
|
return all(x.data == ' ' for x in line)
|
|
|
|
|
return all(x.data == " " for x in line)
|
|
|
|
|
|
|
|
|
|
def line_len(line):
|
|
|
|
|
"Returns len of the line excluding spaces from the right"
|
|
|
|
|
|
|
|
|
|
last_pos = len(line)
|
|
|
|
|
while last_pos > 0 and line[last_pos-1].data == ' ':
|
|
|
|
|
while last_pos > 0 and line[last_pos - 1].data == " ":
|
|
|
|
|
last_pos -= 1
|
|
|
|
|
return last_pos
|
|
|
|
|
|
|
|
|
@ -141,6 +141,7 @@ def _strip_buf(buf):
|
|
|
|
|
|
|
|
|
|
return buf
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _script_category(char):
|
|
|
|
|
"""Returns category of a Unicode character
|
|
|
|
|
|
|
|
|
@ -152,12 +153,13 @@ def _script_category(char):
|
|
|
|
|
return "Emoji"
|
|
|
|
|
|
|
|
|
|
cat = unicodedata2.script_cat(char)[0]
|
|
|
|
|
if char == u':':
|
|
|
|
|
return 'Han'
|
|
|
|
|
if cat in ['Latin', 'Common']:
|
|
|
|
|
return 'default'
|
|
|
|
|
if char == ":":
|
|
|
|
|
return "Han"
|
|
|
|
|
if cat in ["Latin", "Common"]:
|
|
|
|
|
return "default"
|
|
|
|
|
return cat
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _load_emojilib():
|
|
|
|
|
"""Load known emojis from a directory, and return dictionary
|
|
|
|
|
of PIL Image objects correspodent to the loaded emojis.
|
|
|
|
@ -167,10 +169,10 @@ def _load_emojilib():
|
|
|
|
|
emojilib = {}
|
|
|
|
|
for filename in glob.glob("share/emoji/*.png"):
|
|
|
|
|
character = os.path.basename(filename)[:-3]
|
|
|
|
|
emojilib[character] = \
|
|
|
|
|
Image.open(filename).resize((CHAR_HEIGHT, CHAR_HEIGHT))
|
|
|
|
|
emojilib[character] = Image.open(filename).resize((CHAR_HEIGHT, CHAR_HEIGHT))
|
|
|
|
|
return emojilib
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# pylint: disable=too-many-locals,too-many-branches,too-many-statements
|
|
|
|
|
def _gen_term(buf, graphemes, options=None):
|
|
|
|
|
"""Renders rendered pyte buffer `buf` and list of workaround `graphemes`
|
|
|
|
@ -190,7 +192,7 @@ def _gen_term(buf, graphemes, options=None):
|
|
|
|
|
if "background" in options:
|
|
|
|
|
bg_color = _color_mapping(options["background"], options.get("inverted_colors"))
|
|
|
|
|
|
|
|
|
|
image = Image.new('RGB', (cols * CHAR_WIDTH, rows * CHAR_HEIGHT), color=bg_color)
|
|
|
|
|
image = Image.new("RGB", (cols * CHAR_WIDTH, rows * CHAR_HEIGHT), color=bg_color)
|
|
|
|
|
|
|
|
|
|
buf = buf[-ROWS:]
|
|
|
|
|
|
|
|
|
@ -207,11 +209,11 @@ def _gen_term(buf, graphemes, options=None):
|
|
|
|
|
x_pos = 0
|
|
|
|
|
for char in line:
|
|
|
|
|
current_color = _color_mapping(char.fg, options.get("inverted_colors"))
|
|
|
|
|
if char.bg != 'default':
|
|
|
|
|
if char.bg != "default":
|
|
|
|
|
draw.rectangle(
|
|
|
|
|
((x_pos, y_pos),
|
|
|
|
|
(x_pos+CHAR_WIDTH, y_pos+CHAR_HEIGHT)),
|
|
|
|
|
fill=_color_mapping(char.bg, options.get("inverted_colors")))
|
|
|
|
|
((x_pos, y_pos), (x_pos + CHAR_WIDTH, y_pos + CHAR_HEIGHT)),
|
|
|
|
|
fill=_color_mapping(char.bg, options.get("inverted_colors")),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if char.data == "!":
|
|
|
|
|
try:
|
|
|
|
@ -226,20 +228,21 @@ def _gen_term(buf, graphemes, options=None):
|
|
|
|
|
cat = _script_category(data[0])
|
|
|
|
|
if cat not in font:
|
|
|
|
|
globals.log("Unknown font category: %s" % cat)
|
|
|
|
|
if cat == 'Emoji' and emojilib.get(data):
|
|
|
|
|
if cat == "Emoji" and emojilib.get(data):
|
|
|
|
|
image.paste(emojilib.get(data), (x_pos, y_pos))
|
|
|
|
|
else:
|
|
|
|
|
draw.text(
|
|
|
|
|
(x_pos, y_pos),
|
|
|
|
|
data,
|
|
|
|
|
font=font.get(cat, font.get('default')),
|
|
|
|
|
fill=current_color)
|
|
|
|
|
font=font.get(cat, font.get("default")),
|
|
|
|
|
fill=current_color,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
x_pos += CHAR_WIDTH * constants.WEATHER_SYMBOL_WIDTH_VTE.get(data, 1)
|
|
|
|
|
y_pos += CHAR_HEIGHT
|
|
|
|
|
|
|
|
|
|
if 'transparency' in options:
|
|
|
|
|
transparency = options.get('transparency', '255')
|
|
|
|
|
if "transparency" in options:
|
|
|
|
|
transparency = options.get("transparency", "255")
|
|
|
|
|
try:
|
|
|
|
|
transparency = int(transparency)
|
|
|
|
|
except ValueError:
|
|
|
|
@ -265,6 +268,7 @@ def _gen_term(buf, graphemes, options=None):
|
|
|
|
|
image.save(img_bytes, format="png")
|
|
|
|
|
return img_bytes.getvalue()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _fix_graphemes(text):
|
|
|
|
|
"""
|
|
|
|
|
Extract long graphemes sequences that can't be handled
|
|
|
|
|