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 if len(re.findall('\)', transform)) > 1:
13 while transform != '':
14 if transform[0] == ',':
15 transform = transform[1:].strip()
16 s = transform.find('(')
17 e = transform.find(')')
19 print 'Unknown transform: %s' % (transform)
22 data = map(float, re.split('[ \t,]+', transform[s+1:e].strip()))
23 if tag == 'matrix' and len(data) == 6:
24 matrix = numpy.matrix([[data[0],data[1],0],[data[2],data[3],0],[data[4],data[5],1]], numpy.float64) * matrix
25 elif tag == 'translate' and len(data) == 1:
26 matrix = numpy.matrix([[1,0,data[0]],[0,1,0],[0,0,1]], numpy.float64) * matrix
27 elif tag == 'translate' and len(data) == 2:
28 matrix = numpy.matrix([[1,0,0],[0,1,0],[data[0],data[1],1]], numpy.float64) * matrix
29 elif tag == 'scale' and len(data) == 1:
30 matrix = numpy.matrix([[data[0],0,0],[0,data[0],0],[0,0,1]], numpy.float64) * matrix
31 elif tag == 'scale' and len(data) == 2:
32 matrix = numpy.matrix([[data[0],0,0],[0,data[1],0],[0,0,1]], numpy.float64) * matrix
33 elif tag == 'rotate' and len(data) == 1:
34 r = math.radians(data[0])
35 matrix = numpy.matrix([[math.cos(r),math.sin(r),0],[-math.sin(r),math.cos(r),0],[0,0,1]], numpy.float64) * matrix
36 elif tag == 'rotate' and len(data) == 3:
37 matrix = numpy.matrix([[1,0,0],[0,1,0],[data[1],data[2],1]], numpy.float64) * matrix
38 r = math.radians(data[0])
39 matrix = numpy.matrix([[math.cos(r),math.sin(r),0],[-math.sin(r),math.cos(r),0],[0,0,1]], numpy.float64) * matrix
40 matrix = numpy.matrix([[1,0,0],[0,1,0],[-data[1],-data[2],1]], numpy.float64) * matrix
41 elif tag == 'skewX' and len(data) == 1:
42 matrix = numpy.matrix([[1,0,0],[math.tan(data[0]),1,0],[0,0,1]], numpy.float64) * matrix
43 elif tag == 'skewY' and len(data) == 1:
44 matrix = numpy.matrix([[1,math.tan(data[0]),0],[0,1,0],[0,0,1]], numpy.float64) * matrix
46 print 'Unknown transform: %s' % (transform)
48 transform = transform[e+1:].strip()
52 f = re.search('^[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?', f).group(0)
60 def __init__(self, x, y, matrix):
62 self._relMatrix = numpy.matrix([[matrix[0,0],matrix[0,1]], [matrix[1,0],matrix[1,1]]])
63 self._startPoint = complex(x, y)
66 def addLineTo(self, x, y):
67 self._points.append({'type': Path.LINE, 'p': complex(x, y)})
69 def addArcTo(self, x, y, rot, rx, ry, large, sweep):
74 'radius': complex(rx, ry),
79 def addCurveTo(self, x, y, cp1x, cp1y, cp2x, cp2y):
83 'cp1': complex(cp1x, cp1y),
84 'cp2': complex(cp2x, cp2y)
88 self._points.append({'type': Path.LINE, 'p': self._startPoint})
90 def getPoints(self, accuracy = 1):
91 pointList = [self._m(self._startPoint)]
93 for p in self._points:
94 if p['type'] == Path.LINE:
96 pointList.append(self._m(p1))
97 elif p['type'] == Path.ARC:
99 rot = math.radians(p['rot'])
102 #http://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter
104 p1alt = diff #TODO: apply rot
105 p2alt = -diff #TODO: apply rot
108 x1alt2 = p1alt.real*p1alt.real
109 y1alt2 = p1alt.imag*p1alt.imag
111 f = x1alt2 / rx2 + y1alt2 / ry2
113 r *= math.sqrt(f+0.000001)
117 f = math.sqrt((rx2*ry2 - rx2*y1alt2 - ry2*x1alt2) / (rx2*y1alt2+ry2*x1alt2))
118 if p['large'] == p['sweep']:
120 cAlt = f * complex(r.real*p1alt.imag/r.imag, -r.imag*p1alt.real/r.real)
122 c = cAlt + (p1 + p2) / 2 #TODO: apply rot
124 a1 = math.atan2((p1alt.imag - cAlt.imag) / r.imag, (p1alt.real - cAlt.real) / r.real)
125 a2 = math.atan2((p2alt.imag - cAlt.imag) / r.imag, (p2alt.real - cAlt.real) / r.real)
127 large = abs(a2 - a1) > math.pi
128 if large != p['large']:
134 pCenter = self._m(c + complex(math.cos(a1 + 0.5*(a2-a1)) * r.real, math.sin(a1 + 0.5*(a2-a1)) * r.imag))
135 dist = abs(pCenter - self._m(p1)) + abs(pCenter - self._m(p2))
136 segments = int(dist / accuracy) + 1
137 for n in xrange(1, segments):
138 pointList.append(self._m(c + complex(math.cos(a1 + n*(a2-a1)/segments) * r.real, math.sin(a1 + n*(a2-a1)/segments) * r.imag)))
140 pointList.append(self._m(p2))
142 elif p['type'] == Path.CURVE:
145 cp1 = self._m(p['cp1'])
146 cp2 = self._m(p['cp2'])
148 pCenter = p1_*0.5*0.5*0.5 + cp1*3.0*0.5*0.5*0.5 + cp2*3.0*0.5*0.5*0.5 + p2*0.5*0.5*0.5
149 dist = abs(pCenter - p1_) + abs(pCenter - p2)
150 segments = int(dist / accuracy) + 1
151 for n in xrange(1, segments):
152 f = n / float(segments)
154 point = p1_*g*g*g + cp1*3.0*g*g*f + cp2*3.0*g*f*f + p2*f*f*f
155 pointList.append(point)
162 def getSVGPath(self):
163 p0 = self._m(self._startPoint)
164 ret = 'M %f %f ' % (p0.real, p0.imag)
165 for p in self._points:
166 if p['type'] == Path.LINE:
168 ret += 'L %f %f' % (p0.real, p0.imag)
169 elif p['type'] == Path.ARC:
172 ret += 'A %f %f 0 %d %d %f %f' % (radius.real, radius.imag, 1 if p['large'] else 0, 1 if p['sweep'] else 0, p0.real, p0.imag)
173 elif p['type'] == Path.CURVE:
175 cp1 = self._m(p['cp1'])
176 cp2 = self._m(p['cp2'])
177 ret += 'C %f %f %f %f %f %f' % (cp1.real, cp1.imag, cp2.real, cp2.imag, p0.real, p0.imag)
182 tmp = numpy.matrix([p.real, p.imag, 1], numpy.float64) * self._matrix
183 return complex(tmp[0,0], tmp[0,1])
185 tmp = numpy.matrix([p.real, p.imag], numpy.float64) * self._relMatrix
186 return complex(tmp[0,0], tmp[0,1])
189 def __init__(self, filename):
191 self.tagProcess['rect'] = self._processRectTag
192 self.tagProcess['line'] = self._processLineTag
193 self.tagProcess['polyline'] = self._processPolylineTag
194 self.tagProcess['polygon'] = self._processPolygonTag
195 self.tagProcess['elipse'] = self._processPolygonTag
196 self.tagProcess['circle'] = self._processCircleTag
197 self.tagProcess['ellipse'] = self._processEllipseTag
198 self.tagProcess['path'] = self._processPathTag
199 self.tagProcess['use'] = self._processUseTag
200 self.tagProcess['g'] = self._processGTag
201 self.tagProcess['a'] = self._processGTag
202 self.tagProcess['svg'] = self._processGTag
203 self.tagProcess['text'] = None #No text implementation yet
204 self.tagProcess['image'] = None
205 self.tagProcess['metadata'] = None
206 self.tagProcess['defs'] = None
207 self.tagProcess['style'] = None
208 self.tagProcess['marker'] = None
209 self.tagProcess['desc'] = None
210 self.tagProcess['filter'] = None
211 self.tagProcess['linearGradient'] = None
212 self.tagProcess['radialGradient'] = None
213 self.tagProcess['pattern'] = None
214 self.tagProcess['title'] = None
215 self.tagProcess['animate'] = None
216 self.tagProcess['animateColor'] = None
217 self.tagProcess['animateTransform'] = None
218 self.tagProcess['set'] = None
219 self.tagProcess['script'] = None
222 self.tagProcess['namedview'] = None
224 self.tagProcess['SVGTestCase'] = None
227 f = open(filename, "r")
228 self._xml = ElementTree.parse(f)
229 self._recursiveCount = 0
230 self._processGTag(self._xml.getroot(), numpy.matrix(numpy.identity(3, numpy.float64)))
234 def _processGTag(self, tag, baseMatrix):
236 if e.get('transform') is None:
239 matrix = applyTransformString(baseMatrix, e.get('transform'))
240 tagName = e.tag[e.tag.find('}')+1:]
241 if not tagName in self.tagProcess:
242 print 'unknown tag: %s' % (tagName)
243 elif self.tagProcess[tagName] is not None:
244 self.tagProcess[tagName](e, matrix)
246 def _processUseTag(self, tag, baseMatrix):
247 if self._recursiveCount > 16:
249 self._recursiveCount += 1
250 id = tag.get('{http://www.w3.org/1999/xlink}href')
252 for e in self._xml.findall(".//*[@id='%s']" % (id[1:])):
253 if e.get('transform') is None:
256 matrix = applyTransformString(baseMatrix, e.get('transform'))
257 tagName = e.tag[e.tag.find('}')+1:]
258 if not tagName in self.tagProcess:
259 print 'unknown tag: %s' % (tagName)
260 elif self.tagProcess[tagName] is not None:
261 self.tagProcess[tagName](e, matrix)
262 self._recursiveCount -= 1
264 def _processLineTag(self, tag, matrix):
265 x1 = toFloat(tag.get('x1', '0'))
266 y1 = toFloat(tag.get('y1', '0'))
267 x2 = toFloat(tag.get('x2', '0'))
268 y2 = toFloat(tag.get('y2', '0'))
269 p = Path(x1, y1, matrix)
273 def _processPolylineTag(self, tag, matrix):
274 values = map(toFloat, re.split('[, \t]+', tag.get('points', '').strip()))
275 p = Path(values[0], values[1], matrix)
276 for n in xrange(2, len(values)-1, 2):
277 p.addLineTo(values[n], values[n+1])
280 def _processPolygonTag(self, tag, matrix):
281 values = map(toFloat, re.split('[, \t]+', tag.get('points', '').strip()))
282 p = Path(values[0], values[1], matrix)
283 for n in xrange(2, len(values)-1, 2):
284 p.addLineTo(values[n], values[n+1])
288 def _processCircleTag(self, tag, matrix):
289 cx = toFloat(tag.get('cx', '0'))
290 cy = toFloat(tag.get('cy', '0'))
291 r = toFloat(tag.get('r', '0'))
292 p = Path(cx-r, cy, matrix)
293 p.addArcTo(cx+r, cy, 0, r, r, False, False)
294 p.addArcTo(cx-r, cy, 0, r, r, False, False)
297 def _processEllipseTag(self, tag, matrix):
298 cx = toFloat(tag.get('cx', '0'))
299 cy = toFloat(tag.get('cy', '0'))
300 rx = toFloat(tag.get('rx', '0'))
301 ry = toFloat(tag.get('rx', '0'))
302 p = Path(cx-rx, cy, matrix)
303 p.addArcTo(cx+rx, cy, 0, rx, ry, False, False)
304 p.addArcTo(cx-rx, cy, 0, rx, ry, False, False)
307 def _processRectTag(self, tag, matrix):
308 x = toFloat(tag.get('x', '0'))
309 y = toFloat(tag.get('y', '0'))
310 width = toFloat(tag.get('width', '0'))
311 height = toFloat(tag.get('height', '0'))
312 if width <= 0 or height <= 0:
316 if rx is not None or ry is not None:
331 if rx > 0 and ry > 0:
332 p = Path(x+rx, y, matrix)
333 p.addLineTo(x+width-rx, y)
334 p.addArcTo(x+width,y+ry, 0, rx, ry, False, True)
335 p.addLineTo(x+width, y+height-ry)
336 p.addArcTo(x+width-rx,y+height, 0, rx, ry, False, True)
337 p.addLineTo(x+rx, y+height)
338 p.addArcTo(x,y+height-ry, 0, rx, ry, False, True)
340 p.addArcTo(x+rx,y, 0, rx, ry, False, True)
343 p = Path(x, y, matrix)
344 p.addLineTo(x,y+height)
345 p.addLineTo(x+width,y+height)
346 p.addLineTo(x+width,y)
350 def _processPathTag(self, tag, matrix):
351 pathString = tag.get('d', '').replace(',', ' ')
357 for command in re.findall('[a-df-zA-DF-Z][^a-df-zA-DF-Z]*', pathString):
358 params = re.split(' +', command[1:].strip())
359 if len(params) > 0 and params[0] == '':
361 if len(params) > 0 and params[-1] == '':
363 params = map(toFloat, params)
369 path = Path(x, y, matrix)
370 self.paths.append(path)
372 while len(params) > 1:
381 path = Path(x, y, matrix)
382 self.paths.append(path)
384 while len(params) > 1:
391 while len(params) > 1:
398 while len(params) > 1:
421 while len(params) > 6:
424 path.addArcTo(x, y, params[2], params[0], params[1], params[3] > 0, params[4] > 0)
428 while len(params) > 6:
431 path.addArcTo(x, y, params[2], params[0], params[1], params[3] > 0, params[4] > 0)
435 while len(params) > 5:
442 path.addCurveTo(x, y, c1x, c1y, c2x, c2y)
445 while len(params) > 5:
452 path.addCurveTo(x, y, c1x, c1y, c2x, c2y)
455 while len(params) > 3:
462 path.addCurveTo(x, y, c1x, c1y, c2x, c2y)
465 while len(params) > 3:
472 path.addCurveTo(x, y, c1x, c1y, c2x, c2y)
475 while len(params) > 3:
482 path.addCurveTo(x, y, c1x, c1y, c2x, c2y)
485 while len(params) > 3:
492 path.addCurveTo(x, y, c1x, c1y, c2x, c2y)
495 while len(params) > 1:
502 path.addCurveTo(x, y, c1x, c1y, c2x, c2y)
505 while len(params) > 1:
512 path.addCurveTo(x, y, c1x, c1y, c2x, c2y)
514 elif command == 'z' or command == 'Z':
516 x = path._startPoint.real
517 y = path._startPoint.imag
519 print 'Unknown path command:', command, params
522 if __name__ == '__main__':
523 for n in xrange(1, len(sys.argv)):
524 print 'File: %s' % (sys.argv[n])
525 svg = SVG(sys.argv[n])
527 f = open("test_export.html", "w")
529 f.write("<!DOCTYPE html><html><body>\n")
530 f.write("<svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\" style='width:%dpx;height:%dpx'>\n" % (1000, 1000))
531 f.write("<g fill-rule='evenodd' style=\"fill: gray; stroke:black;stroke-width:2\">\n")
532 f.write("<path d=\"")
533 for path in svg.paths:
534 points = path.getPoints()
535 f.write("M %f %f " % (points[0].real, points[0].imag))
536 for point in points[1:]:
537 f.write("L %f %f " % (point.real, point.imag))
541 f.write("<g style=\"fill: none; stroke:red;stroke-width:1\">\n")
542 f.write("<path d=\"")
543 for path in svg.paths:
544 f.write(path.getSVGPath())
549 f.write("</body></html>")