python-lottie 0.7.1+dev23f1ddd
A framework to work with lottie files and telegram animated stickers (tgs)
Loading...
Searching...
No Matches
importer.py
Go to the documentation of this file.
1import re
2import math
3import colorsys
4from xml.etree import ElementTree
5from ... import objects
6from ...nvector import NVector
7from .svgdata import color_table, css_atrrs
8from .handler import SvgHandler, NameMode
9from ...utils.ellipse import Ellipse
10from ...utils.transform import TransformMatrix
11from ...utils.color import Color
12
13try:
14 from ...utils import font
15 has_font = True
16except ImportError:
17 has_font = False
18
19nocolor = {"none"}
20
21
23 def __init__(self, name, comp, value, percent):
24 self.name = name
25 self.comp = comp
26 self.value = value
27 self.percent = percent
28
29 def to_value(self, bbox, default=None):
30 if self.value is None:
31 return default
32
33 if not self.percent:
34 return self.value
35
36 if self.comp == "w":
37 return (bbox.x2 - bbox.x1) * self.value
38
39 if self.comp == "x":
40 return bbox.x1 + (bbox.x2 - bbox.x1) * self.value
41
42 return bbox.y1 + (bbox.y2 - bbox.y1) * self.value
43
44 def parse(self, attr, default_percent):
45 if attr is None:
46 return
47 if attr.endswith("%"):
48 self.percent = True
49 self.value = float(attr[:-1])/100
50 else:
51 self.percent = default_percent
52 self.value = float(attr)
53
54
56 def __init__(self):
57 self.colors = []
58 self.coords = []
60
61 def add_color(self, offset, color):
62 self.colors.append((offset, color[:4]))
63
64 def to_lottie(self, gradient_shape, shape, time=0):
65 """!
66 @param gradient_shape Should be a GradientFill or GradientStroke
67 @param shape ShapeElement to apply the gradient to
68 @param time Time to fetch properties from @p shape
69
70 """
71 for off, col in self.colors:
72 gradient_shape.colors.add_color(off, col)
73
74 def add_coord(self, value):
75 setattr(self, value.name, value)
76 self.coords.append(value)
77
78 def parse_attrs(self, attrib):
79 relunits = attrib.get("gradientUnits", "") != "userSpaceOnUse"
80 for c in self.coords:
81 c.parse(attrib.get(c.name, None), relunits)
82
83
85 def __init__(self):
86 super().__init__()
87 self.add_coord(SvgGradientCoord("x1", "x", 0, True))
88 self.add_coord(SvgGradientCoord("y1", "y", 0, True))
89 self.add_coord(SvgGradientCoord("x2", "x", 1, True))
90 self.add_coord(SvgGradientCoord("y2", "y", 0, True))
91
92 def to_lottie(self, gradient_shape, shape, time=0):
93 bbox = shape.bounding_box(time)
94 gradient_shape.start_point.value = self.matrix.apply(NVector(
95 self.x1.to_value(bbox),
96 self.y1.to_value(bbox),
97 ))
98 gradient_shape.end_point.value = self.matrix.apply(NVector(
99 self.x2.to_value(bbox),
100 self.y2.to_value(bbox),
101 ))
102 gradient_shape.gradient_type = objects.GradientType.Linear
103
104 super().to_lottie(gradient_shape, shape, time)
105
106
108 def __init__(self):
109 super().__init__()
110 self.add_coord(SvgGradientCoord("cx", "x", 0.5, True))
111 self.add_coord(SvgGradientCoord("cy", "y", 0.5, True))
112 self.add_coord(SvgGradientCoord("fx", "x", None, True))
113 self.add_coord(SvgGradientCoord("fy", "y", None, True))
114 self.add_coord(SvgGradientCoord("r", "w", 0.5, True))
115
116 def to_lottie(self, gradient_shape, shape, time=0):
117 bbox = shape.bounding_box(time)
118 cx = self.cx.to_value(bbox)
119 cy = self.cy.to_value(bbox)
120 gradient_shape.start_point.value = self.matrix.apply(NVector(cx, cy))
121 r = self.r.to_value(bbox)
122 gradient_shape.end_point.value = self.matrix.apply(NVector(cx+r, cy))
123
124 fx = self.fx.to_value(bbox, cx) - cx
125 fy = self.fy.to_value(bbox, cy) - cy
126 gradient_shape.highlight_angle.value = math.atan2(fy, fx) * 180 / math.pi
127 gradient_shape.highlight_length.value = math.hypot(fx, fy)
128
129 gradient_shape.gradient_type = objects.GradientType.Radial
130
131 super().to_lottie(gradient_shape, shape, time)
132
133
134def parse_color(color, current_color=Color(0, 0, 0, 1)):
135 """!
136 Parses CSS colors
137 @see https://www.w3.org/wiki/CSS/Properties/color
138 """
139 # #112233
140 if re.match(r"^#[0-9a-fA-F]{6}$", color):
141 return Color(int(color[1:3], 16) / 0xff, int(color[3:5], 16) / 0xff, int(color[5:7], 16) / 0xff, 1)
142 # #fff
143 if re.match(r"^#[0-9a-fA-F]{3}$", color):
144 return Color(int(color[1], 16) / 0xf, int(color[2], 16) / 0xf, int(color[3], 16) / 0xf, 1)
145 # #112233aa
146 if re.match(r"^#[0-9a-fA-F]{8}$", color):
147 return Color(
148 int(color[1:3], 16) / 0xff,
149 int(color[3:5], 16) / 0xff,
150 int(color[5:7], 16) / 0xff,
151 int(color[7:9], 16) / 0xff
152 )
153 # #fffa
154 if re.match(r"^#[0-9a-fA-F]{4}$", color):
155 return Color(int(color[1], 16) / 0xf, int(color[2], 16) / 0xf, int(color[3], 16) / 0xf, int(color[4], 16) / 0xf)
156 # rgba(123, 123, 123, 0.7)
157 match = re.match(r"^rgba\s*\‍(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9.eE]+)\s*\‍)$", color)
158 if match:
159 return Color(int(match[1])/255, int(match[2])/255, int(match[3])/255, float(match[4]))
160 # rgb(123, 123, 123)
161 match = re.match(r"^rgb\s*\‍(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\‍)$", color)
162 if match:
163 return Color(int(match[1])/255, int(match[2])/255, int(match[3])/255, 1)
164 # rgb(60%, 30%, 20%)
165 match = re.match(r"^rgb\s*\‍(\s*([0-9.eE]+)%\s*,\s*([0-9.eE]+)%\s*,\s*([0-9.eE]+)%\s*\‍)$", color)
166 if match:
167 return Color(float(match[1])/100, float(match[2])/100, float(match[3])/100, 1)
168 # rgba(60%, 30%, 20%, 0.7)
169 match = re.match(r"^rgba\s*\‍(\s*([0-9.eE]+)%\s*,\s*([0-9.eE]+)%\s*,\s*([0-9.eE]+)%\s*,\s*([0-9.eE]+)\s*\‍)$", color)
170 if match:
171 return Color(float(match[1])/100, float(match[2])/100, float(match[3])/100, float(match[4]))
172 # transparent
173 if color == "transparent":
174 return Color(0, 0, 0, 0)
175 # hsl(60, 30%, 20%)
176 match = re.match(r"^hsl\s*\‍(\s*([0-9.eE]+)\s*,\s*([0-9.eE]+)%\s*,\s*([0-9.eE]+)%\s*\‍)$", color)
177 if match:
178 return Color(*(colorsys.hls_to_rgb(float(match[1])/360, float(match[3])/100, float(match[2])/100) + (1,)))
179 # hsla(60, 30%, 20%, 0.7)
180 match = re.match(r"^hsla\s*\‍(\s*([0-9.eE]+)\s*,\s*([0-9.eE]+)%\s*,\s*([0-9.eE]+)%\s*,\s*([0-9.eE]+)\s*\‍)$", color)
181 if match:
182 return Color(*(colorsys.hls_to_rgb(float(match[1])/360, float(match[3])/100, float(match[2])/100) + (float(match[4]),)))
183 # currentColor
184 if color in {"currentColor", "inherit"}:
185 return current_color.clone()
186 # red
187 return Color(*color_table[color])
188
189
191 def __init__(self):
192 self.items = {}
193
194 def insert(self, dummy, shape):
195 self.items[shape.name] = shape
196
197 def __getitem__(self, key):
198 return self.items[key]
199
200 def __setitem__(self, key, value):
201 self.items[key] = value
202
203 def __contains__(self, key):
204 return key in self.items
205
206 @property
207 def shapes(self):
208 return self
209
210
212 def __init__(self, name_mode=NameMode.Inkscape):
213 self.init_etree()
214 self.name_mode = name_mode
215 self.current_color = Color(0, 0, 0, 1)
216 self.gradients = {}
217 self.max_time = 0
219 self.dpi = 96
220
221 def _get_name(self, element, inkscapequal):
222 if self.name_mode == NameMode.Inkscape:
223 return element.attrib.get(inkscapequal, element.attrib.get("id"))
224 return self._get_id(element)
225
226 def _get_id(self, element):
227 if self.name_mode != NameMode.NoName:
228 return element.attrib.get("id")
229 return None
230
231 def _parse_viewbox(self, attrib):
232 return map(float, attrib.replace(",", " ").split())
233
234 def parse_etree(self, etree, layer_frames=0, *args, **kwargs):
235 animation = objects.Animation(*args, **kwargs)
236 self.animation = animation
237 self.max_time = 0
238 self.document = etree
239
240 svg = etree.getroot()
241
242 self._get_dpi(svg)
243
244 if "width" in svg.attrib and "height" in svg.attrib:
245 animation.width = int(round(self._parse_unit(svg.attrib["width"])))
246 animation.height = int(round(self._parse_unit(svg.attrib["height"])))
247 else:
248 _, _, animation.width, animation.height = self._parse_viewbox(svg.attrib["viewBox"])
249 animation.name = self._get_name(svg, self.qualified("sodipodi", "docname"))
250
251 for defs in svg.findall(".//{*}defs") + svg.findall(".//defs"):
252 self.parse_defs(defs)
253
254 if layer_frames:
255 for frame in svg:
256 if self.unqualified(frame.tag) == "g":
257 layer = objects.ShapeLayer()
258 layer.in_point = self.max_time
259 animation.add_layer(layer)
260 self._parseshape_g(frame, layer, {})
261 self.max_time += layer_frames
262 layer.out_point = self.max_time
263 else:
264 self._svg_to_layer(animation, svg)
265
266 if self.max_time:
267 animation.out_point = self.max_time
268
269 self._fix_viewbox(svg, (layer for layer in animation.layers if not layer.parent_index))
270
271 return animation
272
273 def etree_to_layer(self, animation, etree):
274 svg = etree.getroot()
275 self._get_dpi(svg)
276 layer = self._svg_to_layer(animation, svg)
277 self._fix_viewbox(svg, [layer])
278 return layer
279
280 def _get_dpi(self, svg):
281 self.dpi = float(svg.attrib.get(self.qualified("inkscape", "export-xdpi"), self.dpi))
282
283 def _svg_to_layer(self, animation, svg):
284 self.animation = animation
285 layer = objects.ShapeLayer()
286 animation.add_layer(layer)
287 self.parse_children(svg, layer, self.parse_style(svg, {}))
288 if self.max_time:
289 for sublayer in layer.find_all(objects.Layer):
290 sublayer.out_point = self.max_time
291 return layer
292
293 def _fix_viewbox(self, svg, layers):
294 if "viewBox" in svg.attrib:
295 vbx, vby, vbw, vbh = self._parse_viewbox(svg.attrib["viewBox"])
296 if vbx != 0 or vby != 0 or vbw != self.animation.width or vbh != self.animation.height:
297 for layer in layers:
298 layer.transform.position.value = -NVector(vbx, vby)
299 layer.transform.scale.value = NVector(self.animation.width / vbw, self.animation.height / vbh) * 100
300
301 def _parse_unit(self, value):
302 if not isinstance(value, str):
303 return value
304
305 mult = 1
306 cmin = 2.54
307 if value.endswith("px"):
308 value = value[:-2]
309 elif value.endswith("vw"):
310 value = value[:-2]
311 mult = self.animation.width * 0.01
312 elif value.endswith("vh"):
313 value = value[:-2]
314 mult = self.animation.height * 0.01
315 elif value.endswith("vmin"):
316 value = value[:-4]
317 mult = min(self.animation.width, self.animation.height) * 0.01
318 elif value.endswith("vmax"):
319 value = value[:-4]
320 mult = max(self.animation.width, self.animation.height) * 0.01
321 elif value.endswith("in"):
322 value = value[:-2]
323 mult = self.dpi
324 elif value.endswith("pc"):
325 value = value[:-2]
326 mult = self.dpi / 6
327 elif value.endswith("pt"):
328 value = value[:-2]
329 mult = self.dpi / 72
330 elif value.endswith("cm"):
331 value = value[:-2]
332 mult = self.dpi / cmin
333 elif value.endswith("mm"):
334 value = value[:-2]
335 mult = self.dpi / cmin / 10
336 elif value.endswith("Q"):
337 value = value[:-1]
338 mult = self.dpi / cmin / 40
339
340 return float(value) * mult
341
342 def parse_color(self, color):
343 return parse_color(color, self.current_color)
344
345 def parse_transform(self, element, group, dest_trans):
346 bb = group.bounding_box()
347 if not bb.isnull():
348 itcx = self.qualified("inkscape", "transform-center-x")
349 if itcx in element.attrib:
350 cx = float(element.attrib[itcx])
351 cy = float(element.attrib[self.qualified("inkscape", "transform-center-y")])
352 bbx, bby = bb.center()
353 cx += bbx
354 cy = bby - cy
355 dest_trans.anchor_point.value = NVector(cx, cy)
356 dest_trans.position.value = NVector(cx, cy)
357 #else:
358 #c = bb.center()
359 #dest_trans.anchor_point.value = c
360 #dest_trans.position.value = c.clone()
361
362 if "transform" not in element.attrib:
363 return
364
365 matrix = TransformMatrix()
366 read_matrix = False
367
368 for t in re.finditer(r"([a-zA-Z]+)\s*\‍(([^\‍)]*)\‍)", element.attrib["transform"]):
369 name = t[1]
370 params = list(map(float, t[2].strip().replace(",", " ").split()))
371 if name == "translate":
372 dest_trans.position.value += NVector(
373 params[0],
374 (params[1] if len(params) > 1 else 0),
375 )
376 elif name == "scale":
377 xfac = params[0]
378 dest_trans.scale.value[0] = (dest_trans.scale.value[0] / 100 * xfac) * 100
379 yfac = params[1] if len(params) > 1 else xfac
380 dest_trans.scale.value[1] = (dest_trans.scale.value[1] / 100 * yfac) * 100
381 elif name == "rotate":
382 ang = params[0]
383 x = y = 0
384 if len(params) > 2:
385 x = params[1]
386 y = params[2]
387 ap = NVector(x, y)
388 dap = ap - dest_trans.position.value
389 dest_trans.position.value += dap
390 dest_trans.anchor_point.value += dap
391 dest_trans.rotation.value = ang
392 else:
393 read_matrix = True
395
396 if read_matrix:
397 dest_trans.position.value -= dest_trans.anchor_point.value
398 dest_trans.anchor_point.value = NVector(0, 0)
399 trans = matrix.extract_transform()
400 dest_trans.skew_axis.value = math.degrees(trans["skew_axis"])
401 dest_trans.skew.value = -math.degrees(trans["skew_angle"])
402 dest_trans.position.value += trans["translation"]
403 dest_trans.rotation.value -= math.degrees(trans["angle"])
404 dest_trans.scale.value *= trans["scale"]
405
406 def parse_style(self, element, parent_style):
407 style = parent_style.copy()
408 for att in css_atrrs & set(element.attrib.keys()):
409 if att in element.attrib:
410 style[att] = element.attrib[att]
411 if "style" in element.attrib:
412 style.update(**dict(map(
413 lambda x: map(lambda y: y.strip(), x.split(":")),
414 filter(bool, element.attrib["style"].split(";"))
415 )))
416 return style
417
418 def apply_common_style(self, style, transform):
419 opacity = float(style.get("opacity", 1))
420 transform.opacity.value = opacity * 100
421
422 def apply_visibility(self, style, object):
423 if style.get("display", "inline") == "none" or style.get("visibility", "visible") == "hidden":
424 object.hidden = True
425
426 def add_shapes(self, element, shapes, shape_parent, parent_style):
427 style = self.parse_style(element, parent_style)
428
429 group = objects.Group()
430 self.apply_common_style(style, group.transform)
431 self.apply_visibility(style, group)
432 group.name = self._get_name(element, self.qualified("inkscape", "label"))
433
434 shape_parent.shapes.insert(0, group)
435 for shape in shapes:
436 group.add_shape(shape)
437
438 self._add_style_shapes(style, group)
439
440 self.parse_transform(element, group, group.transform)
441
442 return group
443
444 def _add_style_shapes(self, style, group):
445 stroke_color = style.get("stroke", "none")
446 if stroke_color not in nocolor:
447 if stroke_color.startswith("url"):
448 stroke = self.get_color_url(stroke_color, objects.GradientStroke, group)
449 opacity = 1
450 if not stroke:
451 stroke = objects.Stroke()
452 else:
453 stroke = objects.Stroke()
454 color = self.parse_color(stroke_color)
455 stroke.color.value = color
456 opacity = color[3]
457 group.add_shape(stroke)
458
459 stroke.opacity.value = opacity * float(style.get("stroke-opacity", 1)) * 100
460
461 stroke.width.value = self._parse_unit(style.get("stroke-width", 1))
462
463 linecap = style.get("stroke-linecap")
464 if linecap == "round":
465 stroke.line_cap = objects.shapes.LineCap.Round
466 elif linecap == "butt":
467 stroke.line_cap = objects.shapes.LineCap.Butt
468 elif linecap == "square":
469 stroke.line_cap = objects.shapes.LineCap.Square
470
471 linejoin = style.get("stroke-linejoin")
472 if linejoin == "round":
473 stroke.line_join = objects.shapes.LineJoin.Round
474 elif linejoin == "bevel":
475 stroke.line_join = objects.shapes.LineJoin.Bevel
476 elif linejoin in {"miter", "arcs", "miter-clip"}:
477 stroke.line_join = objects.shapes.LineJoin.Miter
478
479 stroke.miter_limit = self._parse_unit(style.get("stroke-miterlimit", 0))
480
481 dash_array = style.get("stroke-dasharray")
482 if dash_array and dash_array != "none":
483 values = list(map(self._parse_unit, dash_array.replace(",", " ").split()))
484 if len(values) % 2:
485 values += values
486
487 stroke.dashes = []
488 for i in range(0, len(values), 2):
489 stroke.dashes.append(objects.StrokeDash(values[i], objects.StrokeDashType.Dash))
490 stroke.dashes.append(objects.StrokeDash(values[i + 1], objects.StrokeDashType.Gap))
491
492 fill_color = style.get("fill", "inherit")
493 if fill_color not in nocolor:
494 if fill_color.startswith("url"):
495 fill = self.get_color_url(fill_color, objects.GradientFill, group)
496 opacity = 1
497 if not fill:
498 fill = objects.Fill()
499 else:
500 color = self.parse_color(fill_color)
501 fill = objects.Fill(color)
502 opacity = color[3]
503 opacity *= float(style.get("fill-opacity", 1))
504 fill.opacity.value = opacity * 100
505
506 if style.get("fill-rule", "") == "evenodd":
507 fill.fill_rule = objects.FillRule.EvenOdd
508
509 group.add_shape(fill)
510
511 def _parseshape_use(self, element, shape_parent, parent_style):
512 link = element.attrib.get(self.qualified("xlink", "href"))
513 if link and link.startswith("#"):
514 id = link[1:]
515 base_element = self.document.find(".//*[@id='%s']" % id)
516 use_style = self.parse_style(element, parent_style)
517 used = objects.Group()
518 shape_parent.add_shape(used)
519 used.name = "use"
520 used.transform.position.value.x = float(element.attrib.get("x", 0))
521 used.transform.position.value.y = float(element.attrib.get("y", 0))
522 self.parse_transform(element, used, used.transform)
523 self.parse_shape(base_element, used, use_style)
524 return used
525
526 def _parseshape_g(self, element, shape_parent, parent_style):
527 group = objects.Group()
528 shape_parent.shapes.insert(0, group)
529 style = self.parse_style(element, parent_style)
530 self.apply_common_style(style, group.transform)
531 self.apply_visibility(style, group)
532 group.name = self._get_name(element, self.qualified("inkscape", "label"))
533 self.parse_children(element, group, style)
534 self.parse_transform(element, group, group.transform)
535 if group.hidden: # Lottie web doesn't seem to support .hd
536 group.transform.opacity.value = 0
537 return group
538
539 def _parseshape_ellipse(self, element, shape_parent, parent_style):
540 ellipse = objects.Ellipse()
541 ellipse.position.value = NVector(
542 self._parse_unit(element.attrib["cx"]),
543 self._parse_unit(element.attrib["cy"])
544 )
545 ellipse.size.value = NVector(
546 self._parse_unit(element.attrib["rx"]) * 2,
547 self._parse_unit(element.attrib["ry"]) * 2
548 )
549 self.add_shapes(element, [ellipse], shape_parent, parent_style)
550 return ellipse
551
552 def _parseshape_anim_ellipse(self, ellipse, element, animations):
553 self._merge_animations(element, animations, "cx", "cy", "position")
554 self._merge_animations(element, animations, "rx", "ry", "size", lambda x, y: NVector(x, y) * 2)
555 self._apply_animations(ellipse.position, "position", animations)
556 self._apply_animations(ellipse.size, "size", animations)
557
558 def _parseshape_circle(self, element, shape_parent, parent_style):
559 ellipse = objects.Ellipse()
560 ellipse.position.value = NVector(
561 self._parse_unit(element.attrib["cx"]),
562 self._parse_unit(element.attrib["cy"])
563 )
564 r = self._parse_unit(element.attrib["r"]) * 2
565 ellipse.size.value = NVector(r, r)
566 self.add_shapes(element, [ellipse], shape_parent, parent_style)
567 return ellipse
568
569 def _parseshape_anim_circle(self, ellipse, element, animations):
570 self._merge_animations(element, animations, "cx", "cy", "position")
571 self._apply_animations(ellipse.position, "position", animations)
572 self._apply_animations(ellipse.size, "r", animations, lambda r: NVector(r, r) * 2)
573
574 def _parseshape_rect(self, element, shape_parent, parent_style):
575 rect = objects.Rect()
576 w = self._parse_unit(element.attrib.get("width", 0))
577 h = self._parse_unit(element.attrib.get("height", 0))
578 rect.position.value = NVector(
579 self._parse_unit(element.attrib.get("x", 0)) + w / 2,
580 self._parse_unit(element.attrib.get("y", 0)) + h / 2
581 )
582 rect.size.value = NVector(w, h)
583 rx = self._parse_unit(element.attrib.get("rx", 0))
584 ry = self._parse_unit(element.attrib.get("ry", 0))
585 rect.rounded.value = (rx + ry) / 2
586 self.add_shapes(element, [rect], shape_parent, parent_style)
587 return rect
588
589 def _parseshape_anim_rect(self, rect, element, animations):
590 self._merge_animations(element, animations, "width", "height", "size", lambda x, y: NVector(x, y))
591 self._apply_animations(rect.size, "size", animations)
592 self._merge_animations(element, animations, "x", "y", "position")
593 self._merge_animations(element, animations, "position", "size", "position", lambda p, s: p + s / 2)
594 self._apply_animations(rect.position, "position", animations)
595 self._merge_animations(element, animations, "rx", "ry", "rounded", lambda x, y: (x + y) / 2)
596 self._apply_animations(rect.rounded, "rounded", animations)
597
598 def _parseshape_line(self, element, shape_parent, parent_style):
599 line = objects.Path()
600 line.shape.value.add_point(NVector(
601 self._parse_unit(element.attrib["x1"]),
602 self._parse_unit(element.attrib["y1"])
603 ))
604 line.shape.value.add_point(NVector(
605 self._parse_unit(element.attrib["x2"]),
606 self._parse_unit(element.attrib["y2"])
607 ))
608 return self.add_shapes(element, [line], shape_parent, parent_style)
609
610 def _parseshape_anim_line(self, group, element, animations):
611 line = group.shapes[0]
612
613 self._merge_animations(element, animations, "x1", "y1", "p1")
614 self._merge_animations(element, animations, "x2", "y2", "p2")
615
616 def combine(p1, p2):
617 shape = objects.Bezier()
618 shape.add_point(p1)
619 shape.add_point(p2)
620 return shape
621
622 self._merge_animations(element, animations, "p1", "p2", "points", combine)
623 self._apply_animations(line.shape, "points", animations)
624
625 def _handle_poly(self, element):
626 line = objects.Path()
627 coords = list(map(float, element.attrib["points"].replace(",", " ").split()))
628 for i in range(0, len(coords), 2):
629 line.shape.value.add_point(NVector(*coords[i:i+2]))
630 return line
631
632 def _parseshape_polyline(self, element, shape_parent, parent_style):
633 line = self._handle_poly(element)
634 return self.add_shapes(element, [line], shape_parent, parent_style)
635
636 def _parseshape_polygon(self, element, shape_parent, parent_style):
637 line = self._handle_poly(element)
638 line.shape.value.close()
639 return self.add_shapes(element, [line], shape_parent, parent_style)
640
641 def _parseshape_path(self, element, shape_parent, parent_style):
642 d_parser = PathDParser(element.attrib.get("d", ""))
643 d_parser.parse()
644 paths = []
645 for path in d_parser.paths:
646 p = objects.Path()
647 p.shape.value = path
648 paths.append(p)
649 #if len(d_parser.paths) > 1:
650 #paths.append(objects.shapes.Merge())
651 return self.add_shapes(element, paths, shape_parent, parent_style)
652
653 def parse_children(self, element, shape_parent, parent_style):
654 for child in element:
655 tag = self.unqualified(child.tag)
656 if not self.parse_shape(child, shape_parent, parent_style):
657 handler = getattr(self, "_parse_" + tag, None)
658 if handler:
659 handler(child)
660
661 def parse_shape(self, element, shape_parent, parent_style):
662 handler = getattr(self, "_parseshape_" + self.unqualified(element.tag), None)
663 if handler:
664 out = handler(element, shape_parent, parent_style)
665 self.parse_animations(out, element)
666 if element.attrib.get("id"):
667 self.defs.items[element.attrib["id"]] = out
668 return out
669 return None
670
671 def parse_defs(self, element):
672 self.parse_children(element, self.defs, {})
673
675 name = t[1]
676 params = list(map(float, t[2].strip().replace(",", " ").split()))
677 if name == "translate":
678 matrix.translate(
679 params[0],
680 (params[1] if len(params) > 1 else 0),
681 )
682 elif name == "scale":
683 xfac = params[0]
684 yfac = params[1] if len(params) > 1 else xfac
685 matrix.scale(xfac, yfac)
686 elif name == "rotate":
687 ang = params[0]
688 x = y = 0
689 if len(params) > 2:
690 x = params[1]
691 y = params[2]
692 matrix.translate(-x, -y)
693 matrix.rotate(math.radians(ang))
694 matrix.translate(x, y)
695 else:
696 matrix.rotate(math.radians(ang))
697 elif name == "skewX":
698 matrix.skew(math.radians(params[0]), 0)
699 elif name == "skewY":
700 matrix.skew(0, math.radians(params[0]))
701 elif name == "matrix":
702 m = TransformMatrix()
703 m.a, m.b, m.c, m.d, m.tx, m.ty = params
704 matrix *= m
705
706 def _transform_to_matrix(self, transform):
707 matrix = TransformMatrix()
708
709 for t in re.finditer(r"([a-zA-Z]+)\s*\‍(([^\‍)]*)\‍)", transform):
711
712 return matrix
713
714 def _gradient(self, element, grad):
715 grad.matrix = self._transform_to_matrix(element.attrib.get("gradientTransform", ""))
716
717 id = element.attrib["id"]
718 if id in self.gradients:
719 grad.colors = self.gradients[id].colors
720 grad.parse_attrs(element.attrib)
721 href = element.attrib.get(self.qualified("xlink", "href"))
722 if href:
723 srcid = href.strip("#")
724 if srcid in self.gradients:
725 src = self.gradients[srcid]
726 else:
727 src = grad.__class__()
728 self.gradients[srcid] = src
729 grad.colors = src.colors
730
731 for stop in element.findall("./%s" % self.qualified("svg", "stop")):
732 offset = stop.attrib.get("offset", "0")
733 off = float(offset.strip("%"))
734 if offset.endswith("%"):
735 off /= 100
736 style = self.parse_style(stop, {})
737 color = self.parse_color(style.get("stop-color", "black"))
738 if "stop-opacity" in style:
739 color[3] = float(style["stop-opacity"])
740 grad.add_color(off, color)
741 self.gradients[id] = grad
742
743 def _parse_linearGradient(self, element):
744 self._gradient(element, SvgLinearGradient())
745
746 def _parse_radialGradient(self, element):
747 self._gradient(element, SvgRadialGradient())
748
749 def get_color_url(self, color, gradientclass, shape):
750 match = re.match(r"""url\‍(['"]?#([^)'"]+)['"]?\‍)""", color)
751 if not match:
752 return None
753 id = match[1]
754 if id not in self.gradients:
755 return None
756 grad = self.gradients[id]
757 outgrad = gradientclass()
758 grad.to_lottie(outgrad, shape)
759 if self.name_mode != NameMode.NoName:
760 grad.name = id
761 return outgrad
762
763 ## @todo Parse single font property, fallback family etc
764 def _parse_text_style(self, style, font_style=None):
765 if "font-family" in style:
766 font_style.query.family(style["font-family"].strip("'\""))
767
768 if "font-style" in style:
769 if style["font-style"] == "oblique":
770 font_style.query.custom("slant", 110)
771 elif style["font-style"] == "italic":
772 font_style.query.custom("slant", 100)
773
774 if "font-weight" in style:
775 if style["font-weight"] in {"bold", "bolder"}:
776 font_style.query.weight(200)
777 elif style["font-weight"] == "lighter":
778 font_style.query.weight(50)
779 elif style["font-weight"].isdigit():
780 font_style.query.css_weight(int(style["font-weight"]))
781
782 if "font-size" in style:
783 fz = style["font-size"]
784 fz_names = {
785 "xx-small": 8,
786 "x-small": 16,
787 "small": 32,
788 "medium": 64,
789 "large": 128,
790 "x-large": 256,
791 "xx-large": 512,
792 }
793 if fz in fz_names:
794 font_style.size = fz_names[fz]
795 elif fz == "smaller":
796 font_style.size /= 2
797 elif fz == "larger":
798 font_style.size *= 2
799 elif fz.endswith("px"):
800 font_style.size = float(fz[:-2])
801 elif fz.isnumeric():
802 font_style.size = float(fz)
803
804 if "text-align" in style:
805 ta = style["text-align"]
806 if ta in ("left", "start"):
807 font_style.justify = font.TextJustify.Left
808 elif ta == "center":
809 font_style.justify = font.TextJustify.Center
810 elif ta in ("right", "end"):
811 font_style.justify = font.TextJustify.Right
812
813 def _parse_text_elem(self, element, style, group, parent_style, font_style):
814 self._parse_text_style(style, font_style)
815
816 if "x" in element.attrib or "y" in element.attrib:
817 font_style.position = NVector(
818 float(element.attrib["x"]),
819 float(element.attrib["y"]),
820 )
821
822 childpos = NVector(0, font_style.position.y)
823
824 if element.text:
825 fs = font.FontShape(element.text, font_style)
826 fs.refresh()
827 group.add_shape(fs)
828 childpos.x = fs.wrapped.next_x
829
830 for child in element:
831 if child.tag == self.qualified("svg", "tspan"):
832 child_style = font_style.clone()
833 child_style.position = childpos.clone()
834 fs = self._parseshape_text(child, group, parent_style, child_style)
835 childpos.x = fs.next_x
836 if child.tail:
837 child_style = font_style.clone()
838 child_style.position = childpos.clone()
839 fs = font.FontShape(child.tail, child_style)
840 fs.refresh()
841 group.add_shape(fs)
842 childpos.x = fs.wrapped.next_x
843
844 group.next_x = childpos.x
845
846 def _parseshape_text(self, element, shape_parent, parent_style, font_style=None):
847 group = objects.Group()
848
849 style = self.parse_style(element, parent_style)
850 self.apply_common_style(style, group.transform)
851 self.apply_visibility(style, group)
852 group.name = self._get_id(element)
853
854 if has_font:
855 if font_style is None:
856 font_style = font.FontStyle("", 64)
857 self._parse_text_elem(element, style, group, style, font_style)
858
859 style.setdefault("fill", "none")
860 self._add_style_shapes(style, group)
861
862 ## @todo text-anchor when it doesn't match text-align
863 #if element.tag == self.qualified("svg", "text"):
864 #dx = 0
865 #dy = 0
866
867 #ta = style.get("text-anchor", style.get("text-align", ""))
868 #if ta == "middle":
869 #dx -= group.bounding_box().width / 2
870 #elif ta == "end":
871 #dx -= group.bounding_box().width
872
873 #if dx or dy:
874 #ng = objects.Group()
875 #ng.add_shape(group)
876 #group.transform.position.value.x += dx
877 #group.transform.position.value.y += dy
878 #group = ng
879
880 shape_parent.shapes.insert(0, group)
881 self.parse_transform(element, group, group.transform)
882 return group
883
884 def parse_animations(self, lottie, element):
885 animations = {}
886 for child in element:
887 if self.unqualified(child.tag) == "animate":
888 att = child.attrib["attributeName"]
889
890 from_val = child.attrib["from"]
891 if att == "d":
892 ## @todo
893 continue
894 else:
895 from_val = float(from_val)
896 if "to" in child.attrib:
897 to_val = float(child.attrib["to"])
898 elif "by" in child.attrib:
899 to_val = float(child.attrib["by"]) + from_val
900
901 begin = self.parse_animation_time(child.attrib.get("begin", 0)) or 0
902 if "dur" in child.attrib:
903 end = (self.parse_animation_time(child.attrib["dur"]) or 0) + begin
904 elif "end" in child.attrib:
905 end = self.parse_animation_time(child.attrib["dur"]) or 0
906 else:
907 continue
908
909 if att not in animations:
910 animations[att] = {}
911 animations[att][begin] = from_val
912 animations[att][end] = to_val
913 if self.max_time < end:
914 self.max_time = end
915
916 tag = self.unqualified(element.tag)
917 handler = getattr(self, "_parseshape_anim_" + tag, None)
918 if handler:
919 handler(lottie, element, animations)
920
921 def parse_animation_time(self, value):
922 """!
923 @see https://developer.mozilla.org/en-US/docs/Web/SVG/Content_type#Clock-value
924 """
925 if not value:
926 return None
927 try:
928 seconds = 0
929 if ":" in value:
930 mult = 1
931 for elem in reversed(value.split(":")):
932 seconds += float(elem) * mult
933 mult *= 60
934 elif value.endswith("s"):
935 seconds = float(value[:-1])
936 elif value.endswith("ms"):
937 seconds = float(value[:-2]) / 1000
938 elif value.endswith("min"):
939 seconds = float(value[:-3]) * 60
940 elif value.endswith("h"):
941 seconds = float(value[:-1]) * 60 * 60
942 else:
943 seconds = float(value)
944 return seconds * self.animation.frame_rate
945 except ValueError:
946 pass
947 return None
948
949 def _merge_animations(self, element, animations, val1, val2, dest, merge=NVector):
950 if val1 not in animations and val2 not in animations:
951 return
952
953 dict1 = list(sorted(animations.pop(val1, {}).items()))
954 dict2 = list(sorted(animations.pop(val2, {}).items()))
955
956 x = float(element.attrib[val1])
957 y = float(element.attrib[val2])
958 values = {}
959 while dict1 or dict2:
960 if not dict1 or (dict2 and dict1[0][0] > dict2[0][0]):
961 t, y = dict2.pop(0)
962 elif not dict2 or dict1[0][0] < dict2[0][0]:
963 t, x = dict1.pop(0)
964 else:
965 t, x = dict1.pop(0)
966 t, y = dict2.pop(0)
967
968 values[t] = merge(x, y)
969
970 animations[dest] = values
971
972 def _apply_animations(self, animatable, name, animations, transform=lambda v: v):
973 if name in animations:
974 for t, v in animations[name].items():
975 animatable.add_keyframe(t, transform(v))
976
977
979 _re = re.compile("|".join((
980 r"[a-zA-Z]",
981 r"[-+]?[0-9]*\.?[0-9]*[eE][-+]?[0-9]+",
982 r"[-+]?[0-9]*\.?[0-9]+",
983 )))
984
985 def __init__(self, d_string):
987 self.paths = []
988 self.p = NVector(0, 0)
989 self.la = None
990 self.la_type = None
991 self.tokens = list(map(self.d_subsplit, self._re.findall(d_string)))
992 self.add_p = True
993 self.implicit = "M"
994
995 def d_subsplit(self, tok):
996 if tok.isalpha():
997 return tok
998 return float(tok)
999
1000 def next_token(self):
1001 if self.tokens:
1002 self.la = self.tokens.pop(0)
1003 if isinstance(self.la, str):
1004 self.la_type = 0
1005 else:
1006 self.la_type = 1
1007 else:
1008 self.la = None
1009 return self.la
1010
1011 def next_vec(self):
1012 x = self.next_token()
1013 y = self.next_token()
1014 return NVector(x, y)
1015
1016 def cur_vec(self):
1017 x = self.la
1018 y = self.next_token()
1019 return NVector(x, y)
1020
1021 def parse(self):
1022 self.next_token()
1023 while self.la is not None:
1024 if self.la_type == 0:
1025 parser = "_parse_" + self.la
1026 self.next_token()
1027 getattr(self, parser)()
1028 else:
1029 parser = "_parse_" + self.implicit
1030 getattr(self, parser)()
1031
1032 def _push_path(self):
1034 self.add_p = True
1035
1036 def _parse_M(self):
1037 if self.la_type != 1:
1038 self.next_token()
1039 return
1040 self.p = self.cur_vec()
1041 self.implicit = "L"
1042 if not self.add_p:
1043 self._push_path()
1044 self.next_token()
1045
1046 def _parse_m(self):
1047 if self.la_type != 1:
1048 self.next_token()
1049 return
1050 self.p += self.cur_vec()
1051 self.implicit = "l"
1052 if not self.add_p:
1053 self._push_path()
1054 self.next_token()
1055
1056 def _rpoint(self, point, rel=None):
1057 return (point - (rel or self.p)) if point is not None else NVector(0, 0)
1058
1059 def _do_add_p(self, outp=None):
1060 if self.add_p:
1061 self.paths.append(self.path)
1062 self.path.add_point(self.p.clone(), NVector(0, 0), self._rpoint(outp))
1063 self.add_p = False
1064 elif outp:
1065 rp = self.path.vertices[-1]
1066 self.path.out_tangents[-1] = self._rpoint(outp, rp)
1067
1068 def _parse_L(self):
1069 if self.la_type != 1:
1070 self.next_token()
1071 return
1072 self._do_add_p()
1073 self.p = self.cur_vec()
1074 self.path.add_point(self.p.clone(), NVector(0, 0), NVector(0, 0))
1075 self.implicit = "L"
1076 self.next_token()
1077
1078 def _parse_l(self):
1079 if self.la_type != 1:
1080 self.next_token()
1081 return
1082 self._do_add_p()
1083 self.p += self.cur_vec()
1084 self.path.add_point(self.p.clone(), NVector(0, 0), NVector(0, 0))
1085 self.implicit = "l"
1086 self.next_token()
1087
1088 def _parse_H(self):
1089 if self.la_type != 1:
1090 self.next_token()
1091 return
1092 self._do_add_p()
1093 self.p[0] = self.la
1094 self.path.add_point(self.p.clone(), NVector(0, 0), NVector(0, 0))
1095 self.implicit = "H"
1096 self.next_token()
1097
1098 def _parse_h(self):
1099 if self.la_type != 1:
1100 self.next_token()
1101 return
1102 self._do_add_p()
1103 self.p[0] += self.la
1104 self.path.add_point(self.p.clone(), NVector(0, 0), NVector(0, 0))
1105 self.implicit = "h"
1106 self.next_token()
1107
1108 def _parse_V(self):
1109 if self.la_type != 1:
1110 self.next_token()
1111 return
1112 self._do_add_p()
1113 self.p[1] = self.la
1114 self.path.add_point(self.p.clone(), NVector(0, 0), NVector(0, 0))
1115 self.implicit = "V"
1116 self.next_token()
1117
1118 def _parse_v(self):
1119 if self.la_type != 1:
1120 self.next_token()
1121 return
1122 self._do_add_p()
1123 self.p[1] += self.la
1124 self.path.add_point(self.p.clone(), NVector(0, 0), NVector(0, 0))
1125 self.implicit = "v"
1126 self.next_token()
1127
1128 def _parse_C(self):
1129 if self.la_type != 1:
1130 self.next_token()
1131 return
1132 pout = self.cur_vec()
1133 self._do_add_p(pout)
1134 pin = self.next_vec()
1135 self.p = self.next_vec()
1136 self.path.add_point(
1137 self.p.clone(),
1138 (pin - self.p),
1139 NVector(0, 0)
1140 )
1141 self.implicit = "C"
1142 self.next_token()
1143
1144 def _parse_c(self):
1145 if self.la_type != 1:
1146 self.next_token()
1147 return
1148 pout = self.p + self.cur_vec()
1149 self._do_add_p(pout)
1150 pin = self.p + self.next_vec()
1151 self.p += self.next_vec()
1152 self.path.add_point(
1153 self.p.clone(),
1154 (pin - self.p),
1155 NVector(0, 0)
1156 )
1157 self.implicit = "c"
1158 self.next_token()
1159
1160 def _parse_S(self):
1161 if self.la_type != 1:
1162 self.next_token()
1163 return
1164 pin = self.cur_vec()
1165 self._do_add_p()
1166 handle = self.path.in_tangents[-1]
1167 self.path.out_tangents[-1] = (-handle)
1168 self.p = self.next_vec()
1169 self.path.add_point(
1170 self.p.clone(),
1171 (pin - self.p),
1172 NVector(0, 0)
1173 )
1174 self.implicit = "S"
1175 self.next_token()
1176
1177 def _parse_s(self):
1178 if self.la_type != 1:
1179 self.next_token()
1180 return
1181 pin = self.cur_vec() + self.p
1182 self._do_add_p()
1183 handle = self.path.in_tangents[-1]
1184 self.path.out_tangents[-1] = (-handle)
1185 self.p += self.next_vec()
1186 self.path.add_point(
1187 self.p.clone(),
1188 (pin - self.p),
1189 NVector(0, 0)
1190 )
1191 self.implicit = "s"
1192 self.next_token()
1193
1194 def _parse_Q(self):
1195 if self.la_type != 1:
1196 self.next_token()
1197 return
1198 self._do_add_p()
1199 pin = self.cur_vec()
1200 self.p = self.next_vec()
1201 self.path.add_point(
1202 self.p.clone(),
1203 (pin - self.p),
1204 NVector(0, 0)
1205 )
1206 self.implicit = "Q"
1207 self.next_token()
1208
1209 def _parse_q(self):
1210 if self.la_type != 1:
1211 self.next_token()
1212 return
1213 self._do_add_p()
1214 pin = self.p + self.cur_vec()
1215 self.p += self.next_vec()
1216 self.path.add_point(
1217 self.p.clone(),
1218 (pin - self.p),
1219 NVector(0, 0)
1220 )
1221 self.implicit = "q"
1222 self.next_token()
1223
1224 def _parse_T(self):
1225 if self.la_type != 1:
1226 self.next_token()
1227 return
1228 self._do_add_p()
1229 handle = self.p - self.path.in_tangents[-1]
1230 self.p = self.cur_vec()
1231 self.path.add_point(
1232 self.p.clone(),
1233 (handle - self.p),
1234 NVector(0, 0)
1235 )
1236 self.implicit = "T"
1237 self.next_token()
1238
1239 def _parse_t(self):
1240 if self.la_type != 1:
1241 self.next_token()
1242 return
1243 self._do_add_p()
1244 handle = -self.path.in_tangents[-1] + self.p
1245 self.p += self.cur_vec()
1246 self.path.add_point(
1247 self.p.clone(),
1248 (handle - self.p),
1249 NVector(0, 0)
1250 )
1251 self.implicit = "t"
1252 self.next_token()
1253
1254 def _parse_A(self):
1255 if self.la_type != 1:
1256 self.next_token()
1257 return
1258 r = self.cur_vec()
1259 xrot = self.next_token()
1260 large = self.next_token()
1261 sweep = self.next_token()
1262 dest = self.next_vec()
1263 self._do_arc(r[0], r[1], xrot, large, sweep, dest)
1264 self.implicit = "A"
1265 self.next_token()
1266
1267 def _do_arc(self, rx, ry, xrot, large, sweep, dest):
1268 self._do_add_p()
1269 if self.p == dest:
1270 return
1271
1272 if rx == 0 or ry == 0:
1273 # Straight line
1274 self.p = dest
1275 self.path.add_point(
1276 self.p.clone(),
1277 NVector(0, 0),
1278 NVector(0, 0)
1279 )
1280 return
1281
1282 ellipse, theta1, deltatheta = Ellipse.from_svg_arc(self.p, rx, ry, xrot, large, sweep, dest)
1283 points = ellipse.to_bezier_points(theta1, deltatheta)
1284
1285 self._do_add_p()
1286 self.path.out_tangents[-1] = points[0].out_tangent
1287 for point in points[1:-1]:
1288 self.path.add_point(
1289 point.vertex,
1290 point.in_tangent,
1291 point.out_tangent,
1292 )
1293 self.path.add_point(
1294 dest.clone(),
1295 points[-1].in_tangent,
1296 NVector(0, 0),
1297 )
1298 self.p = dest
1299
1300 def _parse_a(self):
1301 if self.la_type != 1:
1302 self.next_token()
1303 return
1304 r = self.cur_vec()
1305 xrot = self.next_token()
1306 large = self.next_token()
1307 sweep = self.next_token()
1308 dest = self.p + self.next_vec()
1309 self._do_arc(r[0], r[1], xrot, large, sweep, dest)
1310 self.implicit = "a"
1311 self.next_token()
1312
1313 def _parse_Z(self):
1314 if self.path.vertices:
1315 self.p = self.path.vertices[0].clone()
1316 self.path.close()
1317 self._push_path()
1318
1319 def _parse_z(self):
1320 self._parse_Z()
1321
1322
1323def parse_svg_etree(etree, layer_frames=0, *args, **kwargs):
1324 parser = SvgParser()
1325 return parser.parse_etree(etree, layer_frames, *args, **kwargs)
1326
1327
1328def parse_svg_file(file, layer_frames=0, *args, **kwargs):
1329 return parse_svg_etree(ElementTree.parse(file), layer_frames, *args, **kwargs)
Top level object, describing the animation.
Definition animation.py:97
Single bezier curve.
Definition bezier.py:123
Base class for all layers.
Definition layers.py:23
Layer containing ShapeElement objects.
Definition layers.py:227
Solid fill color.
Definition shapes.py:511
ShapeElement that can contain other shapes.
Definition shapes.py:435
Animatable Bezier curve.
Definition shapes.py:401
A simple rectangle shape.
Definition shapes.py:157
_do_arc(self, rx, ry, xrot, large, sweep, dest)
Definition importer.py:1267
parse(self, attr, default_percent)
Definition importer.py:44
to_value(self, bbox, default=None)
Definition importer.py:29
__init__(self, name, comp, value, percent)
Definition importer.py:23
to_lottie(self, gradient_shape, shape, time=0)
Definition importer.py:64
to_lottie(self, gradient_shape, shape, time=0)
Definition importer.py:92
_parseshape_circle(self, element, shape_parent, parent_style)
Definition importer.py:558
_parseshape_path(self, element, shape_parent, parent_style)
Definition importer.py:641
_parseshape_anim_rect(self, rect, element, animations)
Definition importer.py:589
_parseshape_anim_circle(self, ellipse, element, animations)
Definition importer.py:569
parse_children(self, element, shape_parent, parent_style)
Definition importer.py:653
_parseshape_text(self, element, shape_parent, parent_style, font_style=None)
Definition importer.py:846
etree_to_layer(self, animation, etree)
Definition importer.py:273
_parseshape_ellipse(self, element, shape_parent, parent_style)
Definition importer.py:539
_parseshape_polyline(self, element, shape_parent, parent_style)
Definition importer.py:632
parse_transform(self, element, group, dest_trans)
Definition importer.py:345
_parseshape_anim_ellipse(self, ellipse, element, animations)
Definition importer.py:552
_parseshape_line(self, element, shape_parent, parent_style)
Definition importer.py:598
_add_style_shapes(self, style, group)
Definition importer.py:444
_apply_transform_element_to_matrix(self, matrix, t)
Definition importer.py:674
_parseshape_anim_line(self, group, element, animations)
Definition importer.py:610
_svg_to_layer(self, animation, svg)
Definition importer.py:283
parse_shape(self, element, shape_parent, parent_style)
Definition importer.py:661
get_color_url(self, color, gradientclass, shape)
Definition importer.py:749
_parse_text_style(self, style, font_style=None)
Definition importer.py:764
parse_etree(self, etree, layer_frames=0, *args, **kwargs)
Definition importer.py:234
add_shapes(self, element, shapes, shape_parent, parent_style)
Definition importer.py:426
_parseshape_polygon(self, element, shape_parent, parent_style)
Definition importer.py:636
_merge_animations(self, element, animations, val1, val2, dest, merge=NVector)
Definition importer.py:949
apply_visibility(self, style, object)
Definition importer.py:422
apply_common_style(self, style, transform)
Definition importer.py:418
_apply_animations(self, animatable, name, animations, transform=lambda v:v)
Definition importer.py:972
parse_style(self, element, parent_style)
Definition importer.py:406
_parseshape_g(self, element, shape_parent, parent_style)
Definition importer.py:526
_parse_text_elem(self, element, style, group, parent_style, font_style)
Definition importer.py:813
_get_name(self, element, inkscapequal)
Definition importer.py:221
_parseshape_use(self, element, shape_parent, parent_style)
Definition importer.py:511
__init__(self, name_mode=NameMode.Inkscape)
Definition importer.py:212
_parseshape_rect(self, element, shape_parent, parent_style)
Definition importer.py:574
parse_animations(self, lottie, element)
Definition importer.py:884
to_lottie(self, gradient_shape, shape, time=0)
Definition importer.py:116