chiark / gitweb /
Add back the ultimaker platform, and made the platform mesh simpler.
[cura.git] / Cura / slice / cura_sf / fabmetheus_utilities / svg_reader.py
1 """
2 Svg reader.
3
4 """
5
6 from __future__ import absolute_import
7
8 from fabmetheus_utilities.geometry.solids import triangle_mesh
9 from fabmetheus_utilities.xml_simple_reader import DocumentNode
10 from fabmetheus_utilities import archive
11 from fabmetheus_utilities import euclidean
12 from fabmetheus_utilities import gcodec
13 from fabmetheus_utilities import intercircle
14 from fabmetheus_utilities import settings
15 from fabmetheus_utilities import svg_writer
16 import math
17 import os
18 import sys
19 import traceback
20
21
22 __author__ = 'Enrique Perez (perez_enrique@yahoo.com)'
23 __credits__ = 'Nophead <http://hydraraptor.blogspot.com/>\nArt of Illusion <http://www.artofillusion.org/>'
24 __date__ = '$Date: 2008/21/04 $'
25 __license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html'
26
27
28 globalNumberOfCornerPoints = 11
29 globalNumberOfBezierPoints = globalNumberOfCornerPoints + globalNumberOfCornerPoints
30 globalNumberOfCirclePoints = 4 * globalNumberOfCornerPoints
31
32
33 def addFunctionsToDictionary( dictionary, functions, prefix ):
34         "Add functions to dictionary."
35         for function in functions:
36                 dictionary[ function.__name__[ len( prefix ) : ] ] = function
37
38 def getArcComplexes(begin, end, largeArcFlag, radius, sweepFlag, xAxisRotation):
39         'Get the arc complexes, procedure at http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes'
40         if begin == end:
41                 print('Warning, begin equals end in getArcComplexes in svgReader')
42                 print(begin)
43                 print(end)
44                 return []
45         if radius.imag < 0.0:
46                 print('Warning, radius.imag is less than zero in getArcComplexes in svgReader')
47                 print(radius)
48                 radius = complex(radius.real, abs(radius.imag))
49         if radius.real < 0.0:
50                 print('Warning, radius.real is less than zero in getArcComplexes in svgReader')
51                 print(radius)
52                 radius = complex(abs(radius.real), radius.imag)
53         if radius.imag <= 0.0:
54                 print('Warning, radius.imag is too small for getArcComplexes in svgReader')
55                 print(radius)
56                 return [end]
57         if radius.real <= 0.0:
58                 print('Warning, radius.real is too small for getArcComplexes in svgReader')
59                 print(radius)
60                 return [end]
61         xAxisRotationComplex = euclidean.getWiddershinsUnitPolar(xAxisRotation)
62         reverseXAxisRotationComplex = complex(xAxisRotationComplex.real, -xAxisRotationComplex.imag)
63         beginRotated = begin * reverseXAxisRotationComplex
64         endRotated = end * reverseXAxisRotationComplex
65         beginTransformed = complex(beginRotated.real / radius.real, beginRotated.imag / radius.imag)
66         endTransformed = complex(endRotated.real / radius.real, endRotated.imag / radius.imag)
67         midpointTransformed = 0.5 * (beginTransformed + endTransformed)
68         midMinusBeginTransformed = midpointTransformed - beginTransformed
69         midMinusBeginTransformedLength = abs(midMinusBeginTransformed)
70         if midMinusBeginTransformedLength > 1.0:
71                 print('The ellipse radius is too small for getArcComplexes in svgReader.')
72                 print('So the ellipse will be scaled to fit, according to the formulas in "Step 3: Ensure radii are large enough" of:')
73                 print('http://www.w3.org/TR/SVG/implnote.html#ArcCorrectionOutOfRangeRadii')
74                 print('')
75                 radius *= midMinusBeginTransformedLength
76                 beginTransformed /= midMinusBeginTransformedLength
77                 endTransformed /= midMinusBeginTransformedLength
78                 midpointTransformed /= midMinusBeginTransformedLength
79                 midMinusBeginTransformed /= midMinusBeginTransformedLength
80                 midMinusBeginTransformedLength = 1.0
81         midWiddershinsTransformed = complex(-midMinusBeginTransformed.imag, midMinusBeginTransformed.real)
82         midWiddershinsLengthSquared = 1.0 - midMinusBeginTransformedLength * midMinusBeginTransformedLength
83         if midWiddershinsLengthSquared < 0.0:
84                 midWiddershinsLengthSquared = 0.0
85         midWiddershinsLength = math.sqrt(midWiddershinsLengthSquared)
86         midWiddershinsTransformed *= midWiddershinsLength / abs(midWiddershinsTransformed)
87         centerTransformed = midpointTransformed
88         if largeArcFlag == sweepFlag:
89                 centerTransformed -= midWiddershinsTransformed
90         else:
91                 centerTransformed += midWiddershinsTransformed
92         beginMinusCenterTransformed = beginTransformed - centerTransformed
93         beginMinusCenterTransformedLength = abs(beginMinusCenterTransformed)
94         if beginMinusCenterTransformedLength <= 0.0:
95                 return end
96         beginAngle = math.atan2(beginMinusCenterTransformed.imag, beginMinusCenterTransformed.real)
97         endMinusCenterTransformed = endTransformed - centerTransformed
98         angleDifference = euclidean.getAngleDifferenceByComplex(endMinusCenterTransformed, beginMinusCenterTransformed)
99         if sweepFlag:
100                 if angleDifference < 0.0:
101                         angleDifference += 2.0 * math.pi
102         else:
103                 if angleDifference > 0.0:
104                         angleDifference -= 2.0 * math.pi
105         global globalSideAngle
106         sides = int(math.ceil(abs(angleDifference) / globalSideAngle))
107         sideAngle = angleDifference / float(sides)
108         arcComplexes = []
109         center = complex(centerTransformed.real * radius.real, centerTransformed.imag * radius.imag) * xAxisRotationComplex
110         for side in xrange(1, sides):
111                 unitPolar = euclidean.getWiddershinsUnitPolar(beginAngle + float(side) * sideAngle)
112                 circumferential = complex(unitPolar.real * radius.real, unitPolar.imag * radius.imag) * beginMinusCenterTransformedLength
113                 point = center + circumferential * xAxisRotationComplex
114                 arcComplexes.append(point)
115         arcComplexes.append(end)
116         return arcComplexes
117
118 def getChainMatrixSVG(elementNode, matrixSVG):
119         "Get chain matrixSVG by svgElement."
120         matrixSVG = matrixSVG.getOtherTimesSelf(getMatrixSVG(elementNode).tricomplex)
121         if elementNode.parentNode != None:
122                 matrixSVG = getChainMatrixSVG(elementNode.parentNode, matrixSVG)
123         return matrixSVG
124
125 def getChainMatrixSVGIfNecessary(elementNode, yAxisPointingUpward):
126         "Get chain matrixSVG by svgElement and yAxisPointingUpward."
127         matrixSVG = MatrixSVG()
128         if yAxisPointingUpward:
129                 return matrixSVG
130         return getChainMatrixSVG(elementNode, matrixSVG)
131
132 def getCubicPoint( along, begin, controlPoints, end ):
133         'Get the cubic point.'
134         segmentBegin = getQuadraticPoint( along, begin, controlPoints[0], controlPoints[1] )
135         segmentEnd = getQuadraticPoint( along, controlPoints[0], controlPoints[1], end )
136         return ( 1.0 - along ) * segmentBegin + along * segmentEnd
137
138 def getCubicPoints( begin, controlPoints, end, numberOfBezierPoints=globalNumberOfBezierPoints):
139         'Get the cubic points.'
140         bezierPortion = 1.0 / float(numberOfBezierPoints)
141         cubicPoints = []
142         for bezierIndex in xrange( 1, numberOfBezierPoints + 1 ):
143                 cubicPoints.append(getCubicPoint(bezierPortion * bezierIndex, begin, controlPoints, end))
144         return cubicPoints
145
146 def getFontReader(fontFamily):
147         'Get the font reader for the fontFamily.'
148         fontLower = fontFamily.lower().replace(' ', '_')
149         global globalFontReaderDictionary
150         if fontLower in globalFontReaderDictionary:
151                 return globalFontReaderDictionary[fontLower]
152         global globalFontFileNames
153         if globalFontFileNames == None:
154                 globalFontFileNames = archive.getFileNamesByFilePaths(archive.getFilePathsByDirectory(getFontsDirectoryPath()))
155         if fontLower not in globalFontFileNames:
156                 print('Warning, the %s font was not found in the fabmetheus_utilities/fonts folder, so Gentium Basic Regular will be substituted.' % fontFamily)
157                 print('The available fonts are:')
158                 globalFontFileNames.sort()
159                 print(globalFontFileNames)
160                 print('')
161                 fontLower = 'gentium_basic_regular'
162         fontReader = FontReader(fontLower)
163         globalFontReaderDictionary[fontLower] = fontReader
164         return fontReader
165
166 def getFontsDirectoryPath():
167         "Get the fonts directory path."
168         return archive.getFabmetheusUtilitiesPath('fonts')
169
170 def getLabelString(dictionary):
171         "Get the label string for the dictionary."
172         for key in dictionary:
173                 labelIndex = key.find('label')
174                 if labelIndex >= 0:
175                         return dictionary[key]
176         return ''
177
178 def getMatrixSVG(elementNode):
179         "Get matrixSVG by svgElement."
180         matrixSVG = MatrixSVG()
181         if 'transform' not in elementNode.attributes:
182                 return matrixSVG
183         transformWords = []
184         for transformWord in elementNode.attributes['transform'].replace(')', '(').split('('):
185                 transformWordStrip = transformWord.strip()
186                 if transformWordStrip != '': # workaround for split(character) bug which leaves an extra empty element
187                         transformWords.append(transformWordStrip)
188         global globalGetTricomplexDictionary
189         getTricomplexDictionaryKeys = globalGetTricomplexDictionary.keys()
190         for transformWordIndex, transformWord in enumerate(transformWords):
191                 if transformWord in getTricomplexDictionaryKeys:
192                         transformString = transformWords[transformWordIndex + 1].replace(',', ' ')
193                         matrixSVG = matrixSVG.getSelfTimesOther(globalGetTricomplexDictionary[ transformWord ](transformString.split()))
194         return matrixSVG
195
196 def getQuadraticPoint( along, begin, controlPoint, end ):
197         'Get the quadratic point.'
198         oneMinusAlong = 1.0 - along
199         segmentBegin = oneMinusAlong * begin + along * controlPoint
200         segmentEnd = oneMinusAlong * controlPoint + along * end
201         return oneMinusAlong * segmentBegin + along * segmentEnd
202
203 def getQuadraticPoints(begin, controlPoint, end, numberOfBezierPoints=globalNumberOfBezierPoints):
204         'Get the quadratic points.'
205         bezierPortion = 1.0 / float(numberOfBezierPoints)
206         quadraticPoints = []
207         for bezierIndex in xrange(1, numberOfBezierPoints + 1):
208                 quadraticPoints.append(getQuadraticPoint(bezierPortion * bezierIndex, begin, controlPoint, end))
209         return quadraticPoints
210
211 def getRightStripAlphabetPercent(word):
212         "Get word with alphabet characters and the percent sign stripped from the right."
213         word = word.strip()
214         for characterIndex in xrange(len(word) - 1, -1, -1):
215                 character = word[characterIndex]
216                 if not character.isalpha() and not character == '%':
217                         return float(word[: characterIndex + 1])
218         return None
219
220 def getRightStripMinusSplit(lineString):
221         "Get string with spaces after the minus sign stripped."
222         oldLineStringLength = -1
223         while oldLineStringLength < len(lineString):
224                 oldLineStringLength = len(lineString)
225                 lineString = lineString.replace('- ', '-')
226         return lineString.split()
227
228 def getStrokeRadius(elementNode):
229         "Get the stroke radius."
230         return 0.5 * getRightStripAlphabetPercent(getStyleValue('1.0', elementNode, 'stroke-width'))
231
232 def getStyleValue(defaultValue, elementNode, key):
233         "Get the stroke value string."
234         if 'style' in elementNode.attributes:
235                 line = elementNode.attributes['style']
236                 strokeIndex = line.find(key)
237                 if strokeIndex > -1:
238                         words = line[strokeIndex :].replace(':', ' ').replace(';', ' ').split()
239                         if len(words) > 1:
240                                 return words[1]
241         if key in elementNode.attributes:
242                 return elementNode.attributes[key]
243         if elementNode.parentNode == None:
244                 return defaultValue
245         return getStyleValue(defaultValue, elementNode.parentNode, key)
246
247 def getTextComplexLoops(fontFamily, fontSize, text, yAxisPointingUpward=True):
248         "Get text as complex loops."
249         textComplexLoops = []
250         fontReader = getFontReader(fontFamily)
251         horizontalAdvanceX = 0.0
252         for character in text:
253                 glyph = fontReader.getGlyph(character, yAxisPointingUpward)
254                 textComplexLoops += glyph.getSizedAdvancedLoops(fontSize, horizontalAdvanceX, yAxisPointingUpward)
255                 horizontalAdvanceX += glyph.horizontalAdvanceX
256         return textComplexLoops
257
258 def getTransformedFillOutline(elementNode, loop, yAxisPointingUpward):
259         "Get the loops if fill is on, otherwise get the outlines."
260         fillOutlineLoops = None
261         if getStyleValue('none', elementNode, 'fill').lower() == 'none':
262                 fillOutlineLoops = intercircle.getAroundsFromLoop(loop, getStrokeRadius(elementNode))
263         else:
264                 fillOutlineLoops = [loop]
265         return getChainMatrixSVGIfNecessary(elementNode, yAxisPointingUpward).getTransformedPaths(fillOutlineLoops)
266
267 def getTransformedOutlineByPath(elementNode, path, yAxisPointingUpward):
268         "Get the outline from the path."
269         aroundsFromPath = intercircle.getAroundsFromPath(path, getStrokeRadius(elementNode))
270         return getChainMatrixSVGIfNecessary(elementNode, yAxisPointingUpward).getTransformedPaths(aroundsFromPath)
271
272 def getTransformedOutlineByPaths(elementNode, paths, yAxisPointingUpward):
273         "Get the outline from the paths."
274         aroundsFromPaths = intercircle.getAroundsFromPaths(paths, getStrokeRadius(elementNode))
275         return getChainMatrixSVGIfNecessary(elementNode, yAxisPointingUpward).getTransformedPaths(aroundsFromPaths)
276
277 def getTricomplexmatrix(transformWords):
278         "Get matrixSVG by transformWords."
279         tricomplex = [euclidean.getComplexByWords(transformWords)]
280         tricomplex.append(euclidean.getComplexByWords(transformWords, 2))
281         tricomplex.append(euclidean.getComplexByWords(transformWords, 4))
282         return tricomplex
283
284 def getTricomplexrotate(transformWords):
285         "Get matrixSVG by transformWords."
286         rotate = euclidean.getWiddershinsUnitPolar(math.radians(float(transformWords[0])))
287         return [rotate, complex(-rotate.imag,rotate.real), complex()]
288
289 def getTricomplexscale(transformWords):
290         "Get matrixSVG by transformWords."
291         scale = euclidean.getComplexByWords(transformWords)
292         return [complex(scale.real,0.0), complex(0.0,scale.imag), complex()]
293
294 def getTricomplexskewX(transformWords):
295         "Get matrixSVG by transformWords."
296         skewX = math.tan(math.radians(float(transformWords[0])))
297         return [complex(1.0, 0.0), complex(skewX, 1.0), complex()]
298
299 def getTricomplexskewY(transformWords):
300         "Get matrixSVG by transformWords."
301         skewY = math.tan(math.radians(float(transformWords[0])))
302         return [complex(1.0, skewY), complex(0.0, 1.0), complex()]
303
304 def getTricomplexTimesColumn(firstTricomplex, otherColumn):
305         "Get this matrix multiplied by the otherColumn."
306         dotProductX = firstTricomplex[0].real * otherColumn.real + firstTricomplex[1].real * otherColumn.imag
307         dotProductY = firstTricomplex[0].imag * otherColumn.real + firstTricomplex[1].imag * otherColumn.imag
308         return complex(dotProductX, dotProductY)
309
310 def getTricomplexTimesOther(firstTricomplex, otherTricomplex):
311         "Get the first tricomplex multiplied by the other tricomplex."
312         #A down, B right from http://en.wikipedia.org/wiki/Matrix_multiplication
313         tricomplexTimesOther = [getTricomplexTimesColumn(firstTricomplex, otherTricomplex[0])]
314         tricomplexTimesOther.append(getTricomplexTimesColumn(firstTricomplex, otherTricomplex[1]))
315         tricomplexTimesOther.append(getTricomplexTimesColumn(firstTricomplex, otherTricomplex[2]) + firstTricomplex[2])
316         return tricomplexTimesOther
317
318 def getTricomplextranslate(transformWords):
319         "Get matrixSVG by transformWords."
320         translate = euclidean.getComplexByWords(transformWords)
321         return [complex(1.0, 0.0), complex(0.0, 1.0), translate]
322
323 def processSVGElementcircle( elementNode, svgReader ):
324         "Process elementNode by svgReader."
325         attributes = elementNode.attributes
326         center = euclidean.getComplexDefaultByDictionaryKeys( complex(), attributes, 'cx', 'cy')
327         radius = euclidean.getFloatDefaultByDictionary( 0.0, attributes, 'r')
328         if radius == 0.0:
329                 print('Warning, in processSVGElementcircle in svgReader radius is zero in:')
330                 print(attributes)
331                 return
332         global globalNumberOfCirclePoints
333         global globalSideAngle
334         loop = []
335         loopLayer = svgReader.getLoopLayer()
336         for side in xrange( globalNumberOfCirclePoints ):
337                 unitPolar = euclidean.getWiddershinsUnitPolar( float(side) * globalSideAngle )
338                 loop.append( center + radius * unitPolar )
339         loopLayer.loops += getTransformedFillOutline(elementNode, loop, svgReader.yAxisPointingUpward)
340
341 def processSVGElementellipse( elementNode, svgReader ):
342         "Process elementNode by svgReader."
343         attributes = elementNode.attributes
344         center = euclidean.getComplexDefaultByDictionaryKeys( complex(), attributes, 'cx', 'cy')
345         radius = euclidean.getComplexDefaultByDictionaryKeys( complex(), attributes, 'rx', 'ry')
346         if radius.real == 0.0 or radius.imag == 0.0:
347                 print('Warning, in processSVGElementellipse in svgReader radius is zero in:')
348                 print(attributes)
349                 return
350         global globalNumberOfCirclePoints
351         global globalSideAngle
352         loop = []
353         loopLayer = svgReader.getLoopLayer()
354         for side in xrange( globalNumberOfCirclePoints ):
355                 unitPolar = euclidean.getWiddershinsUnitPolar( float(side) * globalSideAngle )
356                 loop.append( center + complex( unitPolar.real * radius.real, unitPolar.imag * radius.imag ) )
357         loopLayer.loops += getTransformedFillOutline(elementNode, loop, svgReader.yAxisPointingUpward)
358
359 def processSVGElementg(elementNode, svgReader):
360         'Process elementNode by svgReader.'
361         if 'id' not in elementNode.attributes:
362                 return
363         idString = elementNode.attributes['id']
364         if 'beginningOfControlSection' in elementNode.attributes:
365                 if elementNode.attributes['beginningOfControlSection'].lower()[: 1] == 't':
366                         svgReader.stopProcessing = True
367                 return
368         idStringLower = idString.lower()
369         zIndex = idStringLower.find('z:')
370         if zIndex < 0:
371                 idStringLower = getLabelString(elementNode.attributes)
372                 zIndex = idStringLower.find('z:')
373         if zIndex < 0:
374                 return
375         floatFromValue = euclidean.getFloatFromValue(idStringLower[zIndex + len('z:') :].strip())
376         if floatFromValue != None:
377                 svgReader.z = floatFromValue
378
379 def processSVGElementline(elementNode, svgReader):
380         "Process elementNode by svgReader."
381         begin = euclidean.getComplexDefaultByDictionaryKeys(complex(), elementNode.attributes, 'x1', 'y1')
382         end = euclidean.getComplexDefaultByDictionaryKeys(complex(), elementNode.attributes, 'x2', 'y2')
383         loopLayer = svgReader.getLoopLayer()
384         loopLayer.loops += getTransformedOutlineByPath(elementNode, [begin, end], svgReader.yAxisPointingUpward)
385
386 def processSVGElementpath( elementNode, svgReader ):
387         "Process elementNode by svgReader."
388         if 'd' not in elementNode.attributes:
389                 print('Warning, in processSVGElementpath in svgReader can not get a value for d in:')
390                 print(elementNode.attributes)
391                 return
392         loopLayer = svgReader.getLoopLayer()
393         PathReader(elementNode, loopLayer.loops, svgReader.yAxisPointingUpward)
394
395 def processSVGElementpolygon( elementNode, svgReader ):
396         "Process elementNode by svgReader."
397         if 'points' not in elementNode.attributes:
398                 print('Warning, in processSVGElementpolygon in svgReader can not get a value for d in:')
399                 print(elementNode.attributes)
400                 return
401         loopLayer = svgReader.getLoopLayer()
402         words = getRightStripMinusSplit(elementNode.attributes['points'].replace(',', ' '))
403         loop = []
404         for wordIndex in xrange( 0, len(words), 2 ):
405                 loop.append(euclidean.getComplexByWords(words[wordIndex :]))
406         loopLayer.loops += getTransformedFillOutline(elementNode, loop, svgReader.yAxisPointingUpward)
407
408 def processSVGElementpolyline(elementNode, svgReader):
409         "Process elementNode by svgReader."
410         if 'points' not in elementNode.attributes:
411                 print('Warning, in processSVGElementpolyline in svgReader can not get a value for d in:')
412                 print(elementNode.attributes)
413                 return
414         loopLayer = svgReader.getLoopLayer()
415         words = getRightStripMinusSplit(elementNode.attributes['points'].replace(',', ' '))
416         path = []
417         for wordIndex in xrange(0, len(words), 2):
418                 path.append(euclidean.getComplexByWords(words[wordIndex :]))
419         loopLayer.loops += getTransformedOutlineByPath(elementNode, path, svgReader.yAxisPointingUpward)
420
421 def processSVGElementrect( elementNode, svgReader ):
422         "Process elementNode by svgReader."
423         attributes = elementNode.attributes
424         height = euclidean.getFloatDefaultByDictionary( 0.0, attributes, 'height')
425         if height == 0.0:
426                 print('Warning, in processSVGElementrect in svgReader height is zero in:')
427                 print(attributes)
428                 return
429         width = euclidean.getFloatDefaultByDictionary( 0.0, attributes, 'width')
430         if width == 0.0:
431                 print('Warning, in processSVGElementrect in svgReader width is zero in:')
432                 print(attributes)
433                 return
434         center = euclidean.getComplexDefaultByDictionaryKeys(complex(), attributes, 'x', 'y')
435         inradius = 0.5 * complex( width, height )
436         cornerRadius = euclidean.getComplexDefaultByDictionaryKeys( complex(), attributes, 'rx', 'ry')
437         loopLayer = svgReader.getLoopLayer()
438         if cornerRadius.real == 0.0 and cornerRadius.imag == 0.0:
439                 inradiusMinusX = complex( - inradius.real, inradius.imag )
440                 loop = [center + inradius, center + inradiusMinusX, center - inradius, center - inradiusMinusX]
441                 loopLayer.loops += getTransformedFillOutline(elementNode, loop, svgReader.yAxisPointingUpward)
442                 return
443         if cornerRadius.real == 0.0:
444                 cornerRadius = complex( cornerRadius.imag, cornerRadius.imag )
445         elif cornerRadius.imag == 0.0:
446                 cornerRadius = complex( cornerRadius.real, cornerRadius.real )
447         cornerRadius = complex( min( cornerRadius.real, inradius.real ), min( cornerRadius.imag, inradius.imag ) )
448         ellipsePath = [ complex( cornerRadius.real, 0.0 ) ]
449         inradiusMinusCorner = inradius - cornerRadius
450         loop = []
451         global globalNumberOfCornerPoints
452         global globalSideAngle
453         for side in xrange( 1, globalNumberOfCornerPoints ):
454                 unitPolar = euclidean.getWiddershinsUnitPolar( float(side) * globalSideAngle )
455                 ellipsePath.append( complex( unitPolar.real * cornerRadius.real, unitPolar.imag * cornerRadius.imag ) )
456         ellipsePath.append( complex( 0.0, cornerRadius.imag ) )
457         cornerPoints = []
458         for point in ellipsePath:
459                 cornerPoints.append( point + inradiusMinusCorner )
460         cornerPointsReversed = cornerPoints[: : -1]
461         for cornerPoint in cornerPoints:
462                 loop.append( center + cornerPoint )
463         for cornerPoint in cornerPointsReversed:
464                 loop.append( center + complex( - cornerPoint.real, cornerPoint.imag ) )
465         for cornerPoint in cornerPoints:
466                 loop.append( center - cornerPoint )
467         for cornerPoint in cornerPointsReversed:
468                 loop.append( center + complex( cornerPoint.real, - cornerPoint.imag ) )
469         loop = euclidean.getLoopWithoutCloseSequentialPoints( 0.0001 * abs(inradius), loop )
470         loopLayer.loops += getTransformedFillOutline(elementNode, loop, svgReader.yAxisPointingUpward)
471
472 def processSVGElementtext(elementNode, svgReader):
473         "Process elementNode by svgReader."
474         if svgReader.yAxisPointingUpward:
475                 return
476         fontFamily = getStyleValue('Gentium Basic Regular', elementNode, 'font-family')
477         fontSize = getRightStripAlphabetPercent(getStyleValue('12.0', elementNode, 'font-size'))
478         matrixSVG = getChainMatrixSVGIfNecessary(elementNode, svgReader.yAxisPointingUpward)
479         loopLayer = svgReader.getLoopLayer()
480         translate = euclidean.getComplexDefaultByDictionaryKeys(complex(), elementNode.attributes, 'x', 'y')
481         for textComplexLoop in getTextComplexLoops(fontFamily, fontSize, elementNode.getTextContent(), svgReader.yAxisPointingUpward):
482                 translatedLoop = []
483                 for textComplexPoint in textComplexLoop:
484                         translatedLoop.append(textComplexPoint + translate )
485                 loopLayer.loops.append(matrixSVG.getTransformedPath(translatedLoop))
486
487
488 class FontReader(object):
489         "Class to read a font in the fonts folder."
490         def __init__(self, fontFamily):
491                 "Initialize."
492                 self.fontFamily = fontFamily
493                 self.glyphDictionary = {}
494                 self.glyphElementNodeDictionary = {}
495                 self.missingGlyph = None
496                 fileName = os.path.join(getFontsDirectoryPath(), fontFamily + '.svg')
497                 documentElement = DocumentNode(fileName, archive.getFileText(fileName)).getDocumentElement()
498                 self.fontElementNode = documentElement.getFirstChildByLocalName('defs').getFirstChildByLocalName('font')
499                 self.fontFaceElementNode = self.fontElementNode.getFirstChildByLocalName('font-face')
500                 self.unitsPerEM = float(self.fontFaceElementNode.attributes['units-per-em'])
501                 glyphElementNodes = self.fontElementNode.getChildElementsByLocalName('glyph')
502                 for glyphElementNode in glyphElementNodes:
503                         self.glyphElementNodeDictionary[glyphElementNode.attributes['unicode']] = glyphElementNode
504
505         def getGlyph(self, character, yAxisPointingUpward):
506                 "Get the glyph for the character."
507                 if character not in self.glyphElementNodeDictionary:
508                         if self.missingGlyph == None:
509                                 missingGlyphElementNode = self.fontElementNode.getFirstChildByLocalName('missing-glyph')
510                                 self.missingGlyph = Glyph(missingGlyphElementNode, self.unitsPerEM, yAxisPointingUpward)
511                         return self.missingGlyph
512                 if character not in self.glyphDictionary:
513                         self.glyphDictionary[character] = Glyph(self.glyphElementNodeDictionary[character], self.unitsPerEM, yAxisPointingUpward)
514                 return self.glyphDictionary[character]
515
516
517 class Glyph(object):
518         "Class to handle a glyph."
519         def __init__(self, elementNode, unitsPerEM, yAxisPointingUpward):
520                 "Initialize."
521                 self.horizontalAdvanceX = float(elementNode.attributes['horiz-adv-x'])
522                 self.loops = []
523                 self.unitsPerEM = unitsPerEM
524                 elementNode.attributes['fill'] = ''
525                 if 'd' not in elementNode.attributes:
526                         return
527                 PathReader(elementNode, self.loops, yAxisPointingUpward)
528
529         def getSizedAdvancedLoops(self, fontSize, horizontalAdvanceX, yAxisPointingUpward=True):
530                 "Get loops for font size, advanced horizontally."
531                 multiplierX = fontSize / self.unitsPerEM
532                 multiplierY = multiplierX
533                 if not yAxisPointingUpward:
534                         multiplierY = -multiplierY
535                 sizedLoops = []
536                 for loop in self.loops:
537                         sizedLoop = []
538                         sizedLoops.append(sizedLoop)
539                         for point in loop:
540                                 sizedLoop.append( complex(multiplierX * (point.real + horizontalAdvanceX), multiplierY * point.imag))
541                 return sizedLoops
542
543
544 class MatrixSVG(object):
545         "Two by three svg matrix."
546         def __init__(self, tricomplex=None):
547                 "Initialize."
548                 self.tricomplex = tricomplex
549
550         def __repr__(self):
551                 "Get the string representation of this two by three svg matrix."
552                 return str(self.tricomplex)
553
554         def getOtherTimesSelf(self, otherTricomplex):
555                 "Get the other matrix multiplied by this matrix."
556                 if otherTricomplex == None:
557                         return MatrixSVG(self.tricomplex)
558                 if self.tricomplex == None:
559                         return MatrixSVG(otherTricomplex)
560                 return MatrixSVG(getTricomplexTimesOther(otherTricomplex, self.tricomplex))
561
562         def getSelfTimesOther(self, otherTricomplex):
563                 "Get this matrix multiplied by the other matrix."
564                 if otherTricomplex == None:
565                         return MatrixSVG(self.tricomplex)
566                 if self.tricomplex == None:
567                         return MatrixSVG(otherTricomplex)
568                 return MatrixSVG(getTricomplexTimesOther(self.tricomplex, otherTricomplex))
569
570         def getTransformedPath(self, path):
571                 "Get transformed path."
572                 if self.tricomplex == None:
573                         return path
574                 complexX = self.tricomplex[0]
575                 complexY = self.tricomplex[1]
576                 complexTranslation = self.tricomplex[2]
577                 transformedPath = []
578                 for point in path:
579                         x = complexX.real * point.real + complexY.real * point.imag
580                         y = complexX.imag * point.real + complexY.imag * point.imag
581                         transformedPath.append(complex(x, y) + complexTranslation)
582                 return transformedPath
583
584         def getTransformedPaths(self, paths):
585                 "Get transformed paths."
586                 if self.tricomplex == None:
587                         return paths
588                 transformedPaths = []
589                 for path in paths:
590                         transformedPaths.append(self.getTransformedPath(path))
591                 return transformedPaths
592
593
594 class PathReader(object):
595         "Class to read svg path."
596         def __init__(self, elementNode, loops, yAxisPointingUpward):
597                 "Add to path string to loops."
598                 self.controlPoints = None
599                 self.elementNode = elementNode
600                 self.loops = loops
601                 self.oldPoint = None
602                 self.outlinePaths = []
603                 self.path = []
604                 self.yAxisPointingUpward = yAxisPointingUpward
605                 pathString = elementNode.attributes['d'].replace(',', ' ')
606                 global globalProcessPathWordDictionary
607                 processPathWordDictionaryKeys = globalProcessPathWordDictionary.keys()
608                 for processPathWordDictionaryKey in processPathWordDictionaryKeys:
609                         pathString = pathString.replace( processPathWordDictionaryKey, ' %s ' % processPathWordDictionaryKey )
610                 self.words = getRightStripMinusSplit(pathString)
611                 for self.wordIndex in xrange( len( self.words ) ):
612                         word = self.words[ self.wordIndex ]
613                         if word in processPathWordDictionaryKeys:
614                                 globalProcessPathWordDictionary[word](self)
615                 if len(self.path) > 0:
616                         self.outlinePaths.append(self.path)
617                 self.loops += getTransformedOutlineByPaths(elementNode, self.outlinePaths, yAxisPointingUpward)
618
619         def addPathArc( self, end ):
620                 "Add an arc to the path."
621                 begin = self.getOldPoint()
622                 self.controlPoints = None
623                 radius = self.getComplexByExtraIndex(1)
624                 xAxisRotation = math.radians(float(self.words[self.wordIndex + 3]))
625                 largeArcFlag = euclidean.getBooleanFromValue(self.words[ self.wordIndex + 4 ])
626                 sweepFlag = euclidean.getBooleanFromValue(self.words[ self.wordIndex + 5 ])
627                 self.path += getArcComplexes(begin, end, largeArcFlag, radius, sweepFlag, xAxisRotation)
628                 self.wordIndex += 8
629
630         def addPathCubic( self, controlPoints, end ):
631                 "Add a cubic curve to the path."
632                 begin = self.getOldPoint()
633                 self.controlPoints = controlPoints
634                 self.path += getCubicPoints( begin, controlPoints, end )
635                 self.wordIndex += 7
636
637         def addPathCubicReflected( self, controlPoint, end ):
638                 "Add a cubic curve to the path from a reflected control point."
639                 begin = self.getOldPoint()
640                 controlPointBegin = begin
641                 if self.controlPoints != None:
642                         if len(self.controlPoints) == 2:
643                                 controlPointBegin = begin + begin - self.controlPoints[-1]
644                 self.controlPoints = [controlPointBegin, controlPoint]
645                 self.path += getCubicPoints(begin, self.controlPoints, end)
646                 self.wordIndex += 5
647
648         def addPathLine(self, lineFunction, point):
649                 "Add a line to the path."
650                 self.controlPoints = None
651                 self.path.append(point)
652                 self.wordIndex += 3
653                 self.addPathLineByFunction(lineFunction)
654
655         def addPathLineAxis(self, point):
656                 "Add an axis line to the path."
657                 self.controlPoints = None
658                 self.path.append(point)
659                 self.wordIndex += 2
660
661         def addPathLineByFunction( self, lineFunction ):
662                 "Add a line to the path by line function."
663                 while 1:
664                         if self.getFloatByExtraIndex() == None:
665                                 return
666                         self.path.append(lineFunction())
667                         self.wordIndex += 2
668
669         def addPathMove( self, lineFunction, point ):
670                 "Add an axis line to the path."
671                 self.controlPoints = None
672                 if len(self.path) > 0:
673                         self.outlinePaths.append(self.path)
674                         self.oldPoint = self.path[-1]
675                 self.path = [point]
676                 self.wordIndex += 3
677                 self.addPathLineByFunction(lineFunction)
678
679         def addPathQuadratic( self, controlPoint, end ):
680                 "Add a quadratic curve to the path."
681                 begin = self.getOldPoint()
682                 self.controlPoints = [controlPoint]
683                 self.path += getQuadraticPoints(begin, controlPoint, end)
684                 self.wordIndex += 5
685
686         def addPathQuadraticReflected( self, end ):
687                 "Add a quadratic curve to the path from a reflected control point."
688                 begin = self.getOldPoint()
689                 controlPoint = begin
690                 if self.controlPoints != None:
691                         if len( self.controlPoints ) == 1:
692                                 controlPoint = begin + begin - self.controlPoints[-1]
693                 self.controlPoints = [ controlPoint ]
694                 self.path += getQuadraticPoints(begin, controlPoint, end)
695                 self.wordIndex += 3
696
697         def getComplexByExtraIndex( self, extraIndex=0 ):
698                 'Get complex from the extraIndex.'
699                 return euclidean.getComplexByWords(self.words, self.wordIndex + extraIndex)
700
701         def getComplexRelative(self):
702                 "Get relative complex."
703                 return self.getComplexByExtraIndex() + self.getOldPoint()
704
705         def getFloatByExtraIndex( self, extraIndex=0 ):
706                 'Get float from the extraIndex.'
707                 totalIndex = self.wordIndex + extraIndex
708                 if totalIndex >= len(self.words):
709                         return None
710                 word = self.words[totalIndex]
711                 if word[: 1].isalpha():
712                         return None
713                 return euclidean.getFloatFromValue(word)
714
715         def getOldPoint(self):
716                 'Get the old point.'
717                 if len(self.path) > 0:
718                         return self.path[-1]
719                 return self.oldPoint
720
721         def processPathWordA(self):
722                 'Process path word A.'
723                 self.addPathArc( self.getComplexByExtraIndex( 6 ) )
724
725         def processPathWorda(self):
726                 'Process path word a.'
727                 self.addPathArc(self.getComplexByExtraIndex(6) + self.getOldPoint())
728
729         def processPathWordC(self):
730                 'Process path word C.'
731                 end = self.getComplexByExtraIndex( 5 )
732                 self.addPathCubic( [ self.getComplexByExtraIndex( 1 ), self.getComplexByExtraIndex(3) ], end )
733
734         def processPathWordc(self):
735                 'Process path word C.'
736                 begin = self.getOldPoint()
737                 end = self.getComplexByExtraIndex( 5 )
738                 self.addPathCubic( [ self.getComplexByExtraIndex( 1 ) + begin, self.getComplexByExtraIndex(3) + begin ], end + begin )
739
740         def processPathWordH(self):
741                 "Process path word H."
742                 beginY = self.getOldPoint().imag
743                 self.addPathLineAxis(complex(float(self.words[self.wordIndex + 1]), beginY))
744                 while 1:
745                         floatByExtraIndex = self.getFloatByExtraIndex()
746                         if floatByExtraIndex == None:
747                                 return
748                         self.path.append(complex(floatByExtraIndex, beginY))
749                         self.wordIndex += 1
750
751         def processPathWordh(self):
752                 "Process path word h."
753                 begin = self.getOldPoint()
754                 self.addPathLineAxis(complex(float(self.words[self.wordIndex + 1]) + begin.real, begin.imag))
755                 while 1:
756                         floatByExtraIndex = self.getFloatByExtraIndex()
757                         if floatByExtraIndex == None:
758                                 return
759                         self.path.append(complex(floatByExtraIndex + self.getOldPoint().real, begin.imag))
760                         self.wordIndex += 1
761
762         def processPathWordL(self):
763                 "Process path word L."
764                 self.addPathLine(self.getComplexByExtraIndex, self.getComplexByExtraIndex( 1 ))
765
766         def processPathWordl(self):
767                 "Process path word l."
768                 self.addPathLine(self.getComplexRelative, self.getComplexByExtraIndex(1) + self.getOldPoint())
769
770         def processPathWordM(self):
771                 "Process path word M."
772                 self.addPathMove(self.getComplexByExtraIndex, self.getComplexByExtraIndex(1))
773
774         def processPathWordm(self):
775                 "Process path word m."
776                 self.addPathMove(self.getComplexRelative, self.getComplexByExtraIndex(1) + self.getOldPoint())
777
778         def processPathWordQ(self):
779                 'Process path word Q.'
780                 self.addPathQuadratic( self.getComplexByExtraIndex( 1 ), self.getComplexByExtraIndex(3) )
781
782         def processPathWordq(self):
783                 'Process path word q.'
784                 begin = self.getOldPoint()
785                 self.addPathQuadratic(self.getComplexByExtraIndex(1) + begin, self.getComplexByExtraIndex(3) + begin)
786
787         def processPathWordS(self):
788                 'Process path word S.'
789                 self.addPathCubicReflected( self.getComplexByExtraIndex( 1 ), self.getComplexByExtraIndex(3) )
790
791         def processPathWords(self):
792                 'Process path word s.'
793                 begin = self.getOldPoint()
794                 self.addPathCubicReflected(self.getComplexByExtraIndex(1) + begin, self.getComplexByExtraIndex(3) + begin)
795
796         def processPathWordT(self):
797                 'Process path word T.'
798                 self.addPathQuadraticReflected( self.getComplexByExtraIndex( 1 ) )
799
800         def processPathWordt(self):
801                 'Process path word t.'
802                 self.addPathQuadraticReflected(self.getComplexByExtraIndex(1) + self.getOldPoint())
803
804         def processPathWordV(self):
805                 "Process path word V."
806                 beginX = self.getOldPoint().real
807                 self.addPathLineAxis(complex(beginX, float(self.words[self.wordIndex + 1])))
808                 while 1:
809                         floatByExtraIndex = self.getFloatByExtraIndex()
810                         if floatByExtraIndex == None:
811                                 return
812                         self.path.append(complex(beginX, floatByExtraIndex))
813                         self.wordIndex += 1
814
815         def processPathWordv(self):
816                 "Process path word v."
817                 begin = self.getOldPoint()
818                 self.addPathLineAxis(complex(begin.real, float(self.words[self.wordIndex + 1]) + begin.imag))
819                 while 1:
820                         floatByExtraIndex = self.getFloatByExtraIndex()
821                         if floatByExtraIndex == None:
822                                 return
823                         self.path.append(complex(begin.real, floatByExtraIndex + self.getOldPoint().imag))
824                         self.wordIndex += 1
825
826         def processPathWordZ(self):
827                 "Process path word Z."
828                 self.controlPoints = None
829                 if len(self.path) < 1:
830                         return
831                 self.loops.append(getChainMatrixSVGIfNecessary(self.elementNode, self.yAxisPointingUpward).getTransformedPath(self.path))
832                 self.oldPoint = self.path[0]
833                 self.path = []
834
835         def processPathWordz(self):
836                 "Process path word z."
837                 self.processPathWordZ()
838
839
840 class SVGReader(object):
841         "An svg carving."
842         def __init__(self):
843                 "Add empty lists."
844                 self.loopLayers = []
845                 self.sliceDictionary = None
846                 self.stopProcessing = False
847                 self.z = 0.0
848
849         def flipDirectLayer(self, loopLayer):
850                 "Flip the y coordinate of the layer and direct the loops."
851                 for loop in loopLayer.loops:
852                         for pointIndex, point in enumerate(loop):
853                                 loop[pointIndex] = complex(point.real, -point.imag)
854                 triangle_mesh.sortLoopsInOrderOfArea(True, loopLayer.loops)
855                 for loopIndex, loop in enumerate(loopLayer.loops):
856                         isInsideLoops = euclidean.getIsInFilledRegion(loopLayer.loops[: loopIndex], euclidean.getLeftPoint(loop))
857                         intercircle.directLoop((not isInsideLoops), loop)
858
859         def getLoopLayer(self):
860                 "Return the rotated loop layer."
861                 if self.z != None:
862                         loopLayer = euclidean.LoopLayer(self.z)
863                         self.loopLayers.append(loopLayer)
864                         self.z = None
865                 return self.loopLayers[-1]
866
867         def parseSVG(self, fileName, svgText):
868                 "Parse SVG text and store the layers."
869                 self.fileName = fileName
870                 xmlParser = DocumentNode(fileName, svgText)
871                 self.documentElement = xmlParser.getDocumentElement()
872                 if self.documentElement == None:
873                         print('Warning, documentElement was None in parseSVG in SVGReader, so nothing will be done for:')
874                         print(fileName)
875                         return
876                 self.parseSVGByElementNode(self.documentElement)
877
878         def parseSVGByElementNode(self, elementNode):
879                 "Parse SVG by elementNode."
880                 self.sliceDictionary = svg_writer.getSliceDictionary(elementNode)
881                 self.yAxisPointingUpward = euclidean.getBooleanFromDictionary(False, self.sliceDictionary, 'yAxisPointingUpward')
882                 self.processElementNode(elementNode)
883                 if not self.yAxisPointingUpward:
884                         for loopLayer in self.loopLayers:
885                                 self.flipDirectLayer(loopLayer)
886
887         def processElementNode(self, elementNode):
888                 'Process the xml element.'
889                 if self.stopProcessing:
890                         return
891                 lowerLocalName = elementNode.getNodeName().lower()
892                 global globalProcessSVGElementDictionary
893                 if lowerLocalName in globalProcessSVGElementDictionary:
894                         try:
895                                 globalProcessSVGElementDictionary[lowerLocalName](elementNode, self)
896                         except:
897                                 print('Warning, in processElementNode in svg_reader, could not process:')
898                                 print(elementNode)
899                                 traceback.print_exc(file=sys.stdout)
900                 for childNode in elementNode.childNodes:
901                         self.processElementNode(childNode)
902
903
904 globalFontFileNames = None
905 globalFontReaderDictionary = {}
906 globalGetTricomplexDictionary = {}
907 globalGetTricomplexFunctions = [
908         getTricomplexmatrix,
909         getTricomplexrotate,
910         getTricomplexscale,
911         getTricomplexskewX,
912         getTricomplexskewY,
913         getTricomplextranslate ]
914 globalProcessPathWordFunctions = [
915         PathReader.processPathWordA,
916         PathReader.processPathWorda,
917         PathReader.processPathWordC,
918         PathReader.processPathWordc,
919         PathReader.processPathWordH,
920         PathReader.processPathWordh,
921         PathReader.processPathWordL,
922         PathReader.processPathWordl,
923         PathReader.processPathWordM,
924         PathReader.processPathWordm,
925         PathReader.processPathWordQ,
926         PathReader.processPathWordq,
927         PathReader.processPathWordS,
928         PathReader.processPathWords,
929         PathReader.processPathWordT,
930         PathReader.processPathWordt,
931         PathReader.processPathWordV,
932         PathReader.processPathWordv,
933         PathReader.processPathWordZ,
934         PathReader.processPathWordz ]
935 globalProcessPathWordDictionary = {}
936 globalProcessSVGElementDictionary = {}
937 globalProcessSVGElementFunctions = [
938         processSVGElementcircle,
939         processSVGElementellipse,
940         processSVGElementg,
941         processSVGElementline,
942         processSVGElementpath,
943         processSVGElementpolygon,
944         processSVGElementpolyline,
945         processSVGElementrect,
946         processSVGElementtext ]
947 globalSideAngle = 0.5 * math.pi / float( globalNumberOfCornerPoints )
948
949
950 addFunctionsToDictionary( globalGetTricomplexDictionary, globalGetTricomplexFunctions, 'getTricomplex')
951 addFunctionsToDictionary( globalProcessPathWordDictionary, globalProcessPathWordFunctions, 'processPathWord')
952 addFunctionsToDictionary( globalProcessSVGElementDictionary, globalProcessSVGElementFunctions, 'processSVGElement')