python-lottie  0.7.0+devd020110
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.namename = name
25  self.compcomp = comp
26  self.valuevalue = value
27  self.percentpercent = percent
28 
29  def to_value(self, bbox, default=None):
30  if self.valuevalue is None:
31  return default
32 
33  if not self.percentpercent:
34  return self.valuevalue
35 
36  if self.compcomp == "w":
37  return (bbox.x2 - bbox.x1) * self.valuevalue
38 
39  if self.compcomp == "x":
40  return bbox.x1 + (bbox.x2 - bbox.x1) * self.valuevalue
41 
42  return bbox.y1 + (bbox.y2 - bbox.y1) * self.valuevalue
43 
44  def parse(self, attr, default_percent):
45  if attr is None:
46  return
47  if attr.endswith("%"):
48  self.percentpercent = True
49  self.valuevalue = float(attr[:-1])/100
50  else:
51  self.percentpercent = default_percent
52  self.valuevalue = float(attr)
53 
54 
56  def __init__(self):
57  self.colorscolors = []
58  self.coordscoords = []
59  self.matrixmatrix = TransformMatrix()
60 
61  def add_color(self, offset, color):
62  self.colorscolors.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.colorscolors:
72  gradient_shape.colors.add_color(off, col)
73 
74  def add_coord(self, value):
75  setattr(self, value.name, value)
76  self.coordscoords.append(value)
77 
78  def parse_attrs(self, attrib):
79  relunits = attrib.get("gradientUnits", "") != "userSpaceOnUse"
80  for c in self.coordscoords:
81  c.parse(attrib.get(c.name, None), relunits)
82 
83 
85  def __init__(self):
86  super().__init__()
87  self.add_coordadd_coord(SvgGradientCoord("x1", "x", 0, True))
88  self.add_coordadd_coord(SvgGradientCoord("y1", "y", 0, True))
89  self.add_coordadd_coord(SvgGradientCoord("x2", "x", 1, True))
90  self.add_coordadd_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.matrixmatrix.apply(NVector(
95  self.x1.to_value(bbox),
96  self.y1.to_value(bbox),
97  ))
98  gradient_shape.end_point.value = self.matrixmatrix.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_coordadd_coord(SvgGradientCoord("cx", "x", 0.5, True))
111  self.add_coordadd_coord(SvgGradientCoord("cy", "y", 0.5, True))
112  self.add_coordadd_coord(SvgGradientCoord("fx", "x", None, True))
113  self.add_coordadd_coord(SvgGradientCoord("fy", "y", None, True))
114  self.add_coordadd_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.matrixmatrix.apply(NVector(cx, cy))
121  r = self.r.to_value(bbox)
122  gradient_shape.end_point.value = self.matrixmatrix.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.itemsitems = {}
183 
184  def insert(self, dummy, shape):
185  self.itemsitems[shape.name] = shape
186 
187  def __getitem__(self, key):
188  return self.itemsitems[key]
189 
190  def __setitem__(self, key, value):
191  self.itemsitems[key] = value
192 
193  def __contains__(self, key):
194  return key in self.itemsitems
195 
196  @property
197  def shapes(self):
198  return self
199 
200 
202  def __init__(self, name_mode=NameMode.Inkscape):
203  self.init_etreeinit_etree()
204  self.name_modename_mode = name_mode
205  self.current_colorcurrent_color = Color(0, 0, 0, 1)
206  self.gradientsgradients = {}
207  self.max_timemax_time = 0
208  self.defsdefs = SvgDefsParent()
209  self.dpidpi = 96
210 
211  def _get_name(self, element, inkscapequal):
212  if self.name_modename_mode == NameMode.Inkscape:
213  return element.attrib.get(inkscapequal, element.attrib.get("id"))
214  return self._get_id_get_id(element)
215 
216  def _get_id(self, element):
217  if self.name_modename_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.animationanimation = animation
227  self.max_timemax_time = 0
228  self.documentdocument = etree
229 
230  svg = etree.getroot()
231 
232  self._get_dpi_get_dpi(svg)
233 
234  if "width" in svg.attrib and "height" in svg.attrib:
235  animation.width = int(round(self._parse_unit_parse_unit(svg.attrib["width"])))
236  animation.height = int(round(self._parse_unit_parse_unit(svg.attrib["height"])))
237  else:
238  _, _, animation.width, animation.height = self._parse_viewbox_parse_viewbox(svg.attrib["viewBox"])
239  animation.name = self._get_name_get_name(svg, self.qualifiedqualified("sodipodi", "docname"))
240 
241  if layer_frames:
242  for frame in svg:
243  if self.unqualifiedunqualified(frame.tag) == "g":
244  layer = objects.ShapeLayer()
245  layer.in_point = self.max_timemax_time
246  animation.add_layer(layer)
247  self._parseshape_g_parseshape_g(frame, layer, {})
248  self.max_timemax_time += layer_frames
249  layer.out_point = self.max_timemax_time
250  else:
251  self._svg_to_layer_svg_to_layer(animation, svg)
252 
253  if self.max_timemax_time:
254  animation.out_point = self.max_timemax_time
255 
256  self._fix_viewbox_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_get_dpi(svg)
263  layer = self._svg_to_layer_svg_to_layer(animation, svg)
264  self._fix_viewbox_fix_viewbox(svg, [layer])
265  return layer
266 
267  def _get_dpi(self, svg):
268  self.dpidpi = float(svg.attrib.get(self.qualifiedqualified("inkscape", "export-xdpi"), self.dpidpi))
269 
270  def _svg_to_layer(self, animation, svg):
271  self.animationanimation = animation
272  layer = objects.ShapeLayer()
273  animation.add_layer(layer)
274  self.parse_childrenparse_children(svg, layer, self.parse_styleparse_style(svg, {}))
275  if self.max_timemax_time:
276  for sublayer in layer.find_all(objects.Layer):
277  sublayer.out_point = self.max_timemax_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_parse_viewbox(svg.attrib["viewBox"])
283  if vbx != 0 or vby != 0 or vbw != self.animationanimation.width or vbh != self.animationanimation.height:
284  for layer in layers:
285  layer.transform.position.value = -NVector(vbx, vby)
286  layer.transform.scale.value = NVector(self.animationanimation.width / vbw, self.animationanimation.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.animationanimation.width * 0.01
299  elif value.endswith("vh"):
300  value = value[:-2]
301  mult = self.animationanimation.height * 0.01
302  elif value.endswith("vmin"):
303  value = value[:-4]
304  mult = min(self.animationanimation.width, self.animationanimation.height) * 0.01
305  elif value.endswith("vmax"):
306  value = value[:-4]
307  mult = max(self.animationanimation.width, self.animationanimation.height) * 0.01
308  elif value.endswith("in"):
309  value = value[:-2]
310  mult = self.dpidpi
311  elif value.endswith("pc"):
312  value = value[:-2]
313  mult = self.dpidpi / 6
314  elif value.endswith("pt"):
315  value = value[:-2]
316  mult = self.dpidpi / 72
317  elif value.endswith("cm"):
318  value = value[:-2]
319  mult = self.dpidpi / cmin
320  elif value.endswith("mm"):
321  value = value[:-2]
322  mult = self.dpidpi / cmin / 10
323  elif value.endswith("Q"):
324  value = value[:-1]
325  mult = self.dpidpi / cmin / 40
326 
327  return float(value) * mult
328 
329  def parse_color(self, color):
330  return parse_color(color, self.current_colorcurrent_color)
331 
332  def parse_transform(self, element, group, dest_trans):
333  bb = group.bounding_box()
334  if not bb.isnull():
335  itcx = self.qualifiedqualified("inkscape", "transform-center-x")
336  if itcx in element.attrib:
337  cx = float(element.attrib[itcx])
338  cy = float(element.attrib[self.qualifiedqualified("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_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_styleparse_style(element, parent_style)
415 
416  group = objects.Group()
417  self.apply_common_styleapply_common_style(style, group.transform)
418  self.apply_visibilityapply_visibility(style, group)
419  group.name = self._get_name_get_name(element, self.qualifiedqualified("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_add_style_shapes(style, group)
426 
427  self.parse_transformparse_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_urlget_color_url(stroke_color, objects.GradientStroke, group)
436  opacity = 1
437  else:
438  stroke = objects.Stroke()
439  color = self.parse_colorparse_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_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_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_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_urlget_color_url(fill_color, objects.GradientFill, group)
481  opacity = 1
482  else:
483  color = self.parse_colorparse_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.qualifiedqualified("xlink", "href")]
496  if link.startswith("#"):
497  id = link[1:]
498  base_element = self.documentdocument.find(".//*[@id='%s']" % id)
499  use_style = self.parse_styleparse_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_transformparse_transform(element, used, used.transform)
506  self.parse_shapeparse_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_styleparse_style(element, parent_style)
513  self.apply_common_styleapply_common_style(style, group.transform)
514  self.apply_visibilityapply_visibility(style, group)
515  group.name = self._get_name_get_name(element, self.qualifiedqualified("inkscape", "label"))
516  self.parse_childrenparse_children(element, group, style)
517  self.parse_transformparse_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_parse_unit(element.attrib["cx"]),
526  self._parse_unit_parse_unit(element.attrib["cy"])
527  )
528  ellipse.size.value = NVector(
529  self._parse_unit_parse_unit(element.attrib["rx"]) * 2,
530  self._parse_unit_parse_unit(element.attrib["ry"]) * 2
531  )
532  self.add_shapesadd_shapes(element, [ellipse], shape_parent, parent_style)
533  return ellipse
534 
535  def _parseshape_anim_ellipse(self, ellipse, element, animations):
536  self._merge_animations_merge_animations(element, animations, "cx", "cy", "position")
537  self._merge_animations_merge_animations(element, animations, "rx", "ry", "size", lambda x, y: NVector(x, y) * 2)
538  self._apply_animations_apply_animations(ellipse.position, "position", animations)
539  self._apply_animations_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_parse_unit(element.attrib["cx"]),
545  self._parse_unit_parse_unit(element.attrib["cy"])
546  )
547  r = self._parse_unit_parse_unit(element.attrib["r"]) * 2
548  ellipse.size.value = NVector(r, r)
549  self.add_shapesadd_shapes(element, [ellipse], shape_parent, parent_style)
550  return ellipse
551 
552  def _parseshape_anim_circle(self, ellipse, element, animations):
553  self._merge_animations_merge_animations(element, animations, "cx", "cy", "position")
554  self._apply_animations_apply_animations(ellipse.position, "position", animations)
555  self._apply_animations_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_parse_unit(element.attrib.get("width", 0))
560  h = self._parse_unit_parse_unit(element.attrib.get("height", 0))
561  rect.position.value = NVector(
562  self._parse_unit_parse_unit(element.attrib.get("x", 0)) + w / 2,
563  self._parse_unit_parse_unit(element.attrib.get("y", 0)) + h / 2
564  )
565  rect.size.value = NVector(w, h)
566  rx = self._parse_unit_parse_unit(element.attrib.get("rx", 0))
567  ry = self._parse_unit_parse_unit(element.attrib.get("ry", 0))
568  rect.rounded.value = (rx + ry) / 2
569  self.add_shapesadd_shapes(element, [rect], shape_parent, parent_style)
570  return rect
571 
572  def _parseshape_anim_rect(self, rect, element, animations):
573  self._merge_animations_merge_animations(element, animations, "width", "height", "size", lambda x, y: NVector(x, y))
574  self._apply_animations_apply_animations(rect.size, "size", animations)
575  self._merge_animations_merge_animations(element, animations, "x", "y", "position")
576  self._merge_animations_merge_animations(element, animations, "position", "size", "position", lambda p, s: p + s / 2)
577  self._apply_animations_apply_animations(rect.position, "position", animations)
578  self._merge_animations_merge_animations(element, animations, "rx", "ry", "rounded", lambda x, y: (x + y) / 2)
579  self._apply_animations_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_parse_unit(element.attrib["x1"]),
585  self._parse_unit_parse_unit(element.attrib["y1"])
586  ))
587  line.shape.value.add_point(NVector(
588  self._parse_unit_parse_unit(element.attrib["x2"]),
589  self._parse_unit_parse_unit(element.attrib["y2"])
590  ))
591  return self.add_shapesadd_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_merge_animations(element, animations, "x1", "y1", "p1")
597  self._merge_animations_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_merge_animations(element, animations, "p1", "p2", "points", combine)
606  self._apply_animations_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_handle_poly(element)
617  return self.add_shapesadd_shapes(element, [line], shape_parent, parent_style)
618 
619  def _parseshape_polygon(self, element, shape_parent, parent_style):
620  line = self._handle_poly_handle_poly(element)
621  line.shape.value.close()
622  return self.add_shapesadd_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_shapesadd_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.unqualifiedunqualified(child.tag)
639  if not self.parse_shapeparse_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.unqualifiedunqualified(element.tag), None)
646  if handler:
647  out = handler(element, shape_parent, parent_style)
648  self.parse_animationsparse_animations(out, element)
649  if element.attrib.get("id"):
650  self.defsdefs.items[element.attrib["id"]] = out
651  return out
652  return None
653 
654  def _parse_defs(self, element):
655  self.parse_childrenparse_children(element, self.defsdefs, {})
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_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_transform_to_matrix(element.attrib.get("gradientTransform", ""))
699 
700  id = element.attrib["id"]
701  if id in self.gradientsgradients:
702  grad.colors = self.gradientsgradients[id].colors
703  grad.parse_attrs(element.attrib)
704  href = element.attrib.get(self.qualifiedqualified("xlink", "href"))
705  if href:
706  srcid = href.strip("#")
707  if srcid in self.gradientsgradients:
708  src = self.gradientsgradients[srcid]
709  else:
710  src = grad.__class__()
711  self.gradientsgradients[srcid] = src
712  grad.colors = src.colors
713 
714  for stop in element.findall("./%s" % self.qualifiedqualified("svg", "stop")):
715  offset = stop.attrib.get("offset", "0")
716  off = float(offset.strip("%"))
717  if offset.endswith("%"):
718  off /= 100
719  style = self.parse_styleparse_style(stop, {})
720  color = self.parse_colorparse_color(style.get("stop-color", "black"))
721  if "stop-opacity" in style:
722  color[3] = float(style["stop-opacity"])
723  grad.add_color(off, color)
724  self.gradientsgradients[id] = grad
725 
726  def _parse_linearGradient(self, element):
727  self._gradient_gradient(element, SvgLinearGradient())
728 
729  def _parse_radialGradient(self, element):
730  self._gradient_gradient(element, SvgRadialGradient())
731 
732  def get_color_url(self, color, gradientclass, shape):
733  match = re.match(r"""url\‍(['"]?#([^)'"]+)['"]?\‍)""", color)
734  if not match:
735  return None
736  id = match[1]
737  if id not in self.gradientsgradients:
738  return None
739  grad = self.gradientsgradients[id]
740  outgrad = gradientclass()
741  grad.to_lottie(outgrad, shape)
742  if self.name_modename_mode != NameMode.NoName:
743  grad.name = id
744  return outgrad
745 
746  ## @todo Parse single font property, fallback family etc
747  def _parse_text_style(self, style, font_style=None):
748  if "font-family" in style:
749  font_style.query.family(style["font-family"].strip("'\""))
750 
751  if "font-style" in style:
752  if style["font-style"] == "oblique":
753  font_style.query.custom("slant", 110)
754  elif style["font-style"] == "italic":
755  font_style.query.custom("slant", 100)
756 
757  if "font-weight" in style:
758  if style["font-weight"] in {"bold", "bolder"}:
759  font_style.query.weight(200)
760  elif style["font-weight"] == "lighter":
761  font_style.query.weight(50)
762  elif style["font-weight"].isdigit():
763  font_style.query.css_weight(int(style["font-weight"]))
764 
765  if "font-size" in style:
766  fz = style["font-size"]
767  fz_names = {
768  "xx-small": 8,
769  "x-small": 16,
770  "small": 32,
771  "medium": 64,
772  "large": 128,
773  "x-large": 256,
774  "xx-large": 512,
775  }
776  if fz in fz_names:
777  font_style.size = fz_names[fz]
778  elif fz == "smaller":
779  font_style.size /= 2
780  elif fz == "larger":
781  font_style.size *= 2
782  elif fz.endswith("px"):
783  font_style.size = float(fz[:-2])
784  elif fz.isnumeric():
785  font_style.size = float(fz)
786 
787  if "text-align" in style:
788  ta = style["text-align"]
789  if ta in ("left", "start"):
790  font_style.justify = font.TextJustify.Left
791  elif ta == "center":
792  font_style.justify = font.TextJustify.Center
793  elif ta in ("right", "end"):
794  font_style.justify = font.TextJustify.Right
795 
796  def _parse_text_elem(self, element, style, group, parent_style, font_style):
797  self._parse_text_style_parse_text_style(style, font_style)
798 
799  if "x" in element.attrib or "y" in element.attrib:
800  font_style.position = NVector(
801  float(element.attrib["x"]),
802  float(element.attrib["y"]),
803  )
804 
805  childpos = NVector(0, font_style.position.y)
806 
807  if element.text:
808  fs = font.FontShape(element.text, font_style)
809  fs.refresh()
810  group.add_shape(fs)
811  childpos.x = fs.wrapped.next_x
812 
813  for child in element:
814  if child.tag == self.qualifiedqualified("svg", "tspan"):
815  child_style = font_style.clone()
816  child_style.position = childpos.clone()
817  fs = self._parseshape_text_parseshape_text(child, group, parent_style, child_style)
818  childpos.x = fs.next_x
819  if child.tail:
820  child_style = font_style.clone()
821  child_style.position = childpos.clone()
822  fs = font.FontShape(child.tail, child_style)
823  fs.refresh()
824  group.add_shape(fs)
825  childpos.x = fs.wrapped.next_x
826 
827  group.next_x = childpos.x
828 
829  def _parseshape_text(self, element, shape_parent, parent_style, font_style=None):
830  group = objects.Group()
831 
832  style = self.parse_styleparse_style(element, parent_style)
833  self.apply_common_styleapply_common_style(style, group.transform)
834  self.apply_visibilityapply_visibility(style, group)
835  group.name = self._get_id_get_id(element)
836 
837  if has_font:
838  if font_style is None:
839  font_style = font.FontStyle("", 64)
840  self._parse_text_elem_parse_text_elem(element, style, group, style, font_style)
841 
842  style.setdefault("fill", "none")
843  self._add_style_shapes_add_style_shapes(style, group)
844 
845  ## @todo text-anchor when it doesn't match text-align
846  #if element.tag == self.qualified("svg", "text"):
847  #dx = 0
848  #dy = 0
849 
850  #ta = style.get("text-anchor", style.get("text-align", ""))
851  #if ta == "middle":
852  #dx -= group.bounding_box().width / 2
853  #elif ta == "end":
854  #dx -= group.bounding_box().width
855 
856  #if dx or dy:
857  #ng = objects.Group()
858  #ng.add_shape(group)
859  #group.transform.position.value.x += dx
860  #group.transform.position.value.y += dy
861  #group = ng
862 
863  shape_parent.shapes.insert(0, group)
864  self.parse_transformparse_transform(element, group, group.transform)
865  return group
866 
867  def parse_animations(self, lottie, element):
868  animations = {}
869  for child in element:
870  if self.unqualifiedunqualified(child.tag) == "animate":
871  att = child.attrib["attributeName"]
872 
873  from_val = child.attrib["from"]
874  if att == "d":
875  ## @todo
876  continue
877  else:
878  from_val = float(from_val)
879  if "to" in child.attrib:
880  to_val = float(child.attrib["to"])
881  elif "by" in child.attrib:
882  to_val = float(child.attrib["by"]) + from_val
883 
884  begin = self.parse_animation_timeparse_animation_time(child.attrib.get("begin", 0)) or 0
885  if "dur" in child.attrib:
886  end = (self.parse_animation_timeparse_animation_time(child.attrib["dur"]) or 0) + begin
887  elif "end" in child.attrib:
888  end = self.parse_animation_timeparse_animation_time(child.attrib["dur"]) or 0
889  else:
890  continue
891 
892  if att not in animations:
893  animations[att] = {}
894  animations[att][begin] = from_val
895  animations[att][end] = to_val
896  if self.max_timemax_time < end:
897  self.max_timemax_time = end
898 
899  tag = self.unqualifiedunqualified(element.tag)
900  handler = getattr(self, "_parseshape_anim_" + tag, None)
901  if handler:
902  handler(lottie, element, animations)
903 
904  def parse_animation_time(self, value):
905  """!
906  @see https://developer.mozilla.org/en-US/docs/Web/SVG/Content_type#Clock-value
907  """
908  if not value:
909  return None
910  try:
911  seconds = 0
912  if ":" in value:
913  mult = 1
914  for elem in reversed(value.split(":")):
915  seconds += float(elem) * mult
916  mult *= 60
917  elif value.endswith("s"):
918  seconds = float(value[:-1])
919  elif value.endswith("ms"):
920  seconds = float(value[:-2]) / 1000
921  elif value.endswith("min"):
922  seconds = float(value[:-3]) * 60
923  elif value.endswith("h"):
924  seconds = float(value[:-1]) * 60 * 60
925  else:
926  seconds = float(value)
927  return seconds * self.animationanimation.frame_rate
928  except ValueError:
929  pass
930  return None
931 
932  def _merge_animations(self, element, animations, val1, val2, dest, merge=NVector):
933  if val1 not in animations and val2 not in animations:
934  return
935 
936  dict1 = list(sorted(animations.pop(val1, {}).items()))
937  dict2 = list(sorted(animations.pop(val2, {}).items()))
938 
939  x = float(element.attrib[val1])
940  y = float(element.attrib[val2])
941  values = {}
942  while dict1 or dict2:
943  if not dict1 or (dict2 and dict1[0][0] > dict2[0][0]):
944  t, y = dict2.pop(0)
945  elif not dict2 or dict1[0][0] < dict2[0][0]:
946  t, x = dict1.pop(0)
947  else:
948  t, x = dict1.pop(0)
949  t, y = dict2.pop(0)
950 
951  values[t] = merge(x, y)
952 
953  animations[dest] = values
954 
955  def _apply_animations(self, animatable, name, animations, transform=lambda v: v):
956  if name in animations:
957  for t, v in animations[name].items():
958  animatable.add_keyframe(t, transform(v))
959 
960 
962  _re = re.compile("|".join((
963  r"[a-zA-Z]",
964  r"[-+]?[0-9]*\.?[0-9]*[eE][-+]?[0-9]+",
965  r"[-+]?[0-9]*\.?[0-9]+",
966  )))
967 
968  def __init__(self, d_string):
970  self.pathspaths = []
971  self.pp = NVector(0, 0)
972  self.lala = None
973  self.la_typela_type = None
974  self.tokenstokens = list(map(self.d_subsplitd_subsplit, self._re_re.findall(d_string)))
975  self.add_padd_p = True
976  self.implicitimplicit = "M"
977 
978  def d_subsplit(self, tok):
979  if tok.isalpha():
980  return tok
981  return float(tok)
982 
983  def next_token(self):
984  if self.tokenstokens:
985  self.lala = self.tokenstokens.pop(0)
986  if isinstance(self.lala, str):
987  self.la_typela_type = 0
988  else:
989  self.la_typela_type = 1
990  else:
991  self.lala = None
992  return self.lala
993 
994  def next_vec(self):
995  x = self.next_tokennext_token()
996  y = self.next_tokennext_token()
997  return NVector(x, y)
998 
999  def cur_vec(self):
1000  x = self.lala
1001  y = self.next_tokennext_token()
1002  return NVector(x, y)
1003 
1004  def parse(self):
1005  self.next_tokennext_token()
1006  while self.lala is not None:
1007  if self.la_typela_type == 0:
1008  parser = "_parse_" + self.lala
1009  self.next_tokennext_token()
1010  getattr(self, parser)()
1011  else:
1012  parser = "_parse_" + self.implicitimplicit
1013  getattr(self, parser)()
1014 
1015  def _push_path(self):
1016  self.pathpath = objects.properties.Bezier()
1017  self.add_padd_p = True
1018 
1019  def _parse_M(self):
1020  if self.la_typela_type != 1:
1021  self.next_tokennext_token()
1022  return
1023  self.pp = self.cur_veccur_vec()
1024  self.implicitimplicit = "L"
1025  if not self.add_padd_p:
1026  self._push_path_push_path()
1027  self.next_tokennext_token()
1028 
1029  def _parse_m(self):
1030  if self.la_typela_type != 1:
1031  self.next_tokennext_token()
1032  return
1033  self.pp += self.cur_veccur_vec()
1034  self.implicitimplicit = "l"
1035  if not self.add_padd_p:
1036  self._push_path_push_path()
1037  self.next_tokennext_token()
1038 
1039  def _rpoint(self, point, rel=None):
1040  return (point - (rel or self.pp)) if point is not None else NVector(0, 0)
1041 
1042  def _do_add_p(self, outp=None):
1043  if self.add_padd_p:
1044  self.pathspaths.append(self.pathpath)
1045  self.pathpath.add_point(self.pp.clone(), NVector(0, 0), self._rpoint_rpoint(outp))
1046  self.add_padd_p = False
1047  elif outp:
1048  rp = self.pathpath.vertices[-1]
1049  self.pathpath.out_tangents[-1] = self._rpoint_rpoint(outp, rp)
1050 
1051  def _parse_L(self):
1052  if self.la_typela_type != 1:
1053  self.next_tokennext_token()
1054  return
1055  self._do_add_p_do_add_p()
1056  self.pp = self.cur_veccur_vec()
1057  self.pathpath.add_point(self.pp.clone(), NVector(0, 0), NVector(0, 0))
1058  self.implicitimplicit = "L"
1059  self.next_tokennext_token()
1060 
1061  def _parse_l(self):
1062  if self.la_typela_type != 1:
1063  self.next_tokennext_token()
1064  return
1065  self._do_add_p_do_add_p()
1066  self.pp += self.cur_veccur_vec()
1067  self.pathpath.add_point(self.pp.clone(), NVector(0, 0), NVector(0, 0))
1068  self.implicitimplicit = "l"
1069  self.next_tokennext_token()
1070 
1071  def _parse_H(self):
1072  if self.la_typela_type != 1:
1073  self.next_tokennext_token()
1074  return
1075  self._do_add_p_do_add_p()
1076  self.pp[0] = self.lala
1077  self.pathpath.add_point(self.pp.clone(), NVector(0, 0), NVector(0, 0))
1078  self.implicitimplicit = "H"
1079  self.next_tokennext_token()
1080 
1081  def _parse_h(self):
1082  if self.la_typela_type != 1:
1083  self.next_tokennext_token()
1084  return
1085  self._do_add_p_do_add_p()
1086  self.pp[0] += self.lala
1087  self.pathpath.add_point(self.pp.clone(), NVector(0, 0), NVector(0, 0))
1088  self.implicitimplicit = "h"
1089  self.next_tokennext_token()
1090 
1091  def _parse_V(self):
1092  if self.la_typela_type != 1:
1093  self.next_tokennext_token()
1094  return
1095  self._do_add_p_do_add_p()
1096  self.pp[1] = self.lala
1097  self.pathpath.add_point(self.pp.clone(), NVector(0, 0), NVector(0, 0))
1098  self.implicitimplicit = "V"
1099  self.next_tokennext_token()
1100 
1101  def _parse_v(self):
1102  if self.la_typela_type != 1:
1103  self.next_tokennext_token()
1104  return
1105  self._do_add_p_do_add_p()
1106  self.pp[1] += self.lala
1107  self.pathpath.add_point(self.pp.clone(), NVector(0, 0), NVector(0, 0))
1108  self.implicitimplicit = "v"
1109  self.next_tokennext_token()
1110 
1111  def _parse_C(self):
1112  if self.la_typela_type != 1:
1113  self.next_tokennext_token()
1114  return
1115  pout = self.cur_veccur_vec()
1116  self._do_add_p_do_add_p(pout)
1117  pin = self.next_vecnext_vec()
1118  self.pp = self.next_vecnext_vec()
1119  self.pathpath.add_point(
1120  self.pp.clone(),
1121  (pin - self.pp),
1122  NVector(0, 0)
1123  )
1124  self.implicitimplicit = "C"
1125  self.next_tokennext_token()
1126 
1127  def _parse_c(self):
1128  if self.la_typela_type != 1:
1129  self.next_tokennext_token()
1130  return
1131  pout = self.pp + self.cur_veccur_vec()
1132  self._do_add_p_do_add_p(pout)
1133  pin = self.pp + self.next_vecnext_vec()
1134  self.pp += self.next_vecnext_vec()
1135  self.pathpath.add_point(
1136  self.pp.clone(),
1137  (pin - self.pp),
1138  NVector(0, 0)
1139  )
1140  self.implicitimplicit = "c"
1141  self.next_tokennext_token()
1142 
1143  def _parse_S(self):
1144  if self.la_typela_type != 1:
1145  self.next_tokennext_token()
1146  return
1147  pin = self.cur_veccur_vec()
1148  self._do_add_p_do_add_p()
1149  handle = self.pathpath.in_tangents[-1]
1150  self.pathpath.out_tangents[-1] = (-handle)
1151  self.pp = self.next_vecnext_vec()
1152  self.pathpath.add_point(
1153  self.pp.clone(),
1154  (pin - self.pp),
1155  NVector(0, 0)
1156  )
1157  self.implicitimplicit = "S"
1158  self.next_tokennext_token()
1159 
1160  def _parse_s(self):
1161  if self.la_typela_type != 1:
1162  self.next_tokennext_token()
1163  return
1164  pin = self.cur_veccur_vec() + self.pp
1165  self._do_add_p_do_add_p()
1166  handle = self.pathpath.in_tangents[-1]
1167  self.pathpath.out_tangents[-1] = (-handle)
1168  self.pp += self.next_vecnext_vec()
1169  self.pathpath.add_point(
1170  self.pp.clone(),
1171  (pin - self.pp),
1172  NVector(0, 0)
1173  )
1174  self.implicitimplicit = "s"
1175  self.next_tokennext_token()
1176 
1177  def _parse_Q(self):
1178  if self.la_typela_type != 1:
1179  self.next_tokennext_token()
1180  return
1181  self._do_add_p_do_add_p()
1182  pin = self.cur_veccur_vec()
1183  self.pp = self.next_vecnext_vec()
1184  self.pathpath.add_point(
1185  self.pp.clone(),
1186  (pin - self.pp),
1187  NVector(0, 0)
1188  )
1189  self.implicitimplicit = "Q"
1190  self.next_tokennext_token()
1191 
1192  def _parse_q(self):
1193  if self.la_typela_type != 1:
1194  self.next_tokennext_token()
1195  return
1196  self._do_add_p_do_add_p()
1197  pin = self.pp + self.cur_veccur_vec()
1198  self.pp += self.next_vecnext_vec()
1199  self.pathpath.add_point(
1200  self.pp.clone(),
1201  (pin - self.pp),
1202  NVector(0, 0)
1203  )
1204  self.implicitimplicit = "q"
1205  self.next_tokennext_token()
1206 
1207  def _parse_T(self):
1208  if self.la_typela_type != 1:
1209  self.next_tokennext_token()
1210  return
1211  self._do_add_p_do_add_p()
1212  handle = self.pp - self.pathpath.in_tangents[-1]
1213  self.pp = self.cur_veccur_vec()
1214  self.pathpath.add_point(
1215  self.pp.clone(),
1216  (handle - self.pp),
1217  NVector(0, 0)
1218  )
1219  self.implicitimplicit = "T"
1220  self.next_tokennext_token()
1221 
1222  def _parse_t(self):
1223  if self.la_typela_type != 1:
1224  self.next_tokennext_token()
1225  return
1226  self._do_add_p_do_add_p()
1227  handle = -self.pathpath.in_tangents[-1] + self.pp
1228  self.pp += self.cur_veccur_vec()
1229  self.pathpath.add_point(
1230  self.pp.clone(),
1231  (handle - self.pp),
1232  NVector(0, 0)
1233  )
1234  self.implicitimplicit = "t"
1235  self.next_tokennext_token()
1236 
1237  def _parse_A(self):
1238  if self.la_typela_type != 1:
1239  self.next_tokennext_token()
1240  return
1241  r = self.cur_veccur_vec()
1242  xrot = self.next_tokennext_token()
1243  large = self.next_tokennext_token()
1244  sweep = self.next_tokennext_token()
1245  dest = self.next_vecnext_vec()
1246  self._do_arc_do_arc(r[0], r[1], xrot, large, sweep, dest)
1247  self.implicitimplicit = "A"
1248  self.next_tokennext_token()
1249 
1250  def _do_arc(self, rx, ry, xrot, large, sweep, dest):
1251  self._do_add_p_do_add_p()
1252  if self.pp == dest:
1253  return
1254 
1255  if rx == 0 or ry == 0:
1256  # Straight line
1257  self.pp = dest
1258  self.pathpath.add_point(
1259  self.pp.clone(),
1260  NVector(0, 0),
1261  NVector(0, 0)
1262  )
1263  return
1264 
1265  ellipse, theta1, deltatheta = Ellipse.from_svg_arc(self.pp, rx, ry, xrot, large, sweep, dest)
1266  points = ellipse.to_bezier_points(theta1, deltatheta)
1267 
1268  self._do_add_p_do_add_p()
1269  self.pathpath.out_tangents[-1] = points[0].out_tangent
1270  for point in points[1:-1]:
1271  self.pathpath.add_point(
1272  point.vertex,
1273  point.in_tangent,
1274  point.out_tangent,
1275  )
1276  self.pathpath.add_point(
1277  dest.clone(),
1278  points[-1].in_tangent,
1279  NVector(0, 0),
1280  )
1281  self.pp = dest
1282 
1283  def _parse_a(self):
1284  if self.la_typela_type != 1:
1285  self.next_tokennext_token()
1286  return
1287  r = self.cur_veccur_vec()
1288  xrot = self.next_tokennext_token()
1289  large = self.next_tokennext_token()
1290  sweep = self.next_tokennext_token()
1291  dest = self.pp + self.next_vecnext_vec()
1292  self._do_arc_do_arc(r[0], r[1], xrot, large, sweep, dest)
1293  self.implicitimplicit = "a"
1294  self.next_tokennext_token()
1295 
1296  def _parse_Z(self):
1297  if self.pathpath.vertices:
1298  self.pp = self.pathpath.vertices[0].clone()
1299  self.pathpath.close()
1300  self._push_path_push_path()
1301 
1302  def _parse_z(self):
1303  self._parse_Z_parse_Z()
1304 
1305 
1306 def parse_svg_etree(etree, layer_frames=0, *args, **kwargs):
1307  parser = SvgParser()
1308  return parser.parse_etree(etree, layer_frames, *args, **kwargs)
1309 
1310 
1311 def parse_svg_file(file, layer_frames=0, *args, **kwargs):
1312  return parse_svg_etree(ElementTree.parse(file), layer_frames, *args, **kwargs)
Top level object, describing the animation.
Definition: animation.py:79
Single bezier curve.
Definition: bezier.py:123
Base class for all layers.
Definition: layers.py:23
Layer containing ShapeElement objects.
Definition: layers.py:222
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
def qualified(self, ns, name)
Definition: handler.py:20
def _do_arc(self, rx, ry, xrot, large, sweep, dest)
Definition: importer.py:1250
def _rpoint(self, point, rel=None)
Definition: importer.py:1039
def insert(self, dummy, shape)
Definition: importer.py:184
def to_value(self, bbox, default=None)
Definition: importer.py:29
def __init__(self, name, comp, value, percent)
Definition: importer.py:23
def parse(self, attr, default_percent)
Definition: importer.py:44
def to_lottie(self, gradient_shape, shape, time=0)
Definition: importer.py:64
def add_color(self, offset, color)
Definition: importer.py:61
def to_lottie(self, gradient_shape, shape, time=0)
Definition: importer.py:92
def parse_children(self, element, shape_parent, parent_style)
Definition: importer.py:636
def _svg_to_layer(self, animation, svg)
Definition: importer.py:270
def parse_style(self, element, parent_style)
Definition: importer.py:393
def apply_common_style(self, style, transform)
Definition: importer.py:405
def add_shapes(self, element, shapes, shape_parent, parent_style)
Definition: importer.py:413
def get_color_url(self, color, gradientclass, shape)
Definition: importer.py:732
def parse_animations(self, lottie, element)
Definition: importer.py:867
def _merge_animations(self, element, animations, val1, val2, dest, merge=NVector)
Definition: importer.py:932
def _fix_viewbox(self, svg, layers)
Definition: importer.py:280
def _apply_animations(self, animatable, name, animations, transform=lambda v:v)
Definition: importer.py:955
def _parseshape_text(self, element, shape_parent, parent_style, font_style=None)
Definition: importer.py:829
def _get_name(self, element, inkscapequal)
Definition: importer.py:211
def __init__(self, name_mode=NameMode.Inkscape)
Definition: importer.py:202
def apply_visibility(self, style, object)
Definition: importer.py:409
def _gradient(self, element, grad)
Definition: importer.py:697
def parse_shape(self, element, shape_parent, parent_style)
Definition: importer.py:644
def _add_style_shapes(self, style, group)
Definition: importer.py:431
def _apply_transform_element_to_matrix(self, matrix, t)
Definition: importer.py:657
def parse_transform(self, element, group, dest_trans)
Definition: importer.py:332
def parse_etree(self, etree, layer_frames=0, *args, **kwargs)
Definition: importer.py:224
def _transform_to_matrix(self, transform)
Definition: importer.py:689
def _parse_text_elem(self, element, style, group, parent_style, font_style)
Definition: importer.py:796
def etree_to_layer(self, animation, etree)
Definition: importer.py:260
def parse_animation_time(self, value)
Definition: importer.py:904
def _parseshape_g(self, element, shape_parent, parent_style)
Definition: importer.py:509
def _parse_text_style(self, style, font_style=None)
Definition: importer.py:747
def to_lottie(self, gradient_shape, shape, time=0)
Definition: importer.py:116
def parse_svg_file(file, layer_frames=0, *args, **kwargs)
Definition: importer.py:1311
def parse_svg_etree(etree, layer_frames=0, *args, **kwargs)
Definition: importer.py:1306
def parse_color(color, current_color=Color(0, 0, 0, 1))
Parses CSS colors.
Definition: importer.py:134