Skip to content
This repository has been archived by the owner on Jun 16, 2020. It is now read-only.

Add underscore and strikethrough text rendering support #62

Merged
merged 6 commits into from
Aug 17, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 21 additions & 4 deletions termtosvg/anim.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,19 @@ class TemplateError(Exception):
pass


_CharacterCell = namedtuple('_CharacterCell', ['text', 'color', 'background_color', 'bold', 'italics'])
_CharacterCell = namedtuple('_CharacterCell', ['text', 'color',
'background_color', 'bold',
'italics', 'underscore',
'strikethrough'])
# Make Last four arguments of _CharacterCell constructor default to False (bold, italics,
# underscore and strikethrough)
_CharacterCell.__new__.__defaults__ = (False,) * 4
_CharacterCell.__doc__ = 'Representation of a character cell'
_CharacterCell.text.__doc__ = 'Text content of the cell'
_CharacterCell.bold.__doc__ = 'Bold modificator flag'
_CharacterCell.italics.__doc__ = 'Italics modificator flag'
_CharacterCell.underscore.__doc__ = 'Underscore modificator flag'
_CharacterCell.strikethrough.__doc__ = 'Strikethrough modificator flag'
_CharacterCell.color.__doc__ = 'Color of the text'
_CharacterCell.background_color.__doc__ = 'Background color of the cell'

Expand Down Expand Up @@ -80,7 +88,9 @@ def from_pyte(cls, char):
if char.reverse:
text_color, background_color = background_color, text_color

return CharacterCell(char.data, text_color, background_color, char.bold, char.italics)
return CharacterCell(char.data, text_color, background_color,
char.bold, char.italics, char.underscore,
char.strikethrough)


CharacterCellConfig = namedtuple('CharacterCellConfig', ['width', 'height'])
Expand Down Expand Up @@ -162,6 +172,13 @@ def make_text_tag(column, attributes, text, cell_width):
if attributes['italics']:
text_tag_attributes['font-style'] = 'italic'

decoration = ''
if attributes['underscore']:
decoration = 'underline'
if attributes['strikethrough']:
decoration += ' line-through'
text_tag_attributes['text-decoration'] = decoration

if attributes['color'].startswith('#'):
text_tag_attributes['fill'] = attributes['color']
else:
Expand All @@ -178,14 +195,14 @@ def _render_characters(screen_line, cell_width):
# type: (Dict[int, CharacterCell], int) -> List[etree.ElementBase]
"""Return a list of 'text' elements representing the line of the screen

Consecutive characters with the same styling attributes (text color and font weight) are
Consecutive characters with the same styling attributes (text color, font weight...) are
grouped together in a single text element.

:param screen_line: Mapping between column numbers and characters
:param cell_width: Width of a character cell in pixels
"""
line = sorted(screen_line.items())
key = ConsecutiveWithSameAttributes(['color', 'bold', 'italics'])
key = ConsecutiveWithSameAttributes(['color', 'bold', 'italics', 'underscore', 'strikethrough'])
text_tags = [make_text_tag(column, attributes, ''.join(c.text for _, c in group), cell_width)
for (column, attributes), group in groupby(line, key)]

Expand Down
100 changes: 58 additions & 42 deletions tests/test_anim.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,23 @@ def test_from_pyte(self):
pyte.screens.Char('G', 'brightgreen', 'ABCDEF', bold=True),
# Italics
pyte.screens.Char('H', 'red', 'blue', italics=True),
# Underscore
pyte.screens.Char('I', 'red', 'blue', underscore=True),
# Strikethrough
pyte.screens.Char('J', 'red', 'blue', strikethrough=True),
]

char_cells = [
anim.CharacterCell('A', 'color1', 'color4', False, False),
anim.CharacterCell('B', 'color4', 'color1', False, False),
anim.CharacterCell('C', 'color9', 'color4', True, False),
anim.CharacterCell('D', 'color4', 'color9', True, False),
anim.CharacterCell('E', 'foreground', 'background', False, False),
anim.CharacterCell('F', '#008700', '#ABCDEF', False, False),
anim.CharacterCell('G', 'color10', '#ABCDEF', True, False),
anim.CharacterCell('H', 'color1', 'color4', False, True),
anim.CharacterCell('A', 'color1', 'color4'),
anim.CharacterCell('B', 'color4', 'color1'),
anim.CharacterCell('C', 'color9', 'color4', bold=True),
anim.CharacterCell('D', 'color4', 'color9', bold=True),
anim.CharacterCell('E', 'foreground', 'background'),
anim.CharacterCell('F', '#008700', '#ABCDEF'),
anim.CharacterCell('G', 'color10', '#ABCDEF', bold=True),
anim.CharacterCell('H', 'color1', 'color4', italics=True),
anim.CharacterCell('I', 'color1', 'color4', underscore=True),
anim.CharacterCell('J', 'color1', 'color4', strikethrough=True),
]

for pyte_char, cell_char in zip(pyte_chars, char_cells):
Expand All @@ -49,16 +55,16 @@ def test_from_pyte(self):
def test__render_line_bg_colors_xml(self):
cell_width = 8
screen_line = {
0: anim.CharacterCell('A', 'black', 'red', False, False),
1: anim.CharacterCell('A', 'black', 'red', False, False),
3: anim.CharacterCell('A', 'black', 'red', False, False),
4: anim.CharacterCell('A', 'black', 'blue', False, False),
6: anim.CharacterCell('A', 'black', 'blue', False, False),
7: anim.CharacterCell('A', 'black', 'blue', False, False),
8: anim.CharacterCell('A', 'black', 'green', False, False),
9: anim.CharacterCell('A', 'black', 'red', False, False),
10: anim.CharacterCell('A', 'black', 'red', False, False),
11: anim.CharacterCell('A', 'black', '#123456', False, False),
0: anim.CharacterCell('A', 'black', 'red'),
1: anim.CharacterCell('A', 'black', 'red'),
3: anim.CharacterCell('A', 'black', 'red'),
4: anim.CharacterCell('A', 'black', 'blue'),
6: anim.CharacterCell('A', 'black', 'blue'),
7: anim.CharacterCell('A', 'black', 'blue'),
8: anim.CharacterCell('A', 'black', 'green'),
9: anim.CharacterCell('A', 'black', 'red'),
10: anim.CharacterCell('A', 'black', 'red'),
11: anim.CharacterCell('A', 'black', '#123456'),
}

