1*e1fe3e4aSElliott Hughesimport re 2*e1fe3e4aSElliott Hughes 3*e1fe3e4aSElliott Hughes 4*e1fe3e4aSElliott Hughesdef _prefer_non_zero(*args): 5*e1fe3e4aSElliott Hughes for arg in args: 6*e1fe3e4aSElliott Hughes if arg != 0: 7*e1fe3e4aSElliott Hughes return arg 8*e1fe3e4aSElliott Hughes return 0.0 9*e1fe3e4aSElliott Hughes 10*e1fe3e4aSElliott Hughes 11*e1fe3e4aSElliott Hughesdef _ntos(n): 12*e1fe3e4aSElliott Hughes # %f likes to add unnecessary 0's, %g isn't consistent about # decimals 13*e1fe3e4aSElliott Hughes return ("%.3f" % n).rstrip("0").rstrip(".") 14*e1fe3e4aSElliott Hughes 15*e1fe3e4aSElliott Hughes 16*e1fe3e4aSElliott Hughesdef _strip_xml_ns(tag): 17*e1fe3e4aSElliott Hughes # ElementTree API doesn't provide a way to ignore XML namespaces in tags 18*e1fe3e4aSElliott Hughes # so we here strip them ourselves: cf. https://bugs.python.org/issue18304 19*e1fe3e4aSElliott Hughes return tag.split("}", 1)[1] if "}" in tag else tag 20*e1fe3e4aSElliott Hughes 21*e1fe3e4aSElliott Hughes 22*e1fe3e4aSElliott Hughesdef _transform(raw_value): 23*e1fe3e4aSElliott Hughes # TODO assumes a 'matrix' transform. 24*e1fe3e4aSElliott Hughes # No other transform functions are supported at the moment. 25*e1fe3e4aSElliott Hughes # https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/transform 26*e1fe3e4aSElliott Hughes # start simple: if you aren't exactly matrix(...) then no love 27*e1fe3e4aSElliott Hughes match = re.match(r"matrix\((.*)\)", raw_value) 28*e1fe3e4aSElliott Hughes if not match: 29*e1fe3e4aSElliott Hughes raise NotImplementedError 30*e1fe3e4aSElliott Hughes matrix = tuple(float(p) for p in re.split(r"\s+|,", match.group(1))) 31*e1fe3e4aSElliott Hughes if len(matrix) != 6: 32*e1fe3e4aSElliott Hughes raise ValueError("wrong # of terms in %s" % raw_value) 33*e1fe3e4aSElliott Hughes return matrix 34*e1fe3e4aSElliott Hughes 35*e1fe3e4aSElliott Hughes 36*e1fe3e4aSElliott Hughesclass PathBuilder(object): 37*e1fe3e4aSElliott Hughes def __init__(self): 38*e1fe3e4aSElliott Hughes self.paths = [] 39*e1fe3e4aSElliott Hughes self.transforms = [] 40*e1fe3e4aSElliott Hughes 41*e1fe3e4aSElliott Hughes def _start_path(self, initial_path=""): 42*e1fe3e4aSElliott Hughes self.paths.append(initial_path) 43*e1fe3e4aSElliott Hughes self.transforms.append(None) 44*e1fe3e4aSElliott Hughes 45*e1fe3e4aSElliott Hughes def _end_path(self): 46*e1fe3e4aSElliott Hughes self._add("z") 47*e1fe3e4aSElliott Hughes 48*e1fe3e4aSElliott Hughes def _add(self, path_snippet): 49*e1fe3e4aSElliott Hughes path = self.paths[-1] 50*e1fe3e4aSElliott Hughes if path: 51*e1fe3e4aSElliott Hughes path += " " + path_snippet 52*e1fe3e4aSElliott Hughes else: 53*e1fe3e4aSElliott Hughes path = path_snippet 54*e1fe3e4aSElliott Hughes self.paths[-1] = path 55*e1fe3e4aSElliott Hughes 56*e1fe3e4aSElliott Hughes def _move(self, c, x, y): 57*e1fe3e4aSElliott Hughes self._add("%s%s,%s" % (c, _ntos(x), _ntos(y))) 58*e1fe3e4aSElliott Hughes 59*e1fe3e4aSElliott Hughes def M(self, x, y): 60*e1fe3e4aSElliott Hughes self._move("M", x, y) 61*e1fe3e4aSElliott Hughes 62*e1fe3e4aSElliott Hughes def m(self, x, y): 63*e1fe3e4aSElliott Hughes self._move("m", x, y) 64*e1fe3e4aSElliott Hughes 65*e1fe3e4aSElliott Hughes def _arc(self, c, rx, ry, x, y, large_arc): 66*e1fe3e4aSElliott Hughes self._add( 67*e1fe3e4aSElliott Hughes "%s%s,%s 0 %d 1 %s,%s" 68*e1fe3e4aSElliott Hughes % (c, _ntos(rx), _ntos(ry), large_arc, _ntos(x), _ntos(y)) 69*e1fe3e4aSElliott Hughes ) 70*e1fe3e4aSElliott Hughes 71*e1fe3e4aSElliott Hughes def A(self, rx, ry, x, y, large_arc=0): 72*e1fe3e4aSElliott Hughes self._arc("A", rx, ry, x, y, large_arc) 73*e1fe3e4aSElliott Hughes 74*e1fe3e4aSElliott Hughes def a(self, rx, ry, x, y, large_arc=0): 75*e1fe3e4aSElliott Hughes self._arc("a", rx, ry, x, y, large_arc) 76*e1fe3e4aSElliott Hughes 77*e1fe3e4aSElliott Hughes def _vhline(self, c, x): 78*e1fe3e4aSElliott Hughes self._add("%s%s" % (c, _ntos(x))) 79*e1fe3e4aSElliott Hughes 80*e1fe3e4aSElliott Hughes def H(self, x): 81*e1fe3e4aSElliott Hughes self._vhline("H", x) 82*e1fe3e4aSElliott Hughes 83*e1fe3e4aSElliott Hughes def h(self, x): 84*e1fe3e4aSElliott Hughes self._vhline("h", x) 85*e1fe3e4aSElliott Hughes 86*e1fe3e4aSElliott Hughes def V(self, y): 87*e1fe3e4aSElliott Hughes self._vhline("V", y) 88*e1fe3e4aSElliott Hughes 89*e1fe3e4aSElliott Hughes def v(self, y): 90*e1fe3e4aSElliott Hughes self._vhline("v", y) 91*e1fe3e4aSElliott Hughes 92*e1fe3e4aSElliott Hughes def _line(self, c, x, y): 93*e1fe3e4aSElliott Hughes self._add("%s%s,%s" % (c, _ntos(x), _ntos(y))) 94*e1fe3e4aSElliott Hughes 95*e1fe3e4aSElliott Hughes def L(self, x, y): 96*e1fe3e4aSElliott Hughes self._line("L", x, y) 97*e1fe3e4aSElliott Hughes 98*e1fe3e4aSElliott Hughes def l(self, x, y): 99*e1fe3e4aSElliott Hughes self._line("l", x, y) 100*e1fe3e4aSElliott Hughes 101*e1fe3e4aSElliott Hughes def _parse_line(self, line): 102*e1fe3e4aSElliott Hughes x1 = float(line.attrib.get("x1", 0)) 103*e1fe3e4aSElliott Hughes y1 = float(line.attrib.get("y1", 0)) 104*e1fe3e4aSElliott Hughes x2 = float(line.attrib.get("x2", 0)) 105*e1fe3e4aSElliott Hughes y2 = float(line.attrib.get("y2", 0)) 106*e1fe3e4aSElliott Hughes 107*e1fe3e4aSElliott Hughes self._start_path() 108*e1fe3e4aSElliott Hughes self.M(x1, y1) 109*e1fe3e4aSElliott Hughes self.L(x2, y2) 110*e1fe3e4aSElliott Hughes 111*e1fe3e4aSElliott Hughes def _parse_rect(self, rect): 112*e1fe3e4aSElliott Hughes x = float(rect.attrib.get("x", 0)) 113*e1fe3e4aSElliott Hughes y = float(rect.attrib.get("y", 0)) 114*e1fe3e4aSElliott Hughes w = float(rect.attrib.get("width")) 115*e1fe3e4aSElliott Hughes h = float(rect.attrib.get("height")) 116*e1fe3e4aSElliott Hughes rx = float(rect.attrib.get("rx", 0)) 117*e1fe3e4aSElliott Hughes ry = float(rect.attrib.get("ry", 0)) 118*e1fe3e4aSElliott Hughes 119*e1fe3e4aSElliott Hughes rx = _prefer_non_zero(rx, ry) 120*e1fe3e4aSElliott Hughes ry = _prefer_non_zero(ry, rx) 121*e1fe3e4aSElliott Hughes # TODO there are more rules for adjusting rx, ry 122*e1fe3e4aSElliott Hughes 123*e1fe3e4aSElliott Hughes self._start_path() 124*e1fe3e4aSElliott Hughes self.M(x + rx, y) 125*e1fe3e4aSElliott Hughes self.H(x + w - rx) 126*e1fe3e4aSElliott Hughes if rx > 0: 127*e1fe3e4aSElliott Hughes self.A(rx, ry, x + w, y + ry) 128*e1fe3e4aSElliott Hughes self.V(y + h - ry) 129*e1fe3e4aSElliott Hughes if rx > 0: 130*e1fe3e4aSElliott Hughes self.A(rx, ry, x + w - rx, y + h) 131*e1fe3e4aSElliott Hughes self.H(x + rx) 132*e1fe3e4aSElliott Hughes if rx > 0: 133*e1fe3e4aSElliott Hughes self.A(rx, ry, x, y + h - ry) 134*e1fe3e4aSElliott Hughes self.V(y + ry) 135*e1fe3e4aSElliott Hughes if rx > 0: 136*e1fe3e4aSElliott Hughes self.A(rx, ry, x + rx, y) 137*e1fe3e4aSElliott Hughes self._end_path() 138*e1fe3e4aSElliott Hughes 139*e1fe3e4aSElliott Hughes def _parse_path(self, path): 140*e1fe3e4aSElliott Hughes if "d" in path.attrib: 141*e1fe3e4aSElliott Hughes self._start_path(initial_path=path.attrib["d"]) 142*e1fe3e4aSElliott Hughes 143*e1fe3e4aSElliott Hughes def _parse_polygon(self, poly): 144*e1fe3e4aSElliott Hughes if "points" in poly.attrib: 145*e1fe3e4aSElliott Hughes self._start_path("M" + poly.attrib["points"]) 146*e1fe3e4aSElliott Hughes self._end_path() 147*e1fe3e4aSElliott Hughes 148*e1fe3e4aSElliott Hughes def _parse_polyline(self, poly): 149*e1fe3e4aSElliott Hughes if "points" in poly.attrib: 150*e1fe3e4aSElliott Hughes self._start_path("M" + poly.attrib["points"]) 151*e1fe3e4aSElliott Hughes 152*e1fe3e4aSElliott Hughes def _parse_circle(self, circle): 153*e1fe3e4aSElliott Hughes cx = float(circle.attrib.get("cx", 0)) 154*e1fe3e4aSElliott Hughes cy = float(circle.attrib.get("cy", 0)) 155*e1fe3e4aSElliott Hughes r = float(circle.attrib.get("r")) 156*e1fe3e4aSElliott Hughes 157*e1fe3e4aSElliott Hughes # arc doesn't seem to like being a complete shape, draw two halves 158*e1fe3e4aSElliott Hughes self._start_path() 159*e1fe3e4aSElliott Hughes self.M(cx - r, cy) 160*e1fe3e4aSElliott Hughes self.A(r, r, cx + r, cy, large_arc=1) 161*e1fe3e4aSElliott Hughes self.A(r, r, cx - r, cy, large_arc=1) 162*e1fe3e4aSElliott Hughes 163*e1fe3e4aSElliott Hughes def _parse_ellipse(self, ellipse): 164*e1fe3e4aSElliott Hughes cx = float(ellipse.attrib.get("cx", 0)) 165*e1fe3e4aSElliott Hughes cy = float(ellipse.attrib.get("cy", 0)) 166*e1fe3e4aSElliott Hughes rx = float(ellipse.attrib.get("rx")) 167*e1fe3e4aSElliott Hughes ry = float(ellipse.attrib.get("ry")) 168*e1fe3e4aSElliott Hughes 169*e1fe3e4aSElliott Hughes # arc doesn't seem to like being a complete shape, draw two halves 170*e1fe3e4aSElliott Hughes self._start_path() 171*e1fe3e4aSElliott Hughes self.M(cx - rx, cy) 172*e1fe3e4aSElliott Hughes self.A(rx, ry, cx + rx, cy, large_arc=1) 173*e1fe3e4aSElliott Hughes self.A(rx, ry, cx - rx, cy, large_arc=1) 174*e1fe3e4aSElliott Hughes 175*e1fe3e4aSElliott Hughes def add_path_from_element(self, el): 176*e1fe3e4aSElliott Hughes tag = _strip_xml_ns(el.tag) 177*e1fe3e4aSElliott Hughes parse_fn = getattr(self, "_parse_%s" % tag.lower(), None) 178*e1fe3e4aSElliott Hughes if not callable(parse_fn): 179*e1fe3e4aSElliott Hughes return False 180*e1fe3e4aSElliott Hughes parse_fn(el) 181*e1fe3e4aSElliott Hughes if "transform" in el.attrib: 182*e1fe3e4aSElliott Hughes self.transforms[-1] = _transform(el.attrib["transform"]) 183*e1fe3e4aSElliott Hughes return True 184