1def _prefer_non_zero(*args): 2 for arg in args: 3 if arg != 0: 4 return arg 5 return 0. 6 7 8def _ntos(n): 9 # %f likes to add unnecessary 0's, %g isn't consistent about # decimals 10 return ('%.3f' % n).rstrip('0').rstrip('.') 11 12 13def _strip_xml_ns(tag): 14 # ElementTree API doesn't provide a way to ignore XML namespaces in tags 15 # so we here strip them ourselves: cf. https://bugs.python.org/issue18304 16 return tag.split('}', 1)[1] if '}' in tag else tag 17 18 19class PathBuilder(object): 20 def __init__(self): 21 self.paths = [] 22 23 def _start_path(self, initial_path=''): 24 self.paths.append(initial_path) 25 26 def _end_path(self): 27 self._add('z') 28 29 def _add(self, path_snippet): 30 path = self.paths[-1] 31 if path: 32 path += ' ' + path_snippet 33 else: 34 path = path_snippet 35 self.paths[-1] = path 36 37 def _move(self, c, x, y): 38 self._add('%s%s,%s' % (c, _ntos(x), _ntos(y))) 39 40 def M(self, x, y): 41 self._move('M', x, y) 42 43 def m(self, x, y): 44 self._move('m', x, y) 45 46 def _arc(self, c, rx, ry, x, y, large_arc): 47 self._add('%s%s,%s 0 %d 1 %s,%s' % (c, _ntos(rx), _ntos(ry), large_arc, 48 _ntos(x), _ntos(y))) 49 50 def A(self, rx, ry, x, y, large_arc=0): 51 self._arc('A', rx, ry, x, y, large_arc) 52 53 def a(self, rx, ry, x, y, large_arc=0): 54 self._arc('a', rx, ry, x, y, large_arc) 55 56 def _vhline(self, c, x): 57 self._add('%s%s' % (c, _ntos(x))) 58 59 def H(self, x): 60 self._vhline('H', x) 61 62 def h(self, x): 63 self._vhline('h', x) 64 65 def V(self, y): 66 self._vhline('V', y) 67 68 def v(self, y): 69 self._vhline('v', y) 70 71 def _parse_rect(self, rect): 72 x = float(rect.attrib.get('x', 0)) 73 y = float(rect.attrib.get('y', 0)) 74 w = float(rect.attrib.get('width')) 75 h = float(rect.attrib.get('height')) 76 rx = float(rect.attrib.get('rx', 0)) 77 ry = float(rect.attrib.get('ry', 0)) 78 79 rx = _prefer_non_zero(rx, ry) 80 ry = _prefer_non_zero(ry, rx) 81 # TODO there are more rules for adjusting rx, ry 82 83 self._start_path() 84 self.M(x + rx, y) 85 self.H(x + w - rx) 86 if rx > 0: 87 self.A(rx, ry, x + w, y + ry) 88 self.V(y + h - ry) 89 if rx > 0: 90 self.A(rx, ry, x + w - rx, y + h) 91 self.H(x + rx) 92 if rx > 0: 93 self.A(rx, ry, x, y + h - ry) 94 self.V(y + ry) 95 if rx > 0: 96 self.A(rx, ry, x + rx, y) 97 self._end_path() 98 99 def _parse_path(self, path): 100 if 'd' in path.attrib: 101 self._start_path(initial_path=path.attrib['d']) 102 103 def _parse_polygon(self, poly): 104 if 'points' in poly.attrib: 105 self._start_path('M' + poly.attrib['points']) 106 self._end_path() 107 108 def _parse_circle(self, circle): 109 cx = float(circle.attrib.get('cx', 0)) 110 cy = float(circle.attrib.get('cy', 0)) 111 r = float(circle.attrib.get('r')) 112 113 # arc doesn't seem to like being a complete shape, draw two halves 114 self._start_path() 115 self.M(cx - r, cy) 116 self.A(r, r, cx + r, cy, large_arc=1) 117 self.A(r, r, cx - r, cy, large_arc=1) 118 119 def add_path_from_element(self, el): 120 tag = _strip_xml_ns(el.tag) 121 parse_fn = getattr(self, '_parse_%s' % tag.lower(), None) 122 if not callable(parse_fn): 123 return False 124 parse_fn(el) 125 return True 126