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.
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
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'
31 globalNumberOfCornerPoints = 11
32 globalNumberOfBezierPoints = globalNumberOfCornerPoints + globalNumberOfCornerPoints
33 globalNumberOfCirclePoints = 4 * globalNumberOfCornerPoints
36 def addFunctionsToDictionary( dictionary, functions, prefix ):
37 "Add functions to dictionary."
38 for function in functions:
39 dictionary[ function.__name__[ len( prefix ) : ] ] = function
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'
44 print('Warning, begin equals end in getArcComplexes in svgReader')
49 print('Warning, radius.imag is less than zero in getArcComplexes in svgReader')
51 radius = complex(radius.real, abs(radius.imag))
53 print('Warning, radius.real is less than zero in getArcComplexes in svgReader')
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')
60 if radius.real <= 0.0:
61 print('Warning, radius.real is too small for getArcComplexes in svgReader')
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')
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
94 centerTransformed += midWiddershinsTransformed
95 beginMinusCenterTransformed = beginTransformed - centerTransformed
96 beginMinusCenterTransformedLength = abs(beginMinusCenterTransformed)
97 if beginMinusCenterTransformedLength <= 0.0:
99 beginAngle = math.atan2(beginMinusCenterTransformed.imag, beginMinusCenterTransformed.real)
100 endMinusCenterTransformed = endTransformed - centerTransformed
101 angleDifference = euclidean.getAngleDifferenceByComplex(endMinusCenterTransformed, beginMinusCenterTransformed)
103 if angleDifference < 0.0:
104 angleDifference += 2.0 * math.pi
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)
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)
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)
128 def getChainMatrixSVGIfNecessary(elementNode, yAxisPointingUpward):
129 "Get chain matrixSVG by svgElement and yAxisPointingUpward."
130 matrixSVG = MatrixSVG()
131 if yAxisPointingUpward:
133 return getChainMatrixSVG(elementNode, matrixSVG)
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
141 def getCubicPoints( begin, controlPoints, end, numberOfBezierPoints=globalNumberOfBezierPoints):
142 'Get the cubic points.'
143 bezierPortion = 1.0 / float(numberOfBezierPoints)
145 for bezierIndex in xrange( 1, numberOfBezierPoints + 1 ):
146 cubicPoints.append(getCubicPoint(bezierPortion * bezierIndex, begin, controlPoints, end))
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)
164 fontLower = 'gentium_basic_regular'
165 fontReader = FontReader(fontLower)
166 globalFontReaderDictionary[fontLower] = fontReader
169 def getFontsDirectoryPath():
170 "Get the fonts directory path."
171 return archive.getFabmetheusUtilitiesPath('fonts')
173 def getLabelString(dictionary):
174 "Get the label string for the dictionary."
175 for key in dictionary:
176 labelIndex = key.find('label')
178 return dictionary[key]
181 def getMatrixSVG(elementNode):
182 "Get matrixSVG by svgElement."
183 matrixSVG = MatrixSVG()
184 if 'transform' not in elementNode.attributes:
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()))
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
206 def getQuadraticPoints(begin, controlPoint, end, numberOfBezierPoints=globalNumberOfBezierPoints):
207 'Get the quadratic points.'
208 bezierPortion = 1.0 / float(numberOfBezierPoints)
210 for bezierIndex in xrange(1, numberOfBezierPoints + 1):
211 quadraticPoints.append(getQuadraticPoint(bezierPortion * bezierIndex, begin, controlPoint, end))
212 return quadraticPoints
214 def getRightStripAlphabetPercent(word):
215 "Get word with alphabet characters and the percent sign stripped from the right."
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])
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()
231 def getStrokeRadius(elementNode):
232 "Get the stroke radius."
233 return 0.5 * getRightStripAlphabetPercent(getStyleValue('1.0', elementNode, 'stroke-width'))
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)
241 words = line[strokeIndex :].replace(':', ' ').replace(';', ' ').split()
244 if key in elementNode.attributes:
245 return elementNode.attributes[key]
246 if elementNode.parentNode == None:
248 return getStyleValue(defaultValue, elementNode.parentNode, key)
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
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))
267 fillOutlineLoops = [loop]
268 return getChainMatrixSVGIfNecessary(elementNode, yAxisPointingUpward).getTransformedPaths(fillOutlineLoops)
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)
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)
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))
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()]
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()]
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()]
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()]
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)
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
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]
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')
332 print('Warning, in processSVGElementcircle in svgReader radius is zero in:')
335 global globalNumberOfCirclePoints
336 global globalSideAngle
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)
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:')
353 global globalNumberOfCirclePoints
354 global globalSideAngle
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)
362 def processSVGElementg(elementNode, svgReader):
363 'Process elementNode by svgReader.'
364 if 'id' not in elementNode.attributes:
366 idString = elementNode.attributes['id']
367 if 'beginningOfControlSection' in elementNode.attributes:
368 if elementNode.attributes['beginningOfControlSection'].lower()[: 1] == 't':
369 svgReader.stopProcessing = True
371 idStringLower = idString.lower()
372 zIndex = idStringLower.find('z:')
374 idStringLower = getLabelString(elementNode.attributes)
375 zIndex = idStringLower.find('z:')
378 floatFromValue = euclidean.getFloatFromValue(idStringLower[zIndex + len('z:') :].strip())
379 if floatFromValue != None:
380 svgReader.z = floatFromValue
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)
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)
395 loopLayer = svgReader.getLoopLayer()
396 PathReader(elementNode, loopLayer.loops, svgReader.yAxisPointingUpward)
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)
404 loopLayer = svgReader.getLoopLayer()
405 words = getRightStripMinusSplit(elementNode.attributes['points'].replace(',', ' '))
407 for wordIndex in xrange( 0, len(words), 2 ):
408 loop.append(euclidean.getComplexByWords(words[wordIndex :]))
409 loopLayer.loops += getTransformedFillOutline(elementNode, loop, svgReader.yAxisPointingUpward)
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)
417 loopLayer = svgReader.getLoopLayer()
418 words = getRightStripMinusSplit(elementNode.attributes['points'].replace(',', ' '))
420 for wordIndex in xrange(0, len(words), 2):
421 path.append(euclidean.getComplexByWords(words[wordIndex :]))
422 loopLayer.loops += getTransformedOutlineByPath(elementNode, path, svgReader.yAxisPointingUpward)
424 def processSVGElementrect( elementNode, svgReader ):
425 "Process elementNode by svgReader."
426 attributes = elementNode.attributes
427 height = euclidean.getFloatDefaultByDictionary( 0.0, attributes, 'height')
429 print('Warning, in processSVGElementrect in svgReader height is zero in:')
432 width = euclidean.getFloatDefaultByDictionary( 0.0, attributes, 'width')
434 print('Warning, in processSVGElementrect in svgReader width is zero in:')
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)
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
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 ) )
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)
475 def processSVGElementtext(elementNode, svgReader):
476 "Process elementNode by svgReader."
477 if svgReader.yAxisPointingUpward:
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):
486 for textComplexPoint in textComplexLoop:
487 translatedLoop.append(textComplexPoint + translate )
488 loopLayer.loops.append(matrixSVG.getTransformedPath(translatedLoop))
492 "Class to read a font in the fonts folder."
493 def __init__(self, fontFamily):
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
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]
521 "Class to handle a glyph."
522 def __init__(self, elementNode, unitsPerEM, yAxisPointingUpward):
524 self.horizontalAdvanceX = float(elementNode.attributes['horiz-adv-x'])
526 self.unitsPerEM = unitsPerEM
527 elementNode.attributes['fill'] = ''
528 if 'd' not in elementNode.attributes:
530 PathReader(elementNode, self.loops, yAxisPointingUpward)
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
539 for loop in self.loops:
541 sizedLoops.append(sizedLoop)
543 sizedLoop.append( complex(multiplierX * (point.real + horizontalAdvanceX), multiplierY * point.imag))
548 "Two by three svg matrix."
549 def __init__(self, tricomplex=None):
551 self.tricomplex = tricomplex
554 "Get the string representation of this two by three svg matrix."
555 return str(self.tricomplex)
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))
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))
573 def getTransformedPath(self, path):
574 "Get transformed path."
575 if self.tricomplex == None:
577 complexX = self.tricomplex[0]
578 complexY = self.tricomplex[1]
579 complexTranslation = self.tricomplex[2]
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
587 def getTransformedPaths(self, paths):
588 "Get transformed paths."
589 if self.tricomplex == None:
591 transformedPaths = []
593 transformedPaths.append(self.getTransformedPath(path))
594 return transformedPaths
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
605 self.outlinePaths = []
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)
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)
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 )
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)
651 def addPathLine(self, lineFunction, point):
652 "Add a line to the path."
653 self.controlPoints = None
654 self.path.append(point)
656 self.addPathLineByFunction(lineFunction)
658 def addPathLineAxis(self, point):
659 "Add an axis line to the path."
660 self.controlPoints = None
661 self.path.append(point)
664 def addPathLineByFunction( self, lineFunction ):
665 "Add a line to the path by line function."
667 if self.getFloatByExtraIndex() == None:
669 self.path.append(lineFunction())
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]
680 self.addPathLineByFunction(lineFunction)
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)
689 def addPathQuadraticReflected( self, end ):
690 "Add a quadratic curve to the path from a reflected control point."
691 begin = self.getOldPoint()
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)
700 def getComplexByExtraIndex( self, extraIndex=0 ):
701 'Get complex from the extraIndex.'
702 return euclidean.getComplexByWords(self.words, self.wordIndex + extraIndex)
704 def getComplexRelative(self):
705 "Get relative complex."
706 return self.getComplexByExtraIndex() + self.getOldPoint()
708 def getFloatByExtraIndex( self, extraIndex=0 ):
709 'Get float from the extraIndex.'
710 totalIndex = self.wordIndex + extraIndex
711 if totalIndex >= len(self.words):
713 word = self.words[totalIndex]
714 if word[: 1].isalpha():
716 return euclidean.getFloatFromValue(word)
718 def getOldPoint(self):
720 if len(self.path) > 0:
724 def processPathWordA(self):
725 'Process path word A.'
726 self.addPathArc( self.getComplexByExtraIndex( 6 ) )
728 def processPathWorda(self):
729 'Process path word a.'
730 self.addPathArc(self.getComplexByExtraIndex(6) + self.getOldPoint())
732 def processPathWordC(self):
733 'Process path word C.'
734 end = self.getComplexByExtraIndex( 5 )
735 self.addPathCubic( [ self.getComplexByExtraIndex( 1 ), self.getComplexByExtraIndex(3) ], end )
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 )
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))
748 floatByExtraIndex = self.getFloatByExtraIndex()
749 if floatByExtraIndex == None:
751 self.path.append(complex(floatByExtraIndex, beginY))
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))
759 floatByExtraIndex = self.getFloatByExtraIndex()
760 if floatByExtraIndex == None:
762 self.path.append(complex(floatByExtraIndex + self.getOldPoint().real, begin.imag))
765 def processPathWordL(self):
766 "Process path word L."
767 self.addPathLine(self.getComplexByExtraIndex, self.getComplexByExtraIndex( 1 ))
769 def processPathWordl(self):
770 "Process path word l."
771 self.addPathLine(self.getComplexRelative, self.getComplexByExtraIndex(1) + self.getOldPoint())
773 def processPathWordM(self):
774 "Process path word M."
775 self.addPathMove(self.getComplexByExtraIndex, self.getComplexByExtraIndex(1))
777 def processPathWordm(self):
778 "Process path word m."
779 self.addPathMove(self.getComplexRelative, self.getComplexByExtraIndex(1) + self.getOldPoint())
781 def processPathWordQ(self):
782 'Process path word Q.'
783 self.addPathQuadratic( self.getComplexByExtraIndex( 1 ), self.getComplexByExtraIndex(3) )
785 def processPathWordq(self):
786 'Process path word q.'
787 begin = self.getOldPoint()
788 self.addPathQuadratic(self.getComplexByExtraIndex(1) + begin, self.getComplexByExtraIndex(3) + begin)
790 def processPathWordS(self):
791 'Process path word S.'
792 self.addPathCubicReflected( self.getComplexByExtraIndex( 1 ), self.getComplexByExtraIndex(3) )
794 def processPathWords(self):
795 'Process path word s.'
796 begin = self.getOldPoint()
797 self.addPathCubicReflected(self.getComplexByExtraIndex(1) + begin, self.getComplexByExtraIndex(3) + begin)
799 def processPathWordT(self):
800 'Process path word T.'
801 self.addPathQuadraticReflected( self.getComplexByExtraIndex( 1 ) )
803 def processPathWordt(self):
804 'Process path word t.'
805 self.addPathQuadraticReflected(self.getComplexByExtraIndex(1) + self.getOldPoint())
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])))
812 floatByExtraIndex = self.getFloatByExtraIndex()
813 if floatByExtraIndex == None:
815 self.path.append(complex(beginX, floatByExtraIndex))
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))
823 floatByExtraIndex = self.getFloatByExtraIndex()
824 if floatByExtraIndex == None:
826 self.path.append(complex(begin.real, floatByExtraIndex + self.getOldPoint().imag))
829 def processPathWordZ(self):
830 "Process path word Z."
831 self.controlPoints = None
832 if len(self.path) < 1:
834 self.loops.append(getChainMatrixSVGIfNecessary(self.elementNode, self.yAxisPointingUpward).getTransformedPath(self.path))
835 self.oldPoint = self.path[0]
838 def processPathWordz(self):
839 "Process path word z."
840 self.processPathWordZ()
848 self.sliceDictionary = None
849 self.stopProcessing = False
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)
862 def getLoopLayer(self):
863 "Return the rotated loop layer."
865 loopLayer = euclidean.LoopLayer(self.z)
866 self.loopLayers.append(loopLayer)
868 return self.loopLayers[-1]
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:')
879 self.parseSVGByElementNode(self.documentElement)
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)
890 def processElementNode(self, elementNode):
891 'Process the xml element.'
892 if self.stopProcessing:
894 lowerLocalName = elementNode.getNodeName().lower()
895 global globalProcessSVGElementDictionary
896 if lowerLocalName in globalProcessSVGElementDictionary:
898 globalProcessSVGElementDictionary[lowerLocalName](elementNode, self)
900 print('Warning, in processElementNode in svg_reader, could not process:')
902 traceback.print_exc(file=sys.stdout)
903 for childNode in elementNode.childNodes:
904 self.processElementNode(childNode)
907 globalFontFileNames = None
908 globalFontReaderDictionary = {}
909 globalGetTricomplexDictionary = {}
910 globalGetTricomplexFunctions = [
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,
944 processSVGElementline,
945 processSVGElementpath,
946 processSVGElementpolygon,
947 processSVGElementpolyline,
948 processSVGElementrect,
949 processSVGElementtext ]
950 globalSideAngle = 0.5 * math.pi / float( globalNumberOfCornerPoints )
953 addFunctionsToDictionary( globalGetTricomplexDictionary, globalGetTricomplexFunctions, 'getTricomplex')
954 addFunctionsToDictionary( globalProcessPathWordDictionary, globalProcessPathWordFunctions, 'processPathWord')
955 addFunctionsToDictionary( globalProcessSVGElementDictionary, globalProcessSVGElementFunctions, 'processSVGElement')