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