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