rectangles = anim._render_line_bg_colors(screen_line=screen_line,
Expand Down Expand Up @@ -88,32 +94,36 @@ def key(r):

def test__render_characters(self):
screen_line = {
0: anim.CharacterCell('A', 'red', 'white', False, False),
1: anim.CharacterCell('B', 'blue', 'white', False, False),
2: anim.CharacterCell('C', 'blue', 'white', False, False),
7: anim.CharacterCell('D', '#00FF00', 'white', False, False),
8: anim.CharacterCell('E', '#00FF00', 'white', False, False),
9: anim.CharacterCell('F', '#00FF00', 'white', False, False),
10: anim.CharacterCell('G', '#00FF00', 'white', False, False),
11: anim.CharacterCell('H', 'red', 'white', False, False),
20: anim.CharacterCell(' ', 'black', 'black', False, False)
0: anim.CharacterCell('A', 'red', 'white'),
1: anim.CharacterCell('B', 'blue', 'white'),
2: anim.CharacterCell('C', 'blue', 'white'),
7: anim.CharacterCell('D', '#00FF00', 'white'),
8: anim.CharacterCell('E', '#00FF00', 'white'),
9: anim.CharacterCell('F', '#00FF00', 'white'),
10: anim.CharacterCell('G', '#00FF00', 'white'),
20: anim.CharacterCell('H', 'black', 'black', bold=True),
30: anim.CharacterCell('I', 'black', 'black', italics=True),
40: anim.CharacterCell('J', 'black', 'black', underscore=True),
50: anim.CharacterCell('K', 'black', 'black', strikethrough=True),
60: anim.CharacterCell('L', 'black', 'black', underscore=True, strikethrough=True),
}

with self.subTest(case='Content'):
cell_width = 8
texts = anim._render_characters(screen_line, cell_width)

sorted_texts = sorted(texts, key=lambda x: x.text)
[text_a, text_bc, text_defg, text_h, text_space] = sorted_texts
self.assertEqual(text_a.text, 'A')
self.assertEqual(text_a.attrib['class'], 'red')
self.assertEqual(text_a.attrib['x'], '0')
self.assertEqual(text_bc.text, 'BC')
self.assertEqual(text_bc.attrib['class'], 'blue')
self.assertEqual(text_bc.attrib['x'], '8')
self.assertEqual(text_defg.text, 'DEFG')
self.assertEqual(text_defg.attrib['fill'], '#00FF00')
self.assertEqual(text_defg.attrib['x'], '56')
texts = {t.text: t for t in anim._render_characters(screen_line, cell_width)}

self.assertEqual(texts['A'].attrib['class'], 'red')
self.assertEqual(texts['A'].attrib['x'], '0')
self.assertEqual(texts['BC'].attrib['class'], 'blue')
self.assertEqual(texts['BC'].attrib['x'], '8')
self.assertEqual(texts['DEFG'].attrib['fill'], '#00FF00')
self.assertEqual(texts['DEFG'].attrib['x'], '56')
self.assertEqual(texts['H'].attrib['font-weight'], 'bold')
self.assertEqual(texts['I'].attrib['font-style'], 'italic')
self.assertIn('underline', texts['J'].attrib['text-decoration'].split())
self.assertIn('line-through', texts['K'].attrib['text-decoration'].split())
self.assertIn('underline', texts['L'].attrib['text-decoration'].split())
self.assertIn('line-through', texts['L'].attrib['text-decoration'].split())

def test_ConsecutiveWithSameAttributes(self):
testClass = namedtuple('testClass', ['field1', 'field2'])
Expand Down Expand Up @@ -144,7 +154,10 @@ def test_ConsecutiveWithSameAttributes(self):

def test_make_animated_group(self):
def line(i):
chars = [anim.CharacterCell(c, '#123456', '#789012', False, False) for c in 'line{}'.format(i)]
chars = []
for c in 'line{}'.format(i):
chars.append(anim.CharacterCell(c, '#123456', '#789012',
False, False, False, False))
return dict(enumerate(chars))

records = [
Expand All @@ -165,7 +178,10 @@ def line(i):

def test__render_animation(self):
def line(i):
chars = [anim.CharacterCell(c, '#123456', '#789012', False, False) for c in 'line{}'.format(i)]
chars = []
for c in 'line{}'.format(i):
chars.append(anim.CharacterCell(c, '#123456', '#789012',
False, False, False, False))
return dict(enumerate(chars))

records = [
Expand Down
2 changes: 2 additions & 0 deletions tests/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
'echo -e "\\033[1;41mbright red bg\\033[0m"\r\n',
'echo -e "\\033[1mbold\\033[0m"\r\n',
'echo -e "\\033[3mitalics\\033[0m"\r\n',
'echo -e "\\033[4munderscore\\033[0m"\r\n',
'echo -e "\\033[9mstrikethrough\\033[0m"\r\n',
'exit;\r\n'
]

Expand Down