diff --git a/termtosvg/anim.py b/termtosvg/anim.py index 244738d..fb001f0 100644 --- a/termtosvg/anim.py +++ b/termtosvg/anim.py @@ -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' @@ -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']) @@ -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: @@ -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)] diff --git a/tests/test_anim.py b/tests/test_anim.py index 5b5f083..b7dad08 100644 --- a/tests/test_anim.py +++ b/tests/test_anim.py @@ -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): @@ -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, @@ -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']) @@ -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 = [ @@ -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 = [ diff --git a/tests/test_main.py b/tests/test_main.py index b993007..8aa3f78 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -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' ]