Skip to content

Instantly share code, notes, and snippets.

@impiaaa
Last active October 23, 2023 15:06
Show Gist options
  • Save impiaaa/645cb4a2d4a86f74807c52ed376388ef to your computer and use it in GitHub Desktop.
Save impiaaa/645cb4a2d4a86f74807c52ed376388ef to your computer and use it in GitHub Desktop.
FLA to SVG converter
from xml.dom import minidom
import sys
from xml.sax.saxutils import quoteattr, escape
import math, numpy
class Scanner:
def __init__(self, data):
self.data = data
self.pos = 0
def scanChar(self):
c = self.data[self.pos]
self.pos += 1
return c
def scanDecimal(self):
start = self.pos
end = start
while True:
if end >= len(self.data):
break
if not self.data[end].isdigit() and self.data[end] != '.' and self.data[end] != '-':
break
end += 1
if start == end:
return None
else:
self.pos = end
return float(self.data[start:end])
def hasData(self):
return self.pos < len(self.data)
def scanHex(self):
start = self.pos
if self.data[start] != '#':
return None
start += 1
end = start
while self.data[end] != '.': end += 1
wholePart = int(self.data[start:end], 16)
sign = -1 if wholePart&0x80000000 else 1
if sign == -1: wholePart = (~wholePart)+1
start = end+1
end = start
while end < len(self.data) and self.data[end] in '0123456789ABCDEFabcdef': end += 1
fractionalPart = int(self.data[start:end], 16)
self.pos = end
return sign*(wholePart+(fractionalPart/(16**(end-start))))
def scanNumber(self):
self.skipWhitespace()
if self.data[self.pos] == '#':
return self.scanHex()
else:
return self.scanDecimal()
def skipWhitespace(self):
while self.pos < len(self.data) and self.data[self.pos].isspace(): self.pos += 1
class Edge: pass
class PathCommand:
def __init__(self, cmd, x, y, x1=None, y1=None):
self.cmd = cmd
self.x = x
self.y = y
self.x1 = x1
self.y1 = y1
def __str__(self):
if self.cmd == 'Q':
return "{0} {1},{2} {3},{4}".format(self.cmd, self.x1, self.y1, self.x, self.y)
else:
return "{0} {1},{2}".format(self.cmd, self.x, self.y)
def matrixToTransform(el):
if 'a' in el.attributes and 'd' in el.attributes:
if 'b' not in el.attributes: el.attributes['b'] = '0'
if 'c' not in el.attributes: el.attributes['c'] = '0'
return 'matrix({0[a].value},{0[b].value},{0[c].value},{0[d].value},{0[tx].value},{0[ty].value})'.format(el.attributes)
if 'tx' in el.attributes and 'ty' in el.attributes:
return 'translate({0[tx].value},{0[ty].value})'.format(el.attributes)
def reversePath(path):
last = None
for command in reversed(path):
if last is None:
yield PathCommand('M', command.x, command.y)
else:
yield PathCommand(last.cmd, command.x, command.y, last.x1, last.y1)
last = command
def distance(x1, y1, x2, y2):
return math.sqrt((x1-x2)**2 + (y1-y2)**2)
def handleGradient(fout, style, nodeId, osbPaint=None):
if style.tagName.startswith('RadialGradient'):
fout.write('<radialGradient')
elif style.tagName.startswith('LinearGradient'):
fout.write('<linearGradient')
else:
raise Exception("Unknown gradient type {0}".format(style.tagName))
fout.write(' id="{0}"'.format(nodeId))
matrix = style.getElementsByTagName('matrix')
if len(matrix) > 0:
Matrix = matrix[0].getElementsByTagName('Matrix')[0]
fout.write(' gradientUnits="userSpaceOnUse"')
if 1:
fout.write(' gradientTransform="{0}"'.format(matrixToTransform(Matrix)))
if style.tagName.startswith('LinearGradient'):
fout.write(' x1="0" y1="0"')
fout.write(' x2="100%" y2="0"')
else:
fout.write(' cx="0" cy="0"')
fout.write(' r="100%"')
else:
a = float(Matrix.attributes['a'].value) if 'a' in Matrix.attributes else 0
b = float(Matrix.attributes['b'].value) if 'b' in Matrix.attributes else 0
c = float(Matrix.attributes['c'].value) if 'c' in Matrix.attributes else 0
d = float(Matrix.attributes['d'].value) if 'd' in Matrix.attributes else 0
tx = float(Matrix.attributes['tx'].value) if 'tx' in Matrix.attributes else 0
ty = float(Matrix.attributes['ty'].value) if 'ty' in Matrix.attributes else 0
mat = [[a*1000, b*1000, tx], [c*1000, d*1000, ty], [0, 0, 1]]
x1, y1, w = numpy.matmul(mat, [0, 0, 1])
x2, y2, w = numpy.matmul(mat, [1, 0, 1])
if style.tagName.startswith('LinearGradient'):
fout.write(' x1="{0}" y1="{1}"'.format(x1, y1))
fout.write(' x2="{0}" y2="{1}"'.format(x2, y2))
else:
r = distance(x1, y1, x2, y2)
fout.write(' cx="{0}" cy="{1}"'.format(x1, y1))
fout.write(' r="{1}"'.format(r))
if osbPaint is not None:
fout.write(' osb:paint="{0}"'.format(osbPaint))
fout.write('>\n')
for GradientEntry in style.getElementsByTagName('GradientEntry'):
fout.write('<stop offset="{0}" style="'.format(GradientEntry.attributes['ratio'].value))
if 'color' in GradientEntry.attributes: fout.write('stop-color:{0};'.format(GradientEntry.attributes['color'].value))
if 'alpha' in GradientEntry.attributes: fout.write('stop-opacity:{0};'.format(GradientEntry.attributes['alpha'].value))
fout.write('" />\n')
if style.tagName.startswith('RadialGradient'):
fout.write('</radialGradient>\n')
elif style.tagName.startswith('LinearGradient'):
fout.write('</linearGradient>\n')
def handleFillStyle(fout, style, fillType):
global gradientId
if style.tagName == 'SolidColor':
if 'color' in style.attributes: return fillType+':'+style.attributes['color'].value+';'
else: return 'fill:none;'
elif style.tagName == 'RadialGradient' or style.tagName == 'LinearGradient':
#handleGradient(fout, style, 'gradient{0}'.format(gradientId))
#result = fillType+':url(#gradient{0});'.format(gradientId)
#gradientId += 1
#return result
return fillType+':#0000FF;'
else:
print("can't handle", style.tagName, fillType, "style type")
return ''
def handleFrame(fout, DOMFrame):
#fout.write('<g>\n')
for element in DOMFrame.getElementsByTagName('elements')[0].childNodes:
if element.nodeType == doc.TEXT_NODE: continue
if element.tagName == 'DOMShape':
fout.write('<g transform="translate(0, {0})">\n'.format(gridPosY))
fillStyles = {}
fills = element.getElementsByTagName('fills')
if len(fills) > 0:
for FillStyle in fills[0].childNodes:
if FillStyle.nodeType == doc.TEXT_NODE: continue
assert FillStyle.tagName == 'FillStyle'
styleIndex = FillStyle.attributes['index'].value
style = [node for node in FillStyle.childNodes if node.nodeType != doc.TEXT_NODE][0]
fillStyles[styleIndex] = handleFillStyle(fout, style, 'fill')
strokeStyles = {}
strokes = element.getElementsByTagName('strokes')
if len(strokes) > 0:
for StrokeStyle in strokes[0].childNodes:
if StrokeStyle.nodeType == doc.TEXT_NODE: continue
assert StrokeStyle.tagName == 'StrokeStyle'
styleIndex = StrokeStyle.attributes['index'].value
SolidStroke = [node for node in StrokeStyle.childNodes if node.nodeType != doc.TEXT_NODE][0]
assert SolidStroke.tagName == 'SolidStroke'
strokeStyleStr = 'stroke-width:'+SolidStroke.attributes['weight'].value+';'
if 'sharpCorners' in SolidStroke.attributes: strokeStyleStr += 'stroke-linejoin:'+{'true': 'miter', 'false': 'round'}[SolidStroke.attributes['sharpCorners'].value]+';'
fillStyle = [node for node in SolidStroke.getElementsByTagName('fill')[0].childNodes if node.nodeType != doc.TEXT_NODE][0]
strokeStyleStr += handleFillStyle(fout, fillStyle, 'stroke')
strokeStyles[styleIndex] = strokeStyleStr
edges = []
for EdgeEl in element.getElementsByTagName('edges')[0].childNodes:
if EdgeEl.nodeType == doc.TEXT_NODE: continue
assert EdgeEl.tagName == 'Edge'
if 'edges' not in EdgeEl.attributes: continue
s = Scanner(EdgeEl.attributes["edges"].value)
edge = Edge()
if "fillStyle0" in EdgeEl.attributes:
edge.fillStyle0 = EdgeEl.attributes["fillStyle0"].value
else:
edge.fillStyle0 = None
if "fillStyle1" in EdgeEl.attributes:
edge.fillStyle1 = EdgeEl.attributes["fillStyle1"].value
else:
edge.fillStyle1 = None
if "strokeStyle" in EdgeEl.attributes:
edge.strokeStyle = EdgeEl.attributes["strokeStyle"].value
else:
edge.strokeStyle = None
edge.d = []
x = y = None
clockwise = 0.0
while s.hasData():
command = s.scanChar()
if command == "!":
# move to
newX, newY = s.scanNumber()/20, s.scanNumber()/20
if x is None and y is None:
firstX, firstY = newX, newY
else:
clockwise += (newX-x)*(newY+y)
if x != newX or y != newY:
x, y = newX, newY
edge.d.append(PathCommand('M', x, y))
elif command == "|":
# line to
newX, newY = s.scanNumber()/20, s.scanNumber()/20
clockwise += (newX-x)*(newY+y)
x, y = newX, newY
edge.d.append(PathCommand('L', x, y))
elif command == "[":
# quadratic spline
cx, cy = s.scanNumber()/20, s.scanNumber()/20
newX, newY = s.scanNumber()/20, s.scanNumber()/20
clockwise += (newX-x)*(newY+y)
x, y = newX, newY
edge.d.append(PathCommand('Q', x, y, cx, cy))
elif not command.isspace():
print("unknown command", command, EdgeEl.toxml())
#clockwise += (firstX-x)*(firstY+y)
edge.clockwise = clockwise > 0
#edge.d.append('z')
edges.append(edge)
for styleIndex, fillStyle in fillStyles.items():
fout.write('<path style="fill-rule:evenodd;')
fout.write(fillStyle)
fout.write('" d="')
x = y = None
startX = startY = None
fillEdges = edges[:]
while len(fillEdges) > 0:
edge = fillEdges.pop(0)
if edge.fillStyle0 == edge.fillStyle1:
# pure stroke
continue
if edge.fillStyle0 == styleIndex:
path = edge.d
elif edge.fillStyle1 == styleIndex:
path = list(reversePath(edge.d))
if edge.fillStyle0 == styleIndex or edge.fillStyle1 == styleIndex:
if startX is None: startX = path[0].x
if startY is None: startY = path[0].y
if path[0].x == x and path[0].y == y: path = path[1:]
fout.write(' '.join(map(str, path))+' ')
x, y = path[-1].x, path[-1].y
fillEdges.sort(key=lambda edge2: distance(edge2.d[0].x, edge2.d[0].y, x, y) if edge2.fillStyle0 == styleIndex else distance(edge2.d[-1].x, edge2.d[-1].y, x, y))
if x == startX and y == startY: fout.write('z')
fout.write('" />\n')
for styleIndex, strokeStyle in strokeStyles.items():
fout.write('<path style="fill:none;')
fout.write(strokeStyle)
fout.write('" d="')
x = y = None
startX = startY = None
fillEdges = edges[:]
while len(fillEdges) > 0:
edge = fillEdges.pop(0)
if edge.strokeStyle == styleIndex:
path = edge.d
if startX is None: startX = path[0].x
if startY is None: startY = path[0].y
if path[0].x == x and path[0].y == y: path = path[1:]
fout.write(' '.join(map(str, path))+' ')
x, y = path[-1].x, path[-1].y
fillEdges.sort(key=lambda edge2: distance(edge2.d[0].x, edge2.d[0].y, x, y))
if x == startX and y == startY: fout.write('z')
fout.write('" />\n')
fout.write('</g>\n')
elif element.tagName == 'DOMStaticText':
fout.write('<text transform="{0}">\n'.format(matrixToTransform(element.getElementsByTagName("matrix")[0].getElementsByTagName("Matrix")[0])))
for DOMTextRun in element.getElementsByTagName('textRuns')[0].childNodes:
if DOMTextRun.nodeType == doc.TEXT_NODE: continue
assert DOMTextRun.tagName == 'DOMTextRun'
fout.write('<tspan style="')
textAttrs = DOMTextRun.getElementsByTagName('textAttrs')[0]
DOMTextAttrs = textAttrs.getElementsByTagName('DOMTextAttrs')[0]
for textAttrName, textAttrValue in DOMTextAttrs.attributes.items():
if textAttrName == 'alignment':
if textAttrValue == 'center':
fout.write('text-anchor:middle;text-align:center;')
elif textAttrValue == 'left': # TODO: bidi
fout.write('text-anchor:start;text-align:start;')
elif textAttrValue == 'right':
fout.write('text-anchor:end;text-align:end;')
elif textAttrName == 'size': fout.write('font-size:{0};'.format(textAttrValue))
elif textAttrName == 'face': fout.write('font-family:{0};'.format(textAttrValue))
elif textAttrName == 'fillColor': fout.write('fill:{0};'.format(textAttrValue))
fout.write('">')
fout.write(escape(DOMTextRun.getElementsByTagName('characters')[0].firstChild.data))
fout.write('</tspan>\n')
fout.write('</text>\n')
else:
print("can't handle", element.tagName, "element")
#fout.write('</g>\n')
fout = open(sys.argv[1]+".svg", 'w')
doc = minidom.parse(sys.argv[1])
head = doc.firstChild
if head.tagName == 'DOMDocument':
extendedSwatchLists = head.getElementsByTagName('extendedSwatchLists')[0].childNodes
swatchLists = head.getElementsByTagName('swatchLists')[0].childNodes
timelines = head.getElementsByTagName('timelines')[0]
fout.write('<svg xmlns="http://www.w3.org/2000/svg" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" width="{0[width].value}" height="{0[height].value}" version="1.1">\n'.format(head.attributes))
fout.write('<sodipodi:namedview pagecolor="{0[backgroundColor].value}" inkscape:snap-bbox="{0[objectsSnapTo].value}" />\n'.format(head.attributes))
elif head.tagName == 'DOMSymbolItem':
extendedSwatchLists = []
swatchLists = []
timelines = head.getElementsByTagName('timeline')[0]
fout.write('<svg xmlns="http://www.w3.org/2000/svg" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" version="1.1">\n'.format(head.attributes))
swatchId = 0
fout.write('<defs>\n')
for swatchList in swatchLists:
if swatchList.nodeType == doc.TEXT_NODE: continue
assert swatchList.tagName == 'swatchList'
for swatches in swatchList.childNodes:
if swatches.nodeType == doc.TEXT_NODE: continue
assert swatches.tagName == 'swatches'
for swatchItem in swatches.childNodes:
if swatchItem.nodeType == doc.TEXT_NODE: continue
assert swatchItem.tagName == 'SolidSwatchItem'
fout.write('<linearGradient osb:paint="solid" id="swatch{0}">\n'.format(swatchId))
if 'color' in swatchItem.attributes:
fout.write('<stop offset="0" style="stop-color:{0}" />\n'.format(swatchItem.attributes['color'].value))
fout.write('</linearGradient>\n')
swatchId += 1
for swatchList in extendedSwatchLists:
if swatchList.nodeType == doc.TEXT_NODE: continue
assert swatchList.tagName == 'swatchList'
for swatches in swatchList.childNodes:
if swatches.nodeType == doc.TEXT_NODE: continue
assert swatches.tagName == 'swatches'
for swatchItem in swatches.childNodes:
if swatchItem.nodeType == doc.TEXT_NODE: continue
handleGradient(fout, swatchItem, 'swatch{0}'.format(swatchId), "gradient")
swatchId += 1
fout.write('</defs>\n')
timelineId = 0
frameId = 0
layerId = 0
gradientId = 0
for DOMTimeline in timelines.childNodes:
if DOMTimeline.nodeType == doc.TEXT_NODE: continue
assert DOMTimeline.tagName == 'DOMTimeline'
#fout.write('<g id="timeline{0}" inkscape:label={1} inkscape:groupmode="layer">\n'.format(timelineId, quoteattr(DOMTimeline.attributes['name'].value)))
layers = timelines.getElementsByTagName('layers')[0]
frameNames = None
for DOMLayer in layers.childNodes:
if DOMLayer.nodeType == doc.TEXT_NODE: continue
assert DOMLayer.tagName == 'DOMLayer'
fout.write('<g id="layer{0}" inkscape:label={1} inkscape:groupmode="layer">\n'.format(layerId, quoteattr(DOMLayer.attributes['name'].value)))
frames = DOMLayer.getElementsByTagName('frames')[0]
gridPosX = 0
if frameNames is not None:
frameIter = iter(frames.childNodes)
for (startIndex, endIndex), name in frameNames:
fout.write('<g id="frame{0}" inkscape:label={1} transform="translate({2}, 0)">\n'.format(frameId, quoteattr(name), gridPosX))
gridPosX += 100
gridPosY = 0
for i in range(startIndex, endIndex):
while True:
DOMFrame = next(frameIter)
if DOMFrame.nodeType != doc.TEXT_NODE: break
assert DOMFrame.tagName == 'DOMFrame'
assert i == int(DOMFrame.attributes['index'].value)
handleFrame(fout, DOMFrame)
gridPosY += 100
fout.write('</g>\n')
frameId += 1
else:
if DOMLayer.attributes['name'].value == "Label":
frameNames = []
frameNameKeys = []
for DOMFrame in frames.childNodes:
if DOMFrame.nodeType == doc.TEXT_NODE: continue
assert DOMFrame.tagName == 'DOMFrame'
if 'name' in DOMFrame.attributes:
name = DOMFrame.attributes['name'].value
else:
name = None
if DOMLayer.attributes['name'].value == "Label":
startIndex = int(DOMFrame.attributes['index'].value)
if 'duration' in DOMFrame.attributes:
endIndex = startIndex+int(DOMFrame.attributes['duration'].value)
else:
endIndex = startIndex+1
frameNames.append(((startIndex, endIndex), name))
frameNameKeys.append(startIndex)
fout.write('<g id="frame{0}"'.format(frameId))
if name is not None:
fout.write(' inkscape:label={0}'.format(quoteattr(name)))
fout.write(' transform="translate({0}, 0)">\n'.format(gridPosX))
gridPosX += 100
handleFrame(fout, DOMFrame)
fout.write('</g>\n')
frameId += 1
#break
fout.write('</g>\n')
layerId += 1
#fout.write('</g>\n')
timelineId += 1
fout.write('</svg>')
fout.close()
from xml.dom import minidom
import sys
from xml.sax.saxutils import quoteattr, escape
import math, numpy
class Scanner:
def __init__(self, data):
self.data = data
self.pos = 0
def scanChar(self):
c = self.data[self.pos]
self.pos += 1
return c
def scanDecimal(self):
start = self.pos
end = start
while True:
if end >= len(self.data):
break
if not self.data[end].isdigit() and self.data[end] != '.' and self.data[end] != '-':
break
end += 1
if start == end:
return None
else:
self.pos = end
return float(self.data[start:end])
def hasData(self):
return self.pos < len(self.data)
def scanHex(self):
start = self.pos
if self.data[start] != '#':
return None
start += 1
end = start
while self.data[end] != '.': end += 1
wholePart = int(self.data[start:end], 16)
sign = -1 if wholePart&0x80000000 else 1
if sign == -1: wholePart = (~wholePart)+1
start = end+1
end = start
while end < len(self.data) and self.data[end] in '0123456789ABCDEFabcdef': end += 1
fractionalPart = int(self.data[start:end], 16)
self.pos = end
return sign*(wholePart+(fractionalPart/(16**(end-start))))
def scanNumber(self):
self.skipWhitespace()
if self.data[self.pos] == '#':
return self.scanHex()
else:
return self.scanDecimal()
def skipWhitespace(self):
while self.pos < len(self.data) and self.data[self.pos].isspace(): self.pos += 1
class Edge: pass
class PathCommand:
def __init__(self, cmd, x, y, x1=None, y1=None):
self.cmd = cmd
self.x = x
self.y = y
self.x1 = x1
self.y1 = y1
def __str__(self):
if self.cmd == 'Q':
return "{0} {1},{2} {3},{4}".format(self.cmd, self.x1, self.y1, self.x, self.y)
else:
return "{0} {1},{2}".format(self.cmd, self.x, self.y)
def matrixToTransform(el):
if 'a' in el.attributes and 'd' in el.attributes:
if 'b' not in el.attributes: el.attributes['b'] = '0'
if 'c' not in el.attributes: el.attributes['c'] = '0'
return 'matrix({0[a].value},{0[b].value},{0[c].value},{0[d].value},{0[tx].value},{0[ty].value})'.format(el.attributes)
if 'tx' in el.attributes and 'ty' in el.attributes:
return 'translate({0[tx].value},{0[ty].value})'.format(el.attributes)
def reversePath(path):
last = None
for command in reversed(path):
if last is None:
yield PathCommand('M', command.x, command.y)
else:
yield PathCommand(last.cmd, command.x, command.y, last.x1, last.y1)
last = command
def distance(x1, y1, x2, y2):
return math.sqrt((x1-x2)**2 + (y1-y2)**2)
def handleGradient(fout, style, nodeId, osbPaint=None):
if style.tagName.startswith('RadialGradient'):
fout.write('<radialGradient')
elif style.tagName.startswith('LinearGradient'):
fout.write('<linearGradient')
else:
raise Exception("Unknown gradient type {0}".format(style.tagName))
fout.write(' id="{0}"'.format(nodeId))
matrix = style.getElementsByTagName('matrix')
if len(matrix) > 0:
Matrix = matrix[0].getElementsByTagName('Matrix')[0]
fout.write(' gradientUnits="userSpaceOnUse"')
if 1:
fout.write(' gradientTransform="{0}"'.format(matrixToTransform(Matrix)))
if style.tagName.startswith('LinearGradient'):
fout.write(' x1="0" y1="0"')
fout.write(' x2="100%" y2="0"')
else:
fout.write(' cx="0" cy="0"')
fout.write(' r="100%"')
else:
a = float(Matrix.attributes['a'].value) if 'a' in Matrix.attributes else 0
b = float(Matrix.attributes['b'].value) if 'b' in Matrix.attributes else 0
c = float(Matrix.attributes['c'].value) if 'c' in Matrix.attributes else 0
d = float(Matrix.attributes['d'].value) if 'd' in Matrix.attributes else 0
tx = float(Matrix.attributes['tx'].value) if 'tx' in Matrix.attributes else 0
ty = float(Matrix.attributes['ty'].value) if 'ty' in Matrix.attributes else 0
mat = [[a*1000, b*1000, tx], [c*1000, d*1000, ty], [0, 0, 1]]
x1, y1, w = numpy.matmul(mat, [0, 0, 1])
x2, y2, w = numpy.matmul(mat, [1, 0, 1])
if style.tagName.startswith('LinearGradient'):
fout.write(' x1="{0}" y1="{1}"'.format(x1, y1))
fout.write(' x2="{0}" y2="{1}"'.format(x2, y2))
else:
r = distance(x1, y1, x2, y2)
fout.write(' cx="{0}" cy="{1}"'.format(x1, y1))
fout.write(' r="{1}"'.format(r))
if osbPaint is not None:
fout.write(' osb:paint="{0}"'.format(osbPaint))
fout.write('>\n')
for GradientEntry in style.getElementsByTagName('GradientEntry'):
fout.write('<stop offset="{0}" style="'.format(GradientEntry.attributes['ratio'].value))
if 'color' in GradientEntry.attributes: fout.write('stop-color:{0};'.format(GradientEntry.attributes['color'].value))
if 'alpha' in GradientEntry.attributes: fout.write('stop-opacity:{0};'.format(GradientEntry.attributes['alpha'].value))
fout.write('" />\n')
if style.tagName.startswith('RadialGradient'):
fout.write('</radialGradient>\n')
elif style.tagName.startswith('LinearGradient'):
fout.write('</linearGradient>\n')
def handleFillStyle(fout, style, fillType):
global gradientId
if style.tagName == 'SolidColor':
if 'color' in style.attributes: return fillType+':'+style.attributes['color'].value+';'
else: return 'fill:none;'
elif style.tagName == 'RadialGradient' or style.tagName == 'LinearGradient':
#handleGradient(fout, style, 'gradient{0}'.format(gradientId))
#result = fillType+':url(#gradient{0});'.format(gradientId)
#gradientId += 1
#return result
return fillType+':#0000FF;'
else:
print("can't handle", style.tagName, fillType, "style type")
return ''
def handleFrame(fout, DOMFrame):
#fout.write('<g>\n')
for element in DOMFrame.getElementsByTagName('elements')[0].childNodes:
if element.nodeType == doc.TEXT_NODE: continue
if element.tagName == 'DOMShape':
fout.write('<g transform="translate(0, {0})">\n'.format(gridPosY))
fillStyles = {}
fills = element.getElementsByTagName('fills')
if len(fills) > 0:
for FillStyle in fills[0].childNodes:
if FillStyle.nodeType == doc.TEXT_NODE: continue
assert FillStyle.tagName == 'FillStyle'
styleIndex = FillStyle.attributes['index'].value
style = [node for node in FillStyle.childNodes if node.nodeType != doc.TEXT_NODE][0]
fillStyles[styleIndex] = handleFillStyle(fout, style, 'fill')
strokeStyles = {}
strokes = element.getElementsByTagName('strokes')
if len(strokes) > 0:
for StrokeStyle in strokes[0].childNodes:
if StrokeStyle.nodeType == doc.TEXT_NODE: continue
assert StrokeStyle.tagName == 'StrokeStyle'
styleIndex = StrokeStyle.attributes['index'].value
SolidStroke = [node for node in StrokeStyle.childNodes if node.nodeType != doc.TEXT_NODE][0]
assert SolidStroke.tagName == 'SolidStroke'
strokeStyleStr = 'stroke-width:'+SolidStroke.attributes['weight'].value+';'
if 'sharpCorners' in SolidStroke.attributes: strokeStyleStr += 'stroke-linejoin:'+{'true': 'miter', 'false': 'round'}[SolidStroke.attributes['sharpCorners'].value]+';'
fillStyle = [node for node in SolidStroke.getElementsByTagName('fill')[0].childNodes if node.nodeType != doc.TEXT_NODE][0]
strokeStyleStr += handleFillStyle(fout, fillStyle, 'stroke')
strokeStyles[styleIndex] = strokeStyleStr
edges = []
for EdgeEl in element.getElementsByTagName('edges')[0].childNodes:
if EdgeEl.nodeType == doc.TEXT_NODE: continue
assert EdgeEl.tagName == 'Edge'
if 'edges' not in EdgeEl.attributes: continue
s = Scanner(EdgeEl.attributes["edges"].value)
edge = Edge()
if "fillStyle0" in EdgeEl.attributes:
edge.fillStyle0 = EdgeEl.attributes["fillStyle0"].value
else:
edge.fillStyle0 = None
if "fillStyle1" in EdgeEl.attributes:
edge.fillStyle1 = EdgeEl.attributes["fillStyle1"].value
else:
edge.fillStyle1 = None
if "strokeStyle" in EdgeEl.attributes:
edge.strokeStyle = EdgeEl.attributes["strokeStyle"].value
else:
edge.strokeStyle = None
edge.d = []
x = y = None
clockwise = 0.0
while s.hasData():
command = s.scanChar()
if command == "!":
# move to
newX, newY = s.scanNumber()/20, s.scanNumber()/20
if x is None and y is None:
firstX, firstY = newX, newY
else:
clockwise += (newX-x)*(newY+y)
if x != newX or y != newY:
x, y = newX, newY
edge.d.append(PathCommand('M', x, y))
elif command == "|":
# line to
newX, newY = s.scanNumber()/20, s.scanNumber()/20
clockwise += (newX-x)*(newY+y)
x, y = newX, newY
edge.d.append(PathCommand('L', x, y))
elif command == "[":
# quadratic spline
cx, cy = s.scanNumber()/20, s.scanNumber()/20
newX, newY = s.scanNumber()/20, s.scanNumber()/20
clockwise += (newX-x)*(newY+y)
x, y = newX, newY
edge.d.append(PathCommand('Q', x, y, cx, cy))
elif not command.isspace():
print("unknown command", command, EdgeEl.toxml())
#clockwise += (firstX-x)*(firstY+y)
edge.clockwise = clockwise > 0
#edge.d.append('z')
edges.append(edge)
for styleIndex, fillStyle in fillStyles.items():
fout.write('<path style="fill-rule:evenodd;')
fout.write(fillStyle)
fout.write('" d="')
x = y = None
startX = startY = None
fillEdges = edges[:]
while len(fillEdges) > 0:
edge = fillEdges.pop(0)
if edge.fillStyle0 == edge.fillStyle1:
# pure stroke
continue
if edge.fillStyle0 == styleIndex:
path = edge.d
elif edge.fillStyle1 == styleIndex:
path = list(reversePath(edge.d))
if edge.fillStyle0 == styleIndex or edge.fillStyle1 == styleIndex:
if startX is None: startX = path[0].x
if startY is None: startY = path[0].y
if path[0].x == x and path[0].y == y: path = path[1:]
fout.write(' '.join(map(str, path))+' ')
x, y = path[-1].x, path[-1].y
fillEdges.sort(key=lambda edge2: distance(edge2.d[0].x, edge2.d[0].y, x, y) if edge2.fillStyle0 == styleIndex else distance(edge2.d[-1].x, edge2.d[-1].y, x, y))
if x == startX and y == startY: fout.write('z')
fout.write('" />\n')
for styleIndex, strokeStyle in strokeStyles.items():
fout.write('<path style="fill:none;')
fout.write(strokeStyle)
fout.write('" d="')
x = y = None
startX = startY = None
fillEdges = edges[:]
while len(fillEdges) > 0:
edge = fillEdges.pop(0)
if edge.strokeStyle == styleIndex:
path = edge.d
if startX is None: startX = path[0].x
if startY is None: startY = path[0].y
if path[0].x == x and path[0].y == y: path = path[1:]
fout.write(' '.join(map(str, path))+' ')
x, y = path[-1].x, path[-1].y
fillEdges.sort(key=lambda edge2: distance(edge2.d[0].x, edge2.d[0].y, x, y))
if x == startX and y == startY: fout.write('z')
fout.write('" />\n')
fout.write('</g>\n')
elif element.tagName == 'DOMStaticText':
fout.write('<text transform="{0}">\n'.format(matrixToTransform(element.getElementsByTagName("matrix")[0].getElementsByTagName("Matrix")[0])))
for DOMTextRun in element.getElementsByTagName('textRuns')[0].childNodes:
if DOMTextRun.nodeType == doc.TEXT_NODE: continue
assert DOMTextRun.tagName == 'DOMTextRun'
fout.write('<tspan style="')
textAttrs = DOMTextRun.getElementsByTagName('textAttrs')[0]
DOMTextAttrs = textAttrs.getElementsByTagName('DOMTextAttrs')[0]
for textAttrName, textAttrValue in DOMTextAttrs.attributes.items():
if textAttrName == 'alignment':
if textAttrValue == 'center':
fout.write('text-anchor:middle;text-align:center;')
elif textAttrValue == 'left': # TODO: bidi
fout.write('text-anchor:start;text-align:start;')
elif textAttrValue == 'right':
fout.write('text-anchor:end;text-align:end;')
elif textAttrName == 'size': fout.write('font-size:{0};'.format(textAttrValue))
elif textAttrName == 'face': fout.write('font-family:{0};'.format(textAttrValue))
elif textAttrName == 'fillColor': fout.write('fill:{0};'.format(textAttrValue))
fout.write('">')
fout.write(escape(DOMTextRun.getElementsByTagName('characters')[0].firstChild.data))
fout.write('</tspan>\n')
fout.write('</text>\n')
else:
print("can't handle", element.tagName, "element")
#fout.write('</g>\n')
fout = open(sys.argv[1]+".svg", 'w')
doc = minidom.parse(sys.argv[1])
head = doc.firstChild
if head.tagName == 'DOMDocument':
extendedSwatchLists = head.getElementsByTagName('extendedSwatchLists')[0].childNodes
swatchLists = head.getElementsByTagName('swatchLists')[0].childNodes
timelines = head.getElementsByTagName('timelines')[0]
fout.write('<svg xmlns="http://www.w3.org/2000/svg" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" width="{0[width].value}" height="{0[height].value}" version="1.1">\n'.format(head.attributes))
fout.write('<sodipodi:namedview pagecolor="{0[backgroundColor].value}" inkscape:snap-bbox="{0[objectsSnapTo].value}" />\n'.format(head.attributes))
elif head.tagName == 'DOMSymbolItem':
extendedSwatchLists = []
swatchLists = []
timelines = head.getElementsByTagName('timeline')[0]
fout.write('<svg xmlns="http://www.w3.org/2000/svg" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" version="1.1">\n'.format(head.attributes))
swatchId = 0
fout.write('<defs>\n')
for swatchList in swatchLists:
if swatchList.nodeType == doc.TEXT_NODE: continue
assert swatchList.tagName == 'swatchList'
for swatches in swatchList.childNodes:
if swatches.nodeType == doc.TEXT_NODE: continue
assert swatches.tagName == 'swatches'
for swatchItem in swatches.childNodes:
if swatchItem.nodeType == doc.TEXT_NODE: continue
assert swatchItem.tagName == 'SolidSwatchItem'
fout.write('<linearGradient osb:paint="solid" id="swatch{0}">\n'.format(swatchId))
if 'color' in swatchItem.attributes:
fout.write('<stop offset="0" style="stop-color:{0}" />\n'.format(swatchItem.attributes['color'].value))
fout.write('</linearGradient>\n')
swatchId += 1
for swatchList in extendedSwatchLists:
if swatchList.nodeType == doc.TEXT_NODE: continue
assert swatchList.tagName == 'swatchList'
for swatches in swatchList.childNodes:
if swatches.nodeType == doc.TEXT_NODE: continue
assert swatches.tagName == 'swatches'
for swatchItem in swatches.childNodes:
if swatchItem.nodeType == doc.TEXT_NODE: continue
handleGradient(fout, swatchItem, 'swatch{0}'.format(swatchId), "gradient")
swatchId += 1
fout.write('</defs>\n')
timelineId = 0
frameId = 0
layerId = 0
gradientId = 0
for DOMTimeline in timelines.childNodes:
if DOMTimeline.nodeType == doc.TEXT_NODE: continue
assert DOMTimeline.tagName == 'DOMTimeline'
#fout.write('<g id="timeline{0}" inkscape:label={1} inkscape:groupmode="layer">\n'.format(timelineId, quoteattr(DOMTimeline.attributes['name'].value)))
layers = timelines.getElementsByTagName('layers')[0]
frameNames = None
for DOMLayer in layers.childNodes:
if DOMLayer.nodeType == doc.TEXT_NODE: continue
assert DOMLayer.tagName == 'DOMLayer'
fout.write('<g id="layer{0}" inkscape:label={1} inkscape:groupmode="layer">\n'.format(layerId, quoteattr(DOMLayer.attributes['name'].value)))
frames = DOMLayer.getElementsByTagName('frames')[0]
gridPosX = 0
if frameNames is not None:
frameIter = iter(frames.childNodes)
for (startIndex, endIndex), name in frameNames:
fout.write('<g id="frame{0}" inkscape:label={1} transform="translate({2}, 0)">\n'.format(frameId, quoteattr(name), gridPosX))
gridPosX += 100
gridPosY = 0
for i in range(startIndex, endIndex):
while True:
DOMFrame = next(frameIter)
if DOMFrame.nodeType != doc.TEXT_NODE: break
assert DOMFrame.tagName == 'DOMFrame'
assert i == int(DOMFrame.attributes['index'].value)
handleFrame(fout, DOMFrame)
gridPosY += 100
fout.write('</g>\n')
frameId += 1
else:
if DOMLayer.attributes['name'].value == "Label":
frameNames = []
frameNameKeys = []
for DOMFrame in frames.childNodes:
if DOMFrame.nodeType == doc.TEXT_NODE: continue
assert DOMFrame.tagName == 'DOMFrame'
if 'name' in DOMFrame.attributes:
name = DOMFrame.attributes['name'].value
else:
name = None
if DOMLayer.attributes['name'].value == "Label":
startIndex = int(DOMFrame.attributes['index'].value)
if 'duration' in DOMFrame.attributes:
endIndex = startIndex+int(DOMFrame.attributes['duration'].value)
else:
endIndex = startIndex+1
frameNames.append(((startIndex, endIndex), name))
frameNameKeys.append(startIndex)
fout.write('<g id="frame{0}"'.format(frameId))
if name is not None:
fout.write(' inkscape:label={0}'.format(quoteattr(name)))
fout.write(' transform="translate({0}, 0)">\n'.format(gridPosX))
gridPosX += 100
handleFrame(fout, DOMFrame)
fout.write('</g>\n')
frameId += 1
#break
fout.write('</g>\n')
layerId += 1
#fout.write('</g>\n')
timelineId += 1
fout.write('</svg>')
fout.close()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment