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