Coverage for lib/lottie/utils/fancy_text.py: 0%
103 statements
« prev ^ index » next coverage.py v7.2.2, created at 2023-03-20 16:17 +0100
« prev ^ index » next coverage.py v7.2.2, created at 2023-03-20 16:17 +0100
1import re
3from .font import FontStyle
4from ..nvector import NVector
5from ..parsers.svg.importer import parse_color
6from ..objects.shapes import Group, Fill
9class FancyStyle:
10 def __init__(self, font: FontStyle, color: NVector, font_size: float, offset: NVector, scale: NVector, rotation: float):
11 self.font = font
12 self.color = color.clone()
13 self.font_size = font_size
14 self.offset = offset.clone()
15 self.scale = scale.clone()
16 self.rotation = rotation
18 def clone(self):
19 return FancyStyle(self.font, self.color, self.font_size, self.offset, self.scale, self.rotation)
21 def render(self, text, pos, start_x):
22 pos += self.offset
23 g = self.font.renderer.render(text, self.font_size, pos, True, start_x)
24 g.add_shape(Fill(self.color))
25 if self.scale.x != 1 or self.scale.y != 1 or self.rotation != 0:
26 center = g.bounding_box().center()
27 g.transform.anchor_point.value = center
28 g.transform.position.value += center
29 g.transform.scale.value = self.scale * 100
30 g.transform.rotation.value = self.rotation
31 pos -= self.offset
32 return g
35class FancyTextRenderer:
36 _regex = re.compile(r'\\([a-z0-9]+)(?:\{([^}]*)\})?')
38 def __init__(self, font: FontStyle, default_color: NVector, font_size: float):
39 self.font = font
40 self.font_size = font_size
41 self.default_color = default_color
42 self.groups = []
44 @staticmethod
45 def _int_arg(value, default=0):
46 try:
47 return float(value)
48 except (ValueError, TypeError):
49 return default
51 def render(self, text: str, pos: NVector = None):
52 if pos is None:
53 pos = NVector(0, 0)
55 line_start = pos.x
56 default_style = FancyStyle(self.font, self.default_color, self.font_size, NVector(0, 0), NVector(1, 1), 0)
57 style = default_style.clone()
58 container = Group()
59 last_pos = 0
61 for match in self._regex.finditer(text):
62 prev_text = text[last_pos:match.start()]
63 last_pos = match.end()
64 if prev_text:
65 container.insert_shape(0, style.render(prev_text, pos, line_start))
67 style = style.clone()
69 command = match.group(1)
70 arg = match.group(2)
72 if command == "color":
73 if arg:
74 style.color = parse_color(arg, self.default_color)
75 else:
76 style.color = self.default_color
77 elif command == "huge":
78 style.font_size = self.font_size * 2
79 elif command == "large":
80 style.font_size = self.font_size * 1.5
81 elif command == "normal":
82 style.font_size = self.font_size
83 elif command == "small":
84 style.font_size = self.font_size / 1.5
85 elif command == "tiny":
86 style.font_size = self.font_size / 2
87 elif command == "super":
88 style.offset.y -= self.font_size / 2
89 elif command == "sub":
90 style.offset.y += self.font_size / 2
91 elif command == "center":
92 style.offset.y = 0
93 elif command == "clear":
94 style = default_style.clone()
95 elif command == "flip":
96 style.scale.x *= -1
97 elif command == "vflip":
98 style.scale.y *= -1
99 elif command == "r":
100 pos.x = line_start
101 elif command == "n":
102 pos.x = line_start
103 pos.y += self.font.line_height
104 elif command == "hspace":
105 pos.x += self._int_arg(arg)
106 elif command == "rot":
107 style.rotation = self._int_arg(arg)
109 # Eat space after no argument command
110 if arg is None and len(text) > last_pos and text[last_pos] == ' ':
111 last_pos += 1
113 last_text = text[last_pos:]
114 if last_text:
115 container.insert_shape(0, style.render(last_text, pos, line_start))
117 if len(container.shapes) > 1:
118 container.next_x = container.shapes[-2].next_x
119 else:
120 container.next_x = pos.x
122 return container
125def render_fancy_text(text: str, font: FontStyle, default_color: NVector, font_size: float, pos: NVector = None):
126 renderer = FancyTextRenderer(font, default_color, font_size)
127 return renderer.render(text, pos)