chiark / gitweb /
Add extra curve commands to the svg parse.
[cura.git] / Cura / util / svg.py
1 from __future__ import absolute_import
2 __copyright__ = "Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License"
3
4 import math
5 import re
6 import sys
7 import numpy
8 from xml.etree import ElementTree
9
10 def applyTransformString(matrix, transform):
11         s = transform.find('(')
12         e = transform.find(')')
13         if s < 0 or e < 0:
14                 print 'Unknown transform: %s' % (transform)
15                 return matrix
16         tag = transform[:s]
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)
36         return matrix
37
38 class Path(object):
39         LINE = 0
40         ARC = 1
41         CURVE = 2
42
43         def __init__(self, x, y, matrix):
44                 self._matrix = matrix
45                 self._relMatrix = numpy.matrix([[matrix[0,0],matrix[0,1]], [matrix[1,0],matrix[1,1]]])
46                 self._startPoint = complex(x, y)
47                 self._points = []
48
49         def addLineTo(self, x, y):
50                 self._points.append({'type': Path.LINE, 'p': complex(x, y)})
51
52         def addArcTo(self, x, y, rot, rx, ry, large, sweep):
53                 self._points.append({
54                         'type': Path.ARC,
55                         'p': complex(x, y),
56                         'rot': rot,
57                         'radius': complex(rx, ry),
58                         'large': large,
59                         'sweep': sweep
60                 })
61
62         def addCurveTo(self, x, y, cp1x, cp1y, cp2x, cp2y):
63                 self._points.append({
64                         'type': Path.CURVE,
65                         'p': complex(x, y),
66                         'cp1': complex(cp1x, cp1y),
67                         'cp2': complex(cp2x, cp2y)
68                 })
69
70         def closePath(self):
71                 self._points.append({'type': Path.LINE, 'p': self._startPoint})
72
73         def getPoints(self):
74                 pointList = [self._m(self._startPoint)]
75                 p1 = self._startPoint
76                 for p in self._points:
77                         if p['type'] == Path.LINE:
78                                 p1 = p['p']
79                                 pointList.append(self._m(p1))
80                         elif p['type'] == Path.ARC:
81                                 p2 = p['p']
82                                 rot = math.radians(p['rot'])
83                                 r = p['radius']
84
85                                 #http://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter
86                                 diff = (p1 - p2) / 2
87                                 p1alt = diff #TODO: apply rot
88                                 p2alt = -diff #TODO: apply rot
89                                 rx2 = r.real*r.real
90                                 ry2 = r.imag*r.imag
91                                 x1alt2 = p1alt.real*p1alt.real
92                                 y1alt2 = p1alt.imag*p1alt.imag
93
94                                 f = x1alt2 / rx2 + y1alt2 / ry2
95                                 if f >= 1.0:
96                                         r *= math.sqrt(f+0.000001)
97                                         rx2 = r.real*r.real
98                                         ry2 = r.imag*r.imag
99
100                                 f = math.sqrt((rx2*ry2 - rx2*y1alt2 - ry2*x1alt2) / (rx2*y1alt2+ry2*x1alt2))
101                                 if p['large'] == p['sweep']:
102                                         f = -f
103                                 cAlt = f * complex(r.real*p1alt.imag/r.imag, -r.imag*p1alt.real/r.real)
104
105                                 c = cAlt + (p1 + p2) / 2 #TODO: apply rot
106
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)
109
110                                 #if a1 > a2:
111                                 #       a2 += math.pi * 2
112                                 large = abs(a2 - a1) > math.pi
113                                 if large != p['large']:
114                                         if a1 < a2:
115                                                 a1 += math.pi * 2
116                                         else:
117                                                 a2 += math.pi * 2
118
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)))
121
122                                 pointList.append(self._m(p2))
123                                 p1 = p2
124                         elif p['type'] == Path.CURVE:
125                                 p1_ = self._m(p1)
126                                 p2 = self._m(p['p'])
127                                 cp1 = self._m(p['cp1'])
128                                 cp2 = self._m(p['cp2'])
129
130                                 for n in xrange(0, 10):
131                                         f = n / 10.0
132                                         g = 1.0-f
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)
135
136                                 pointList.append(p2)
137                                 p1 = p['p']
138
139                 return pointList
140
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:
146                                 p0 = self._m(p['p'])
147                                 ret += 'L %f %f' % (p0.real, p0.imag)
148                         elif p['type'] == Path.ARC:
149                                 p0 = self._m(p['p'])
150                                 radius = p['radius']
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:
153                                 p0 = self._m(p['p'])
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)
157
158                 return ret
159
160         def _m(self, p):
161                 tmp = numpy.matrix([p.real, p.imag, 1], numpy.float64) * self._matrix
162                 return complex(tmp[0,0], tmp[0,1])
163         def _r(self, p):
164                 tmp = numpy.matrix([p.real, p.imag], numpy.float64) * self._relMatrix
165                 return complex(tmp[0,0], tmp[0,1])
166
167 class SVG(object):
168         def __init__(self, filename):
169                 self.tagProcess = {}
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
188
189                 #From Inkscape
190                 self.tagProcess['namedview'] = None
191                 #From w3c testsuite
192                 self.tagProcess['SVGTestCase'] = None
193
194                 self.paths = []
195                 f = open(filename, "r")
196                 self._processGTag(ElementTree.parse(f).getroot(), numpy.matrix(numpy.identity(3, numpy.float64)))
197                 f.close()
198
199         def _processGTag(self, tag, baseMatrix):
200                 for e in tag:
201                         if e.get('transform') is None:
202                                 matrix = baseMatrix
203                         else:
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)
210
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)
217                 p.addLineTo(x2, y2)
218                 self.paths.append(p)
219
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])
225                 self.paths.append(p)
226
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])
232                 p.closePath()
233                 self.paths.append(p)
234
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)
242                 self.paths.append(p)
243
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)
252                 self.paths.append(p)
253
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:
260                         return
261                 rx = tag.get('rx')
262                 ry = tag.get('ry')
263                 if rx is not None or ry is not None:
264                         if ry is None:
265                                 ry = rx
266                         if rx is None:
267                                 rx = ry
268                         rx = float(rx)
269                         ry = float(ry)
270                         if rx > width / 2:
271                                 rx = width / 2
272                         if ry > height / 2:
273                                 ry = height / 2
274                 else:
275                         rx = 0.0
276                         ry = 0.0
277
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)
286                         p.addLineTo(x, y+ry)
287                         p.addArcTo(x+rx,y, 0, rx, ry, False, True)
288                         self.paths.append(p)
289                 else:
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)
294                         p.closePath()
295                         self.paths.append(p)
296
297         def _processPathTag(self, tag, matrix):
298                 pathString = tag.get('d', '').replace(',', ' ').replace('-', ' -')
299                 x = 0
300                 y = 0
301                 c2x = 0
302                 c2y = 0
303                 path = None
304                 for command in re.findall('[a-df-zA-DF-Z][^a-df-zA-DF-Z]*', pathString):
305                         params = re.split(' +', command[1:].strip())
306                         if len(params) > 0 and params[0] == '':
307                                 params = params[1:]
308                         if len(params) > 0 and params[-1] == '':
309                                 params = params[:-1]
310                         params = map(float, params)
311                         command = command[0]
312
313                         if command == 'm':
314                                 x += params[0]
315                                 y += params[1]
316                                 path = Path(x, y, matrix)
317                                 self.paths.append(path)
318                                 params = params[2:]
319                                 while len(params) > 1:
320                                         x += params[0]
321                                         y += params[1]
322                                         params = params[2:]
323                                         path.addLineTo(x, y)
324                                 c2x, c2y = x, y
325                         elif command == 'M':
326                                 x = params[0]
327                                 y = params[1]
328                                 path = Path(x, y, matrix)
329                                 self.paths.append(path)
330                                 params = params[2:]
331                                 while len(params) > 1:
332                                         x = params[0]
333                                         y = params[1]
334                                         params = params[2:]
335                                         path.addLineTo(x, y)
336                                 c2x, c2y = x, y
337                         elif command == 'l':
338                                 while len(params) > 1:
339                                         x += params[0]
340                                         y += params[1]
341                                         params = params[2:]
342                                         path.addLineTo(x, y)
343                                 c2x, c2y = x, y
344                         elif command == 'L':
345                                 while len(params) > 1:
346                                         x = params[0]
347                                         y = params[1]
348                                         params = params[2:]
349                                         path.addLineTo(x, y)
350                                 c2x, c2y = x, y
351                         elif command == 'h':
352                                 x += params[0]
353                                 path.addLineTo(x, y)
354                                 c2x, c2y = x, y
355                         elif command == 'H':
356                                 x = params[0]
357                                 path.addLineTo(x, y)
358                                 c2x, c2y = x, y
359                         elif command == 'v':
360                                 y += params[0]
361                                 path.addLineTo(x, y)
362                                 c2x, c2y = x, y
363                         elif command == 'V':
364                                 y = params[0]
365                                 path.addLineTo(x, y)
366                                 c2x, c2y = x, y
367                         elif command == 'a':
368                                 while len(params) > 6:
369                                         x += params[5]
370                                         y += params[6]
371                                         path.addArcTo(x, y, params[2], params[0], params[1], params[3] > 0, params[4] > 0)
372                                         params = params[7:]
373                                 c2x, c2y = x, y
374                         elif command == 'A':
375                                 while len(params) > 6:
376                                         x = params[5]
377                                         y = params[6]
378                                         path.addArcTo(x, y, params[2], params[0], params[1], params[3] > 0, params[4] > 0)
379                                         params = params[7:]
380                                 c2x, c2y = x, y
381                         elif command == 'c':
382                                 while len(params) > 5:
383                                         c1x = x + params[0]
384                                         c1y = y + params[1]
385                                         c2x = x + params[2]
386                                         c2y = y + params[3]
387                                         x += params[4]
388                                         y += params[5]
389                                         path.addCurveTo(x, y, c1x, c1y, c2x, c2y)
390                                         params = params[6:]
391                         elif command == 'C':
392                                 while len(params) > 5:
393                                         c1x = params[0]
394                                         c1y = params[1]
395                                         c2x = params[2]
396                                         c2y = params[3]
397                                         x = params[4]
398                                         y = params[5]
399                                         path.addCurveTo(x, y, c1x, c1y, c2x, c2y)
400                                         params = params[6:]
401                         elif command == 's':
402                                 while len(params) > 3:
403                                         c1x = x - (c2x - x)
404                                         c1y = y - (c2y - y)
405                                         c2x = x + params[0]
406                                         c2y = y + params[1]
407                                         x += params[2]
408                                         y += params[3]
409                                         path.addCurveTo(x, y, c1x, c1y, c2x, c2y)
410                                         params = params[4:]
411                         elif command == 'S':
412                                 while len(params) > 3:
413                                         c1x = x - (c2x - x)
414                                         c1y = y - (c2y - y)
415                                         c2x = params[0]
416                                         c2y = params[1]
417                                         x = params[2]
418                                         y = params[3]
419                                         path.addCurveTo(x, y, c1x, c1y, c2x, c2y)
420                                         params = params[4:]
421                         elif command == 'q':
422                                 while len(params) > 3:
423                                         c1x = x + params[0]
424                                         c1y = y + params[1]
425                                         c2x = c1x
426                                         c2y = c1y
427                                         x += params[2]
428                                         y += params[3]
429                                         path.addCurveTo(x, y, c1x, c1y, c2x, c2y)
430                                         params = params[4:]
431                         elif command == 'Q':
432                                 while len(params) > 3:
433                                         c1x = params[0]
434                                         c1y = params[1]
435                                         c2x = c1x
436                                         c2y = c1y
437                                         x = params[2]
438                                         y = params[3]
439                                         path.addCurveTo(x, y, c1x, c1y, c2x, c2y)
440                                         params = params[4:]
441                         elif command == 't':
442                                 while len(params) > 1:
443                                         c1x = x - (c2x - x)
444                                         c1y = y - (c2y - y)
445                                         c2x = c1x
446                                         c2y = c1y
447                                         x += params[0]
448                                         y += params[1]
449                                         path.addCurveTo(x, y, c1x, c1y, c2x, c2y)
450                                         params = params[2:]
451                         elif command == 'T':
452                                 while len(params) > 1:
453                                         c1x = x - (c2x - x)
454                                         c1y = y - (c2y - y)
455                                         c2x = c1x
456                                         c2y = c1y
457                                         x = params[0]
458                                         y = params[1]
459                                         path.addCurveTo(x, y, c1x, c1y, c2x, c2y)
460                                         params = params[2:]
461                         elif command == 'z' or command == 'Z':
462                                 path.closePath()
463                                 x = path._startPoint.real
464                                 y = path._startPoint.imag
465                         else:
466                                 print 'Unknown path command:', command, params
467
468
469 if __name__ == '__main__':
470         for n in xrange(1, len(sys.argv)):
471                 print 'File: %s' % (sys.argv[n])
472                 svg = SVG(sys.argv[n])
473
474         f = open("test_export.html", "w")
475
476         f.write("<!DOCTYPE html><html><body>\n")
477         f.write("<svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\" style='width:%dpx;height:%dpx'>\n" % (1000, 1000))
478         f.write("<g fill-rule='evenodd' style=\"fill: gray; stroke:black;stroke-width:2\">\n")
479         f.write("<path d=\"")
480         for path in svg.paths:
481                 points = path.getPoints()
482                 f.write("M %f %f " % (points[0].real, points[0].imag))
483                 for point in points[1:]:
484                         f.write("L %f %f " % (point.real, point.imag))
485         f.write("\"/>")
486         f.write("</g>\n")
487
488         f.write("<g style=\"fill: none; stroke:red;stroke-width:1\">\n")
489         f.write("<path d=\"")
490         for path in svg.paths:
491                 f.write(path.getSVGPath())
492         f.write("\"/>")
493         f.write("</g>\n")
494
495         f.write("</svg>\n")
496         f.write("</body></html>")
497         f.close()
498