1 from __future__ import absolute_import
2 __copyright__ = "Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License"
8 from xml.etree import ElementTree
10 def applyTransformString(matrix, transform):
11 s = transform.find('(')
12 e = transform.find(')')
14 print 'Unknown transform: %s' % (transform)
17 data = map(float, re.split('[ \t,]+', transform[s+1:e].strip()))
18 if tag == 'matrix' and len(data) == 6:
19 return matrix * numpy.matrix([[data[0],data[1],0],[data[2],data[3],0],[data[4],data[5],1]], numpy.float64)
20 elif tag == 'translate' and len(data) == 1:
21 return matrix * numpy.matrix([[1,0,data[0]],[0,1,0],[0,0,1]], numpy.float64)
22 elif tag == 'translate' and len(data) == 2:
23 return matrix * numpy.matrix([[1,0,0],[0,1,0],[data[0],data[1],1]], numpy.float64)
24 elif tag == 'scale' and len(data) == 1:
25 return matrix * numpy.matrix([[data[0],0,0],[0,data[0],0],[0,0,1]], numpy.float64)
26 elif tag == 'scale' and len(data) == 2:
27 return matrix * numpy.matrix([[data[0],0,0],[0,data[1],0],[0,0,1]], numpy.float64)
28 elif tag == 'rotate' and len(data) == 1:
29 r = math.radians(data[0])
30 return matrix * numpy.matrix([[math.cos(r),math.sin(r),0],[-math.sin(r),math.cos(r),0],[0,0,1]], numpy.float64)
31 elif tag == 'skewX' and len(data) == 1:
32 return matrix * numpy.matrix([[1,0,0],[math.tan(data[0]),1,0],[0,0,1]], numpy.float64)
33 elif tag == 'skewY' and len(data) == 1:
34 return matrix * numpy.matrix([[1,math.tan(data[0]),0],[0,1,0],[0,0,1]], numpy.float64)
35 print 'Unknown transform: %s' % (transform)
43 def __init__(self, x, y, matrix):
45 self._relMatrix = numpy.matrix([[matrix[0,0],matrix[0,1]], [matrix[1,0],matrix[1,1]]])
46 self._startPoint = complex(x, y)
49 def addLineTo(self, x, y):
50 self._points.append({'type': Path.LINE, 'p': complex(x, y)})
52 def addArcTo(self, x, y, rot, rx, ry, large, sweep):
57 'radius': complex(rx, ry),
62 def addCurveTo(self, x, y, cp1x, cp1y, cp2x, cp2y):
66 'cp1': complex(cp1x, cp1y),
67 'cp2': complex(cp2x, cp2y)
71 self._points.append({'type': Path.LINE, 'p': self._startPoint})
74 pointList = [self._m(self._startPoint)]
76 for p in self._points:
77 if p['type'] == Path.LINE:
79 pointList.append(self._m(p1))
80 elif p['type'] == Path.ARC:
82 rot = math.radians(p['rot'])
85 #http://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter
87 p1alt = diff #TODO: apply rot
88 p2alt = -diff #TODO: apply rot
91 x1alt2 = p1alt.real*p1alt.real
92 y1alt2 = p1alt.imag*p1alt.imag
94 f = x1alt2 / rx2 + y1alt2 / ry2
96 r *= math.sqrt(f+0.000001)
100 f = math.sqrt((rx2*ry2 - rx2*y1alt2 - ry2*x1alt2) / (rx2*y1alt2+ry2*x1alt2))
101 if p['large'] == p['sweep']:
103 cAlt = f * complex(r.real*p1alt.imag/r.imag, -r.imag*p1alt.real/r.real)
105 c = cAlt + (p1 + p2) / 2 #TODO: apply rot
107 a1 = math.atan2((p1alt.imag - cAlt.imag) / r.imag, (p1alt.real - cAlt.real) / r.real)
108 a2 = math.atan2((p2alt.imag - cAlt.imag) / r.imag, (p2alt.real - cAlt.real) / r.real)
112 large = abs(a2 - a1) > math.pi
113 if large != p['large']:
119 for n in xrange(0, 16):
120 pointList.append(self._m(c + complex(math.cos(a1 + n*(a2-a1)/16) * r.real, math.sin(a1 + n*(a2-a1)/16) * r.imag)))
122 pointList.append(self._m(p2))
124 elif p['type'] == Path.CURVE:
127 cp1 = self._m(p['cp1'])
128 cp2 = self._m(p['cp2'])
130 for n in xrange(0, 10):
133 point = p1_*g*g*g + cp1*3.0*g*g*f + cp2*3.0*g*f*f + p2*f*f*f
134 pointList.append(point)
141 def getSVGPath(self):
142 p0 = self._m(self._startPoint)
143 ret = 'M %f %f ' % (p0.real, p0.imag)
144 for p in self._points:
145 if p['type'] == Path.LINE:
147 ret += 'L %f %f' % (p0.real, p0.imag)
148 elif p['type'] == Path.ARC:
151 ret += 'A %f %f 0 %d %d %f %f' % (radius.real, radius.imag, p['large'], p['sweep'], p0.real, p0.imag)
152 elif p['type'] == Path.CURVE:
154 cp1 = self._m(p['cp1'])
155 cp2 = self._m(p['cp2'])
156 ret += 'C %f %f %f %f %f %f' % (cp1.real, cp1.imag, cp2.real, cp2.imag, p0.real, p0.imag)
161 tmp = numpy.matrix([p.real, p.imag, 1], numpy.float64) * self._matrix
162 return complex(tmp[0,0], tmp[0,1])
164 tmp = numpy.matrix([p.real, p.imag], numpy.float64) * self._relMatrix
165 return complex(tmp[0,0], tmp[0,1])
168 def __init__(self, filename):
170 self.tagProcess['rect'] = self._processRectTag
171 self.tagProcess['line'] = self._processLineTag
172 self.tagProcess['polyline'] = self._processPolylineTag
173 self.tagProcess['polygon'] = self._processPolygonTag
174 self.tagProcess['elipse'] = self._processPolygonTag
175 self.tagProcess['circle'] = self._processCircleTag
176 self.tagProcess['ellipse'] = self._processEllipseTag
177 self.tagProcess['path'] = self._processPathTag
178 self.tagProcess['g'] = self._processGTag
179 self.tagProcess['a'] = self._processGTag
180 self.tagProcess['text'] = None #No text implementation yet
181 self.tagProcess['image'] = None
182 self.tagProcess['metadata'] = None
183 self.tagProcess['defs'] = None
184 self.tagProcess['title'] = None
185 self.tagProcess['animate'] = None
186 self.tagProcess['set'] = None
187 self.tagProcess['script'] = None
190 self.tagProcess['namedview'] = None
192 self.tagProcess['SVGTestCase'] = None
195 f = open(filename, "r")
196 self._processGTag(ElementTree.parse(f).getroot(), numpy.matrix(numpy.identity(3, numpy.float64)))
199 def _processGTag(self, tag, baseMatrix):
201 if e.get('transform') is None:
204 matrix = applyTransformString(baseMatrix, e.get('transform'))
205 tag = e.tag[e.tag.find('}')+1:]
206 if not tag in self.tagProcess:
207 print 'unknown tag: %s' % (tag)
208 elif self.tagProcess[tag] is not None:
209 self.tagProcess[tag](e, matrix)
211 def _processLineTag(self, tag, matrix):
212 x1 = float(tag.get('x1', 0))
213 y1 = float(tag.get('y1', 0))
214 x2 = float(tag.get('x2', 0))
215 y2 = float(tag.get('y2', 0))
216 p = Path(x1, y1, matrix)
220 def _processPolylineTag(self, tag, matrix):
221 values = map(float, re.split('[, \t]+', tag.get('points', '').replace('-', ' -').strip()))
222 p = Path(values[0], values[1], matrix)
223 for n in xrange(2, len(values)-1, 2):
224 p.addLineTo(values[n], values[n+1])
227 def _processPolygonTag(self, tag, matrix):
228 values = map(float, re.split('[, \t]+', tag.get('points', '').replace('-', ' -').strip()))
229 p = Path(values[0], values[1], matrix)
230 for n in xrange(2, len(values)-1, 2):
231 p.addLineTo(values[n], values[n+1])
235 def _processCircleTag(self, tag, matrix):
236 cx = float(tag.get('cx', 0))
237 cy = float(tag.get('cy', 0))
238 r = float(tag.get('r', 0))
239 p = Path(cx-r, cy, matrix)
240 p.addArcTo(cx+r, cy, 0, r, r, False, False)
241 p.addArcTo(cx-r, cy, 0, r, r, False, False)
244 def _processEllipseTag(self, tag, matrix):
245 cx = float(tag.get('cx', 0))
246 cy = float(tag.get('cy', 0))
247 rx = float(tag.get('rx', 0))
248 ry = float(tag.get('rx', 0))
249 p = Path(cx-rx, cy, matrix)
250 p.addArcTo(cx+rx, cy, 0, rx, ry, False, False)
251 p.addArcTo(cx-rx, cy, 0, rx, ry, False, False)
254 def _processRectTag(self, tag, matrix):
255 x = float(tag.get('x', 0))
256 y = float(tag.get('y', 0))
257 width = float(tag.get('width', 0))
258 height = float(tag.get('height', 0))
259 if width <= 0 or height <= 0:
263 if rx is not None or ry is not None:
278 if rx > 0 and ry > 0:
279 p = Path(x+rx, y, matrix)
280 p.addLineTo(x+width-rx, y)
281 p.addArcTo(x+width,y+ry, 0, rx, ry, False, True)
282 p.addLineTo(x+width, y+height-ry)
283 p.addArcTo(x+width-rx,y+height, 0, rx, ry, False, True)
284 p.addLineTo(x+rx, y+height)
285 p.addArcTo(x,y+height-ry, 0, rx, ry, False, True)
287 p.addArcTo(x+rx,y, 0, rx, ry, False, True)
290 p = Path(x, y, matrix)
291 p.addLineTo(x,y+height)
292 p.addLineTo(x+width,y+height)
293 p.addLineTo(x+width,y)
297 def _processPathTag(self, tag, matrix):
298 pathString = tag.get('d', '').replace(',', ' ').replace('-', ' -')
302 for command in re.findall('[a-df-zA-DF-Z][^a-df-zA-DF-Z]*', pathString):
303 params = re.split(' +', command[1:].strip())
304 if len(params) > 0 and params[0] == '':
306 if len(params) > 0 and params[-1] == '':
308 params = map(float, params)
314 path = Path(x, y, matrix)
315 self.paths.append(path)
317 while len(params) > 1:
325 path = Path(x, y, matrix)
326 self.paths.append(path)
328 while len(params) > 1:
334 while len(params) > 1:
340 while len(params) > 1:
358 while len(params) > 6:
361 path.addArcTo(x, y, params[2], params[0], params[1], params[3] > 0, params[4] > 0)
364 while len(params) > 6:
367 path.addArcTo(x, y, params[2], params[0], params[1], params[3] > 0, params[4] > 0)
370 while len(params) > 5:
377 path.addCurveTo(x, y, c1x, c1y, c2x, c2y)
380 while len(params) > 5:
387 path.addCurveTo(x, y, c1x, c1y, c2x, c2y)
389 elif command == 'z' or command == 'Z':
391 x = path._startPoint.real
392 y = path._startPoint.imag
394 print 'Unknown path command:', command, params
397 if __name__ == '__main__':
398 for n in xrange(1, len(sys.argv)):
399 print 'File: %s' % (sys.argv[n])
400 svg = SVG(sys.argv[n])
402 f = open("test_export.html", "w")
404 f.write("<!DOCTYPE html><html><body>\n")
405 f.write("<svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\" style='width:%dpx;height:%dpx'>\n" % (1000, 1000))
406 f.write("<g fill-rule='evenodd' style=\"fill: gray; stroke:black;stroke-width:2\">\n")
407 f.write("<path d=\"")
408 for path in svg.paths:
409 points = path.getPoints()
410 f.write("M %f %f " % (points[0].real, points[0].imag))
411 for point in points[1:]:
412 f.write("L %f %f " % (point.real, point.imag))
416 f.write("<g style=\"fill: none; stroke:red;stroke-width:1\">\n")
417 f.write("<path d=\"")
418 for path in svg.paths:
419 f.write(path.getSVGPath())
424 f.write("</body></html>")