6 from __future__ import absolute_import
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
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'
28 globalNumberOfCornerPoints = 11
29 globalNumberOfBezierPoints = globalNumberOfCornerPoints + globalNumberOfCornerPoints
30 globalNumberOfCirclePoints = 4 * globalNumberOfCornerPoints
33 def addFunctionsToDictionary( dictionary, functions, prefix ):
34 "Add functions to dictionary."
35 for function in functions:
36 dictionary[ function.__name__[ len( prefix ) : ] ] = function
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'
41 print('Warning, begin equals end in getArcComplexes in svgReader')
46 print('Warning, radius.imag is less than zero in getArcComplexes in svgReader')
48 radius = complex(radius.real, abs(radius.imag))
50 print('Warning, radius.real is less than zero in getArcComplexes in svgReader')
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')
57 if radius.real <= 0.0:
58 print('Warning, radius.real is too small for getArcComplexes in svgReader')
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')
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
91 centerTransformed += midWiddershinsTransformed
92 beginMinusCenterTransformed = beginTransformed - centerTransformed
93 beginMinusCenterTransformedLength = abs(beginMinusCenterTransformed)
94 if beginMinusCenterTransformedLength <= 0.0:
96 beginAngle = math.atan2(beginMinusCenterTransformed.imag, beginMinusCenterTransformed.real)
97 endMinusCenterTransformed = endTransformed - centerTransformed
98 angleDifference = euclidean.getAngleDifferenceByComplex(endMinusCenterTransformed, beginMinusCenterTransformed)
100 if angleDifference < 0.0:
101 angleDifference += 2.0 * math.pi
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)
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)
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)
125 def getChainMatrixSVGIfNecessary(elementNode, yAxisPointingUpward):
126 "Get chain matrixSVG by svgElement and yAxisPointingUpward."
127 matrixSVG = MatrixSVG()
128 if yAxisPointingUpward:
130 return getChainMatrixSVG(elementNode, matrixSVG)
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
138 def getCubicPoints( begin, controlPoints, end, numberOfBezierPoints=globalNumberOfBezierPoints):
139 'Get the cubic points.'
140 bezierPortion = 1.0 / float(numberOfBezierPoints)
142 for bezierIndex in xrange( 1, numberOfBezierPoints + 1 ):
143 cubicPoints.append(getCubicPoint(bezierPortion * bezierIndex, begin, controlPoints, end))
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)
161 fontLower = 'gentium_basic_regular'
162 fontReader = FontReader(fontLower)
163 globalFontReaderDictionary[fontLower] = fontReader
166 def getFontsDirectoryPath():
167 "Get the fonts directory path."
168 return archive.getFabmetheusUtilitiesPath('fonts')
170 def getLabelString(dictionary):
171 "Get the label string for the dictionary."
172 for key in dictionary:
173 labelIndex = key.find('label')
175 return dictionary[key]
178 def getMatrixSVG(elementNode):
179 "Get matrixSVG by svgElement."
180 matrixSVG = MatrixSVG()
181 if 'transform' not in elementNode.attributes:
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()))
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
203 def getQuadraticPoints(begin, controlPoint, end, numberOfBezierPoints=globalNumberOfBezierPoints):
204 'Get the quadratic points.'
205 bezierPortion = 1.0 / float(numberOfBezierPoints)
207 for bezierIndex in xrange(1, numberOfBezierPoints + 1):
208 quadraticPoints.append(getQuadraticPoint(bezierPortion * bezierIndex, begin, controlPoint, end))
209 return quadraticPoints
211 def getRightStripAlphabetPercent(word):
212 "Get word with alphabet characters and the percent sign stripped from the right."
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])
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()
228 def getStrokeRadius(elementNode):
229 "Get the stroke radius."
230 return 0.5 * getRightStripAlphabetPercent(getStyleValue('1.0', elementNode, 'stroke-width'))
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)
238 words = line[strokeIndex :].replace(':', ' ').replace(';', ' ').split()
241 if key in elementNode.attributes:
242 return elementNode.attributes[key]
243 if elementNode.parentNode == None:
245 return getStyleValue(defaultValue, elementNode.parentNode, key)
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
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))
264 fillOutlineLoops = [loop]
265 return getChainMatrixSVGIfNecessary(elementNode, yAxisPointingUpward).getTransformedPaths(fillOutlineLoops)
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)
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)
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))
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()]
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()]
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()]
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()]
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)
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
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]
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')
329 print('Warning, in processSVGElementcircle in svgReader radius is zero in:')
332 global globalNumberOfCirclePoints
333 global globalSideAngle
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)
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:')
350 global globalNumberOfCirclePoints
351 global globalSideAngle
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)
359 def processSVGElementg(elementNode, svgReader):
360 'Process elementNode by svgReader.'
361 if 'id' not in elementNode.attributes:
363 idString = elementNode.attributes['id']
364 if 'beginningOfControlSection' in elementNode.attributes:
365 if elementNode.attributes['beginningOfControlSection'].lower()[: 1] == 't':
366 svgReader.stopProcessing = True
368 idStringLower = idString.lower()
369 zIndex = idStringLower.find('z:')
371 idStringLower = getLabelString(elementNode.attributes)
372 zIndex = idStringLower.find('z:')
375 floatFromValue = euclidean.getFloatFromValue(idStringLower[zIndex + len('z:') :].strip())
376 if floatFromValue != None:
377 svgReader.z = floatFromValue
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)
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)
392 loopLayer = svgReader.getLoopLayer()
393 PathReader(elementNode, loopLayer.loops, svgReader.yAxisPointingUpward)
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)
401 loopLayer = svgReader.getLoopLayer()
402 words = getRightStripMinusSplit(elementNode.attributes['points'].replace(',', ' '))
404 for wordIndex in xrange( 0, len(words), 2 ):
405 loop.append(euclidean.getComplexByWords(words[wordIndex :]))
406 loopLayer.loops += getTransformedFillOutline(elementNode, loop, svgReader.yAxisPointingUpward)
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)
414 loopLayer = svgReader.getLoopLayer()
415 words = getRightStripMinusSplit(elementNode.attributes['points'].replace(',', ' '))
417 for wordIndex in xrange(0, len(words), 2):
418 path.append(euclidean.getComplexByWords(words[wordIndex :]))
419 loopLayer.loops += getTransformedOutlineByPath(elementNode, path, svgReader.yAxisPointingUpward)
421 def processSVGElementrect( elementNode, svgReader ):
422 "Process elementNode by svgReader."
423 attributes = elementNode.attributes
424 height = euclidean.getFloatDefaultByDictionary( 0.0, attributes, 'height')
426 print('Warning, in processSVGElementrect in svgReader height is zero in:')
429 width = euclidean.getFloatDefaultByDictionary( 0.0, attributes, 'width')
431 print('Warning, in processSVGElementrect in svgReader width is zero in:')
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)
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
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 ) )
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)
472 def processSVGElementtext(elementNode, svgReader):
473 "Process elementNode by svgReader."
474 if svgReader.yAxisPointingUpward:
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):
483 for textComplexPoint in textComplexLoop:
484 translatedLoop.append(textComplexPoint + translate )
485 loopLayer.loops.append(matrixSVG.getTransformedPath(translatedLoop))
488 class FontReader(object):
489 "Class to read a font in the fonts folder."
490 def __init__(self, fontFamily):
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
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]
518 "Class to handle a glyph."
519 def __init__(self, elementNode, unitsPerEM, yAxisPointingUpward):
521 self.horizontalAdvanceX = float(elementNode.attributes['horiz-adv-x'])
523 self.unitsPerEM = unitsPerEM
524 elementNode.attributes['fill'] = ''
525 if 'd' not in elementNode.attributes:
527 PathReader(elementNode, self.loops, yAxisPointingUpward)
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
536 for loop in self.loops:
538 sizedLoops.append(sizedLoop)
540 sizedLoop.append( complex(multiplierX * (point.real + horizontalAdvanceX), multiplierY * point.imag))
544 class MatrixSVG(object):
545 "Two by three svg matrix."
546 def __init__(self, tricomplex=None):
548 self.tricomplex = tricomplex
551 "Get the string representation of this two by three svg matrix."
552 return str(self.tricomplex)
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))
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))
570 def getTransformedPath(self, path):
571 "Get transformed path."
572 if self.tricomplex == None:
574 complexX = self.tricomplex[0]
575 complexY = self.tricomplex[1]
576 complexTranslation = self.tricomplex[2]
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
584 def getTransformedPaths(self, paths):
585 "Get transformed paths."
586 if self.tricomplex == None:
588 transformedPaths = []
590 transformedPaths.append(self.getTransformedPath(path))
591 return transformedPaths
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
602 self.outlinePaths = []
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)
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)
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 )
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)
648 def addPathLine(self, lineFunction, point):
649 "Add a line to the path."
650 self.controlPoints = None
651 self.path.append(point)
653 self.addPathLineByFunction(lineFunction)
655 def addPathLineAxis(self, point):
656 "Add an axis line to the path."
657 self.controlPoints = None
658 self.path.append(point)
661 def addPathLineByFunction( self, lineFunction ):
662 "Add a line to the path by line function."
664 if self.getFloatByExtraIndex() == None:
666 self.path.append(lineFunction())
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]
677 self.addPathLineByFunction(lineFunction)
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)
686 def addPathQuadraticReflected( self, end ):
687 "Add a quadratic curve to the path from a reflected control point."
688 begin = self.getOldPoint()
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)
697 def getComplexByExtraIndex( self, extraIndex=0 ):
698 'Get complex from the extraIndex.'
699 return euclidean.getComplexByWords(self.words, self.wordIndex + extraIndex)
701 def getComplexRelative(self):
702 "Get relative complex."
703 return self.getComplexByExtraIndex() + self.getOldPoint()
705 def getFloatByExtraIndex( self, extraIndex=0 ):
706 'Get float from the extraIndex.'
707 totalIndex = self.wordIndex + extraIndex
708 if totalIndex >= len(self.words):
710 word = self.words[totalIndex]
711 if word[: 1].isalpha():
713 return euclidean.getFloatFromValue(word)
715 def getOldPoint(self):
717 if len(self.path) > 0:
721 def processPathWordA(self):
722 'Process path word A.'
723 self.addPathArc( self.getComplexByExtraIndex( 6 ) )
725 def processPathWorda(self):
726 'Process path word a.'
727 self.addPathArc(self.getComplexByExtraIndex(6) + self.getOldPoint())
729 def processPathWordC(self):
730 'Process path word C.'
731 end = self.getComplexByExtraIndex( 5 )
732 self.addPathCubic( [ self.getComplexByExtraIndex( 1 ), self.getComplexByExtraIndex(3) ], end )
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 )
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))
745 floatByExtraIndex = self.getFloatByExtraIndex()
746 if floatByExtraIndex == None:
748 self.path.append(complex(floatByExtraIndex, beginY))
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))
756 floatByExtraIndex = self.getFloatByExtraIndex()
757 if floatByExtraIndex == None:
759 self.path.append(complex(floatByExtraIndex + self.getOldPoint().real, begin.imag))
762 def processPathWordL(self):
763 "Process path word L."
764 self.addPathLine(self.getComplexByExtraIndex, self.getComplexByExtraIndex( 1 ))
766 def processPathWordl(self):
767 "Process path word l."
768 self.addPathLine(self.getComplexRelative, self.getComplexByExtraIndex(1) + self.getOldPoint())
770 def processPathWordM(self):
771 "Process path word M."
772 self.addPathMove(self.getComplexByExtraIndex, self.getComplexByExtraIndex(1))
774 def processPathWordm(self):
775 "Process path word m."
776 self.addPathMove(self.getComplexRelative, self.getComplexByExtraIndex(1) + self.getOldPoint())
778 def processPathWordQ(self):
779 'Process path word Q.'
780 self.addPathQuadratic( self.getComplexByExtraIndex( 1 ), self.getComplexByExtraIndex(3) )
782 def processPathWordq(self):
783 'Process path word q.'
784 begin = self.getOldPoint()
785 self.addPathQuadratic(self.getComplexByExtraIndex(1) + begin, self.getComplexByExtraIndex(3) + begin)
787 def processPathWordS(self):
788 'Process path word S.'
789 self.addPathCubicReflected( self.getComplexByExtraIndex( 1 ), self.getComplexByExtraIndex(3) )
791 def processPathWords(self):
792 'Process path word s.'
793 begin = self.getOldPoint()
794 self.addPathCubicReflected(self.getComplexByExtraIndex(1) + begin, self.getComplexByExtraIndex(3) + begin)
796 def processPathWordT(self):
797 'Process path word T.'
798 self.addPathQuadraticReflected( self.getComplexByExtraIndex( 1 ) )
800 def processPathWordt(self):
801 'Process path word t.'
802 self.addPathQuadraticReflected(self.getComplexByExtraIndex(1) + self.getOldPoint())
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])))
809 floatByExtraIndex = self.getFloatByExtraIndex()
810 if floatByExtraIndex == None:
812 self.path.append(complex(beginX, floatByExtraIndex))
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))
820 floatByExtraIndex = self.getFloatByExtraIndex()
821 if floatByExtraIndex == None:
823 self.path.append(complex(begin.real, floatByExtraIndex + self.getOldPoint().imag))
826 def processPathWordZ(self):
827 "Process path word Z."
828 self.controlPoints = None
829 if len(self.path) < 1:
831 self.loops.append(getChainMatrixSVGIfNecessary(self.elementNode, self.yAxisPointingUpward).getTransformedPath(self.path))
832 self.oldPoint = self.path[0]
835 def processPathWordz(self):
836 "Process path word z."
837 self.processPathWordZ()
840 class SVGReader(object):
845 self.sliceDictionary = None
846 self.stopProcessing = False
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)
859 def getLoopLayer(self):
860 "Return the rotated loop layer."
862 loopLayer = euclidean.LoopLayer(self.z)
863 self.loopLayers.append(loopLayer)
865 return self.loopLayers[-1]
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:')
876 self.parseSVGByElementNode(self.documentElement)
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)
887 def processElementNode(self, elementNode):
888 'Process the xml element.'
889 if self.stopProcessing:
891 lowerLocalName = elementNode.getNodeName().lower()
892 global globalProcessSVGElementDictionary
893 if lowerLocalName in globalProcessSVGElementDictionary:
895 globalProcessSVGElementDictionary[lowerLocalName](elementNode, self)
897 print('Warning, in processElementNode in svg_reader, could not process:')
899 traceback.print_exc(file=sys.stdout)
900 for childNode in elementNode.childNodes:
901 self.processElementNode(childNode)
904 globalFontFileNames = None
905 globalFontReaderDictionary = {}
906 globalGetTricomplexDictionary = {}
907 globalGetTricomplexFunctions = [
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,
941 processSVGElementline,
942 processSVGElementpath,
943 processSVGElementpolygon,
944 processSVGElementpolyline,
945 processSVGElementrect,
946 processSVGElementtext ]
947 globalSideAngle = 0.5 * math.pi / float( globalNumberOfCornerPoints )
950 addFunctionsToDictionary( globalGetTricomplexDictionary, globalGetTricomplexFunctions, 'getTricomplex')
951 addFunctionsToDictionary( globalProcessPathWordDictionary, globalProcessPathWordFunctions, 'processPathWord')
952 addFunctionsToDictionary( globalProcessSVGElementDictionary, globalProcessSVGElementFunctions, 'processSVGElement')