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