Coverage for lib/lottie/parsers/sif/sif/core.py: 89%
114 statements
« prev ^ index » next coverage.py v7.2.2, created at 2023-03-20 16:17 +0100
« prev ^ index » next coverage.py v7.2.2, created at 2023-03-20 16:17 +0100
1from xml.dom import minidom
2import enum
3from uuid import uuid4
5from lottie.nvector import NVector
6from lottie.parsers.sif.xml.utils import xml_text, str_to_bool
7from lottie.parsers.sif.xml.utils import xml_child_elements, value_from_xml_string, xml_make_text, value_to_xml_string
8from lottie.parsers.sif.sif.frame_time import FrameTime
11class ObjectRegistry:
12 def __init__(self):
13 self.registry = {}
15 def register_as(self, object, key):
16 self.registry[key] = object
18 def register(self, object):
19 guid = getattr(object, "guid", None)
20 if guid is None: 20 ↛ 21line 20 didn't jump to line 21, because the condition on line 20 was never true
21 guid = self.guid()
22 object.guid = guid
23 self.registry[guid] = object
25 @classmethod
26 def guid(cls):
27 return str(uuid4()).replace("-", "").upper()
29 def get_object(self, guid):
30 return self.registry[guid]
33def noop(x):
34 return x
37class TypeDescriptor:
38 _type_tag_names = {
39 "bone_object": "bone"
40 }
42 def __init__(self, typename, default=None, type_wrapper=noop):
43 self.typename = typename
44 self.type_wrapper = type_wrapper
45 self.default_value = default
47 def value_to_xml_element(self, value, dom: minidom.Document):
48 element = dom.createElement(self.tag_name)
50 if self.typename == "vector":
51 element.appendChild(xml_make_text(dom, "x", str(value.x)))
52 element.appendChild(xml_make_text(dom, "y", str(value.y)))
53 if hasattr(value, "guid"):
54 element.setAttribute("guid", value.guid)
55 elif self.typename == "color":
56 element.appendChild(xml_make_text(dom, "r", str(value[0])))
57 element.appendChild(xml_make_text(dom, "g", str(value[1])))
58 element.appendChild(xml_make_text(dom, "b", str(value[2])))
59 element.appendChild(xml_make_text(dom, "a", str(value[3])))
60 if hasattr(value, "guid"): 60 ↛ 61line 60 didn't jump to line 61, because the condition on line 60 was never true
61 element.setAttribute("guid", value.guid)
62 elif self.typename == "gradient":
63 for point in value:
64 element.appendChild(point.to_dom(dom))
65 elif self.typename == "bool":
66 element.setAttribute("value", "true" if value else "false")
67 elif self.typename == "bone_object": 67 ↛ 68line 67 didn't jump to line 68, because the condition on line 67 was never true
68 element.setAttribute("guid", value.guid)
69 element.setAttribute("type", self.typename)
70 elif self.typename == "string":
71 element.appendChild(dom.createTextNode(value))
72 else:
73 if isinstance(value, enum.Enum):
74 value = value.value
75 element.setAttribute("value", str(value))
77 return element
79 @property
80 def tag_name(self):
81 return self._type_tag_names.get(self.typename, self.typename)
83 def value_from_xml_element(self, xml: minidom.Element, registry: ObjectRegistry):
84 if xml.tagName != self.tag_name: 84 ↛ 85line 84 didn't jump to line 85, because the condition on line 84 was never true
85 raise ValueError("Wrong value type (%s instead of %s)" % (xml.tagName, self.tag_name))
87 guid = xml.getAttribute("guid")
88 if guid and guid in registry.registry:
89 value = registry.registry[guid]
90 elif self.typename == "vector":
91 value = NVector(
92 float(xml_text(xml.getElementsByTagName("x")[0])),
93 float(xml_text(xml.getElementsByTagName("y")[0]))
94 )
95 if xml.getAttribute("guid"):
96 value.guid = xml.getAttribute("guid")
97 registry.register(value)
98 elif self.typename == "color":
99 value = NVector(
100 float(xml_text(xml.getElementsByTagName("r")[0])),
101 float(xml_text(xml.getElementsByTagName("g")[0])),
102 float(xml_text(xml.getElementsByTagName("b")[0])),
103 float(xml_text(xml.getElementsByTagName("a")[0]))
104 )
105 elif self.typename == "gradient":
106 value = [
107 GradientPoint.from_dom(sub, registry)
108 for sub in xml_child_elements(xml, GradientPoint.type.typename)
109 ]
110 elif self.typename == "real" or self.typename == "angle":
111 value = float(xml.getAttribute("value"))
112 elif self.typename == "integer":
113 value = int(xml.getAttribute("value"))
114 elif self.typename == "time":
115 value = FrameTime.parse_string(xml.getAttribute("value"), registry)
116 elif self.typename == "bool":
117 value = str_to_bool(xml.getAttribute("value"))
118 elif self.typename == "string": 118 ↛ 120line 118 didn't jump to line 120, because the condition on line 118 was never false
119 return xml_text(xml)
120 elif self.typename == "bone_object":
121 # Already done above but this forces the guid to be present
122 return registry.get_object(xml.getAttribute("guid"))
123 else:
124 raise ValueError("Unsupported type %s" % self.typename)
126 return self.type_wrapper(value)
129class GradientPoint:
130 type = TypeDescriptor("color")
132 def __init__(self, pos: float, color: NVector):
133 self.pos = pos
134 self.color = color
136 def to_dom(self, dom: minidom.Document):
137 element = self.type.value_to_xml_element(self.color, dom)
138 element.setAttribute("pos", value_to_xml_string(self.pos, float))
139 return element
141 @classmethod
142 def from_dom(cls, xml: minidom.Element, registry: ObjectRegistry):
143 return GradientPoint(
144 value_from_xml_string(xml.getAttribute("pos"), float, registry),
145 cls.type.value_from_xml_element(xml, registry)
146 )
148 def __repr__(self):
149 return "<GradientPoint %s %s>" % (self.pos, self.color)
152class SifNodeMeta(type):
153 def __new__(cls, name, bases, attr):
154 props = []
155 for base in bases:
156 if type(base) == cls:
157 props += base._nodes
158 attr["_nodes"] = props + attr.get("_nodes", [])
159 if "_tag" not in attr:
160 attr["_tag"] = name.lower()
161 attr["_nodemap"] = {
162 node.att_name: node
163 for node in attr["_nodes"]
164 }
165 return super().__new__(cls, name, bases, attr)