Skip to content

Instantly share code, notes, and snippets.

@larscwallin
Last active October 13, 2015 23:18
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save larscwallin/4271123 to your computer and use it in GitHub Desktop.
Save larscwallin/4271123 to your computer and use it in GitHub Desktop.
Bansai! exports SVG from wonderous Inkscape to BonsaiJS template code. Still in Alpha but works as a proof of concept :)
Bansai!
* What is Bansai?
This is a script extension for Inkscape.It was inspired by the splendid BonsaiJS library. As well as
my lazy disposition, which does not like "coding" graphics ;)
So in short Bansai lets you select one or elements in Inkscape, and export them to BonsaiJS JSON notation.
* What is supported by this version?
A little, and still a lot :) At the moment I have had time to implement support for
- Path elements
- Group elements (also nested)
- Transformation matrix
- Filters
- Gradients
These initial features can get you pretty far as most, or all, SVG shapes can be described using
one or more of the path types available.
* What is NOT supported by this version?
A lot of course, such as
- SVG shapes such as Rect and Ellipse
- Fonts and Text
Thanks to Bonsai its really easy for anyone with a basic knowledge of Python,
JavaScript and SVG to help with development :)
Update 20121221 17:30
Removed the "feature" that made toggeling of layer visibility neccesary. This however will
make all elements, also on hidden layers, present in the exported json.
Also fixed a file system bug thanks to Tobias
--------------------------------------------------------------------------------------------------
Update 20121221 16:25
Fixed some silly bugs. Now opacity works for fill and stroke again :)
--------------------------------------------------------------------------------------------------
Update 20121221 15:48
Started implementing gradient support. Lots of work compared to the previous stuff!
Help would be awesome :D
--------------------------------------------------------------------------------------------------
Update 20121219 19:06
Added blur filter support to the Bansai js code :)
--------------------------------------------------------------------------------------------------
Update 20121219
Gradients added to json output:
{
'radialGradient':{
'fx':'281.42856',
'fy':'323.79074',
'stops':[
{
'stop-color':'#000000',
'stop-opacity':'1',
'id':'stop840',
'offset':'0'
},
{
'stop-color':'#000000',
'stop-opacity':'0.49803922',
'id':'stop848',
'offset':'0.35054708'
},
{
'stop-color':'#000000',
'stop-opacity':'0',
'id':'stop842',
'offset':'1'
}
],
'gradientUnits':'userSpaceOnUse',
'collect':'always',
'cy':'323.79074',
'cx':'281.42856',
'gradientTransform':[
[
0.96764083999999995,
0.08781738,
-19.327677000000001
],
[
-0.076970060000000007,
1.1040095999999999,
-12.015768
]
],
'r':'95.714287',
'id':'radialGradient850'
}
}
--------------------------------------------------------------------------------------------------
Update 20121218 20:50
Implemented inclusion of filters:
{
'filter':{
'feTurbulence':{
'baseFrequency':'0.2',
'seed':'0',
'result':'result7',
'numOctaves':'1',
'type':'fractalNoise',
'id':'feTurbulence853'
},
'feGaussianBlur':{
'result':'result91',
'stdDeviation':'8',
'id':'feGaussianBlur869',
'in':'result0'
},
'feOffset':{
'result':'result4',
'id':'feOffset835',
'dx':'0',
'dy':'1.4'
},
'feMorphology':{
'result':'result0',
'radius':'4',
'id':'feMorphology867',
'in':'fbSourceGraphic'
},
'feConvolveMatrix':{
'divisor':'6.03',
'kernelMatrix':'0 -1 0 0 -1 0 0 2 0',
'order':'3 3',
'result':'result1',
'in':'fbSourceGraphic',
'id':'feConvolveMatrix823'
}
}
--------------------------------------------------------------------------------------------------
Update 20121216 23:07
Fixed crash in py code when retrieving fill color for paths.
--------------------------------------------------------------------------------------------------
Update 20121216 22:42
Separated out the Bansai code into its own "static" closure. This to make it more reusable.
--------------------------------------------------------------------------------------------------
Update 20121214 16:37
Added indexing of all added Bonsai objects. This makes it possible to reference them later in
your script like this:
var tree = stage.options.lookup.labels['tree1'];
or
var tree = stage.options.lookup.ids['g7816'];
And you get a reference back to that instance.
This means that you can preserve your Inkscape layer/shape/group naming in you script. Neat.... :)
You JS console will also output all the contents of these two indexes.
--------------------------------------------------------------------------------------------------
Update 20121214 15:23
Externalized the template HTML code to a separate file. This should be placed, along with the
other files, in the Inkscape/share/extensions/ folder.
--------------------------------------------------------------------------------------------------
Update 20121213 10:51
Added check to make sure that bounding boxes are never "None" but instead empty arrays.
Update 20121213 10:21
I'm such a dork. I forgot to add transform support for Path elements.
Fixed now though :)
<inkscape-extension>
<_name>Bansai!</_name>
<id>com.larscwallin.bansai</id>
<dependency type="executable" location="extensions">larscwallin.inx.bansai.py</dependency>
<dependency type="executable" location="extensions">inkex.py</dependency>
<param name="where" type="string" _gui-text="Where to save the resulting JS file?"></param>
<param name="reposition" type="boolean" _gui-text="Reposition each selected element to 0,0?">false</param>
<param name="viewresult" type="boolean" _gui-text="Do you wish to view the result?">true</param>
<effect>
<object-type>all</object-type>
<effects-menu>
<submenu _name="Export"/>
</effects-menu>
</effect>
<script>
<command reldir="extensions" interpreter="python">larscwallin.inx.bansai.py</command>
</script>
</inkscape-extension>
#!/usr/bin/env python
import inkex
import simpletransform
import simplepath
import os.path
from simplestyle import *
from time import gmtime, strftime
import string
import webbrowser
import threading
from optparse import OptionParser
# This line below is only needed if you don't put the script directly into
# the installation directory
# sys.path.append('/usr/share/inkscape/extensions')
"""
Bansai! version 0.00001
* What is Bansai?
This is a script extension for Inkscape.It was inspired by the splendid BonsaiJS library. As well as
my lazy disposition, which does not like "coding" graphics ;)
So in short Bansai lets you select one or elements in Inkscape, and export them to BonsaiJS JSON notation.
"""
class SVGElement():
id=''
label=''
box=[]
path=[]
fill=''
fill_opacity=1
stroke=''
stroke_width=0
stroke_opacity=1
transform=''
x=0
y=0
width=0
height=0
nodeRef = None
def __init__(self,node = None):
self.nodeRef = node
class SVGPath(SVGElement):
def __init__(self,node = None):
SVGElement.__init__(self)
class SVGRect(SVGElement):
def __init__(self,node = None):
SVGElement.__init__(self)
class SVGArc(SVGElement):
cx=0
cy=0
rx=0
ry=0
def __init__(self,node = None):
SVGElement.__init__(self)
class SVGGroup(SVGElement):
items=[]
def __init__(self,node = None):
SVGElement.__init__(self)
"""
This is where the actual fun stuff starts
"""
# Effect main class
class Bansai(inkex.Effect):
json_output = []
debug_tab =' '
svg_doc = None
svg_doc_width = ''
svg_doc_height = ''
svg_file = ''
bonsaistyle = False
reposition = True
parsing_context = ''
parse_stack = []
def __init__(self):
"""
Constructor.
"""
"""
First we grab the input parameters that we got from the Inkscape plugin system (see the .inx file)
"""
inkex.Effect.__init__(self)
self.OptionParser.add_option('--where', action = 'store',
type = 'string', dest = 'where', default = '',
help = 'Where to save the resulting file?')
self.OptionParser.add_option('--reposition', action = 'store',
type = 'inkbool', dest = 'reposition', default = False,
help = 'Reposition elements to 0,0?')
self.OptionParser.add_option('--bonsaistyle', action = 'store',
type = 'inkbool', dest = 'bonsaistyle', default = False,
help = 'Should the output be BonsaiJS specific?')
self.OptionParser.add_option('--viewresult', action = 'store',
type = 'inkbool', dest = 'viewresult', default = True,
help = 'Do you want to view the result?')
def effect(self):
"""
Effect behaviour.
Overrides base class method
"""
self.svg_file = self.args[-1]
self.svg_doc = self.document.xpath('//svg:svg',namespaces=inkex.NSS)[0]
self.svg_doc_width = inkex.unittouu(self.svg_doc.get('width'))
self.svg_doc_height = inkex.unittouu(self.svg_doc.get('height'))
self.where = self.options.where
self.reposition = self.options.reposition
self.bonsaistyle = self.options.bonsaistyle
self.viewresult = self.options.viewresult
filename = ''
success = False
#inkex.debug("filter:url(#g4567)".split('filter:url(#')[1].split(')'))
self.getselected()
if(self.selected.__len__() > 0):
#inkex.debug(self.debug_tab + 'Elements selected\n');
self.json_output.append({
'defs':{
'filters':[],
'fonts':[],
'gradients':[]
},
'elements':[]
})
parent = self.json_output[0]['elements']
selected = []
layers = self.document.xpath('//svg:svg/svg:g/*',namespaces=inkex.NSS)
# Iterate through all selected elements
for element in self.selected.values():
selected.append(element.get('id'))
#inkex.debug(self.debug_tab + 'selected ' + element.get('id'))
for element in layers:
#inkex.debug(self.debug_tab + 'Looping element ' + element.get('id'))
if(element.get('id') in selected):
self.parseElement(element,parent)
#inkex.debug(self.debug_tab + 'found ' + element.get('id'))
self.debug_tab = self.debug_tab[:-4]
else:
#inkex.debug(self.debug_tab + 'No elements were selected')
#layers = self.document.xpath('//svg:svg/svg:g[@style!="display:none"]',namespaces=inkex.NSS)
layers = self.document.xpath('//svg:svg/svg:g',namespaces=inkex.NSS)
self.json_output.append({
'svg':'document',
'id':'',
'name':'',
'transform':'',
'box':[
0,
self.svg_doc_width,
0,
self.svg_doc_height
],
'defs':{
'filters':[],
'fonts':[],
'gradients':[]
},
'elements':[]
})
parent = self.json_output[0]['elements']
# Iterate through all selected elements
for element in layers:
self.parseElement(element,parent)
self.debug_tab = self.debug_tab[:-4]
#inkex.debug(self.debug_tab + '\nDone iterating.\n')
#inkex.debug(self.debug_tab + ','.join([str(el) for el in self.json_output]))
if(self.where!=''):
# The easiest way to name rendered elements is by using their id since we can trust that this is always unique.
time_stamp = strftime('%a%d%b%Y%H%M', gmtime())
filename = os.path.join(self.where, 'bansai-'+time_stamp+'.html')
content = self.templateOutput('larscwallin.inx.bansai.template.html','{/*bonsai_content*/}')
success = self.saveToFile(content,filename)
if(success and self.viewresult):
self.viewOutput(filename)
else:
inkex.debug('Unable to write to file "' + filename + '"')
#inkex.debug(self.debug_tab + ','.join([str(el) for el in self.json_output]))
def normalizeSVG(self):
pass
def parseGroup(self,node,parent):
#inkex.debug(self.debug_tab + 'Parsing group' + node.get('id'))
self.debug_tab += ' '
self.parsing_context = 'g'
id = node.get('id')
transform = simpletransform.parseTransform(node.get('transform',''))
label = str(node.get(inkex.addNS('label', 'inkscape'),''))
elements = node.xpath('./*',namespaces=inkex.NSS)
box = simpletransform.computeBBox(elements)
box = list(box) if box != None else []
group = {
'id':id,
'name':label,
'svg':'g',
'transform':transform,
'box':box,
'elements':[]
}
parent.append(group)
self.parse_stack.append(group)
#inkex.debug('Loop through all grouped elements')
for child in elements:
self.parseElement(child,group["elements"])
self.debug_tab = self.debug_tab[:-4]
self.parsing_context = ''
self.parse_stack.pop()
def parseElement(self,node,parent):
type = node.get(inkex.addNS('type', 'sodipodi'))
if(type == None):
#remove namespace data {....}
tag_name = node.tag
tag_name = tag_name.split('}')[1]
else:
tag_name = str(type)
id = node.get('id')
#inkex.debug(self.debug_tab + 'Got "' + tag_name + '" element ' + id);
if(tag_name == 'g'):
self.parseGroup(node,parent)
elif(tag_name == 'path'):
self.parsePath(node,parent)
elif(tag_name == 'arc'):
self.parsePath(node,parent)
elif(tag_name == 'rect'):
self.parseRect(node,parent)
def parseStyleAttribute(self,str):
#inkex.debug(self.debug_tab + 'Got style ' + str)
rules = str.split(';')
parsed_set = {}
result = ''
for rule in rules:
parts = rule.split(':')
if(len(parts) > 1):
key = self.camelConvert(parts[0])
val = self.camelConvert(parts[1])
if(key== 'filter'):
parsed_set['filter'] = self.parseFilter(val)
elif(key == 'fill' and val.find('url(#') > -1):
parsed_set['fill-gradient'] = self.parseGradient(val)
elif(key == 'stroke' and val.find('url(#') > -1):
parsed_set['stroke-gradient'] = self.parseGradient(val)
else:
parsed_set[key] = val
result = parsed_set
return result
def parseFilter(self,filter_str):
# Split out the id from the url reference
filter_id = self.parseUrlParam(filter_str)
result_list = []
tag_name = ''
# Got a valid id?
if(filter_id!=''):
# Ok lets get the filter element which holds all actual instructions
filters = self.document.xpath('//svg:svg/svg:defs/svg:filter[@id="'+filter_id+'"]/*',namespaces=inkex.NSS)
for node in filters:
inkex.debug(self.debug_tab + 'Looping filter ' + node.get('id'))
tag_name = self.parseTagName(node.tag)
filter = {
'svg':tag_name
}
# Grab all the parameters and values for the current filter
for param,val in node.items():
param = self.parseTagName(param)
inkex.debug(self.debug_tab + 'param ' + param + ' val ' + val)
filter[param] = val
result_list.append(filter)
return result_list
def parsePath(self,node,parent):
#self.parsing_context = 'path'
style = node.get('style')
style = self.parseStyleAttribute(style)
transform = simpletransform.parseTransform(node.get('transform',''))
path_array = simplepath.parsePath(node.get('d'))
path = {
'id':node.get('id'),
'svg':'path',
'label':str(node.get(inkex.addNS('label', 'inkscape'),'')),
'box':list(simpletransform.computeBBox([node])),
'transform':transform,
'path':path_array,
'd':node.get('d',''),
'attr':{
'fillColor':style.get('fill',''),
'fillGradient':style.get('fillGradient',''),
'fillOpacity':style.get('fillOpacity','1'),
'opacity':style.get('opacity','1'),
'strokeColor':style.get('stroke',''),
'strokeGradient':style.get('strokeGradient',''),
'strokeWidth':style.get('strokeWidth','0'),
'strokeOpacity':style.get('strokeOpacity','1'),
'filters':style.get('filter','')
}
}
#inkex.debug('Path resides in group ' + self.parse_stack[len(self.parse_stack)-1]['id'])
if(self.reposition):
path['path'] = self.movePath(path,0,0,'tl')
else:
path['path'] = simplepath.formatPath(path_array)
path['box'] = list(path['box']) if path['box'] != None else []
parent.append(path)
"""
movePath changes a paths bounding box x,y extents to a new, absolute, position.
In other words, this function does not use translate for repositioning.
Note: The origin parameter is not currently used but will soon let you choose
which origin point (top left, top right, bottom left, bottom right, center)
to use.
"""
def movePath(self,node,x,y,origin):
path = node.get('path')
box = node.get('box')
#inkex.debug(box)
offset_x = (box[0] - x)
offset_y = (box[2] - (y))
#inkex.debug('Will move path "'+id+'" from x, y ' + str(box[0]) + ', ' + str(box[2]))
#inkex.debug('to x, y ' + str(x) + ', ' + str(y))
#inkex.debug('The x offset is ' + str(offset_x))
#inkex.debug('The y offset is = ' + str(offset_y))
for cmd in path:
params = cmd[1]
i = 0
while(i < len(params)):
if(i % 2 == 0):
#inkex.debug('x point at ' + str( round( params[i] )))
params[i] = (params[i] - offset_x)
#inkex.debug('moved to ' + str( round( params[i] )))
else:
#inkex.debug('y point at ' + str( round( params[i]) ))
params[i] = (params[i] - offset_y)
#inkex.debug('moved to ' + str( round( params[i] )))
i = i + 1
#inkex.debug(simplepath.formatPath(path))
return simplepath.formatPath(path)
def parseRect(self,node,parent):
#self.parsing_context = 'rect'
style = node.get('style')
style = self.parseStyleAttribute(style)
rect = {
'id':node.get('id',''),
'svg':'rect',
'label':str(node.get(inkex.addNS('label', 'inkscape'),'')),
'x': node.get('x',0),
'y': node.get('y',0),
'width':node.get('width',0),
'height':node.get('height',0),
'box':[],
'fill':style.get('fill',''),
'fillOpacity':style.get('fillOpacity',''),
'opacity':style.get('opacity',''),
'stroke':style.get('stroke',''),
'strokeWidth':style.get('strokeWidth',''),
'strokeOpacity':style.get('strokeOpacity',''),
'transform':node.get('transform','')
}
if(self.reposition):
self.x = 0
self.y = 0
parent.append(rect)
def parseArc(self,node,parent):
#self.parsing_context = 'arc'
style = node.get('style')
style = self.parseStyleAttribute(style)
arc = {
'id':node.get('id',''),
'svg':'arc',
'label':str(node.get(inkex.addNS('label', 'inkscape'),'')),
'cx': node.get(inkex.addNS('cx', 'sodipodi'),''),
'cy':node.get(inkex.addNS('cy', 'sodipodi'),''),
'rx':node.get(inkex.addNS('rx', 'sodipodi'),''),
'ry':node.get(inkex.addNS('ry', 'sodipodi'),''),
'path':simplepath.parsePath(node.get('d')),
'd':node.get('d',''),
'box':list(simpletransform.computeBBox([node])),
'fill':style.get('fill',''),
'fill-opacity':style.get('fillOpacity',''),
'stroke':style.get('stroke',''),
'stroke-width':style.get('strokeWidth',''),
'stroke-opacity':style.get('strokeOpacity',''),
'transform':node.get('transform','')
}
if(self.reposition):
arc['path'] = self.movePath(node,0,0,'tl')
else:
arc['path'] = arc['d']
parent.append(arc)
def parseDef(self,node,parent):
pass
def parseGradient(self,gradient_str):
# Split out the id from the url reference
gradient_use_id = self.parseUrlParam(gradient_str)
gradient_use = {}
gradient_href_id = ''
gradient_href = {}
result_list = {}
tag_name = ''
gradient_stops = []
gradient_params = {}
# Got a valid id?
if(gradient_use_id != ''):
#
gradient_use = self.document.xpath('//svg:svg/svg:defs/*[@id="'+ gradient_use_id +'"]',namespaces=inkex.NSS)[0]
tag_name = self.parseTagName(gradient_use.tag)
gradient_params['stops'] = []
#inkex.debug(self.debug_tab + 'Gradient ' + tag_name)
# Grab all the parameters and values for the current gradient
for param,val in gradient_use.items():
#inkex.debug(self.debug_tab + 'param ' + param + ' val ' + val)
param = self.parseTagName(param)
if(param == 'href'):
# Inkscape uses one-to-many rel for gradients. We need to get the base config
# element which has params for color and stops.
gradient_href_id = val.split('#')[1]
gradient_href = self.document.xpath('//svg:svg/svg:defs/*[@id="'+ gradient_href_id +'"]/*',namespaces=inkex.NSS)
#inkex.debug(self.debug_tab + 'href to ' + gradient_href_id)
#inkex.debug(self.debug_tab + 'Looping through ' + str(len(gradient_href)) + ' gradient parameter elements')
for node in gradient_href:
#inkex.debug(self.debug_tab + 'Current parameter element ' + node.tag)
gradient_stop = {}
for param,val in node.items():
param = self.parseTagName(param)
#inkex.debug(self.debug_tab + 'Looping through gradient parameter attributes of ' + param)
if(param == 'style'):
#inkex.debug(self.debug_tab + 'Parsing style ' + val)
gradient_stop.update(self.parseStyleAttribute(val))
else:
#inkex.debug(self.debug_tab + 'Adding param/value : ' + param + '/' + val)
gradient_stop[param] = val
#inkex.debug(self.debug_tab + 'Adding stop ' + gradient_stop['id'])
gradient_params['stops'].append(gradient_stop)
elif(param == 'gradientTransform'):
transform = simpletransform.parseTransform(val)
gradient_params[param] = transform
else:
gradient_params[param] = val
gradient_params['svg'] = tag_name
result_list = gradient_params
return result_list
"""
stops Array | Object Color stops in the form: `['red','yellow',...]` or `[['red', 0], ['green', 50], ['#FFF', 100]]` i.e. Sub-array [0] is color and [1] is percentage As an object: { 0: 'yellow', 50: 'red', 100: 'green' }
direction Number | String Direction in degrees or a string, one of: `top`, `left`, `right`, `bottom`, `top left`, `top right`, `bottom left`, `bottom right`
matrix Matrix <optional>
Matrix transform for gradient
repeat String <optional>
How many times to repeat the gradient
units String <optional>
Either 'userSpace' or 'boundingBox'.
stops Array Color stops in the form: `['red','yellow',...]` or `[['red', 0], ['green', 50], ['#FFF', 100]]` i.e. Sub-array [0] is color and [1] is percentage
r Number <optional>
Radius in percentage (default: 50)
cx Number <optional>
X coordinate of center of gradient in percentage (default: 50)
cy Number <optional>
Y coordinate of center of gradient in percentage (default: 50)
matrix Matrix <optional>
Matrix transform for gradient
repeat String <optional>
How many times to repeat the gradient
units String <optional>
Either 'userSpace' or 'boundingBox'.
"""
def parseFont(self,node,parent):
pass
def parseGlyph(self,node,parent):
pass
def pathToObject(self,node):
pass
def repositionGroupedElements(self, box, elements):
pass
def parseUrlParam(self,url):
return url.split('url(#')[1].split(')')[0]
def camelConvert(self,str):
camel = str.split('-')
if(len(camel) == 2):
str = camel[0] + camel[1].title()
return str
def parseTagName(self,tag):
tag_name = tag.split('}')
if(len(tag_name) > 1):
return tag_name[1]
else:
return tag
def viewOutput(self,url):
vwswli = VisitWebSiteWithoutLockingInkscape()
vwswli.url = url
vwswli.start()
def templateOutput(self,templateName = '',placeholder = ''):
if(placeholder == ''):
inkex.debug('Bonsai.templateOutput: Mandatory argument "placeholder" missing. Aborting.')
return False
if(templateName == ''):
inkex.debug('Bonsai.templateOutput: Mandatory argument "templateName" missing. Aborting.')
return False
FILE = open(templateName,'r')
if(FILE):
template = FILE.read()
FILE.close()
if(len(template) > 0):
content = ','.join([str(el) for el in self.json_output])
tplResult = string.replace(template,placeholder,content)
return tplResult
else:
inkex.debug('Bonsai.templateOutput: Empty template file "'+templateName+'". Aborting.')
return False
else:
inkex.debug('Bonsai.templateOutput: Unable to open template file "'+templateName+'". Aborting.')
return False
def saveToFile(self,content,filename):
FILE = open(filename,'w')
if(FILE):
FILE.write(content)
FILE.close()
return True
else:
inkex.debug('Bonsai.templateOutput: Unable to open output file "'+filename+'". Aborting.')
return False
class VisitWebSiteWithoutLockingInkscape(threading.Thread):
url = ''
def __init__(self):
threading.Thread.__init__ (self)
def run(self):
webbrowser.open('file://' + self.url)
# Create effect instance and apply it.
effect = Bansai()
effect.affect(output=False)
<!DOCTYPE html>
<html>
<head>
<title></title>
<meta http-equiv='Content-Type' content='text/html; charset=UTF-8'>
</head>
<body>
<script src='http://cdnjs.cloudflare.com/ajax/libs/bonsai/0.4.1/bonsai.min.js'></script>
<div id='stage'></div>
<script>
stage = document.getElementById('stage');
bonsai.run(stage, {
code: function() {
bansai = {
ids: {},
labels:{},
groups:{},
stage:null,
init:function(stage,svgDoc){
backgroundLayer = bansai.createBackgroundLayer();
bansai.stage = stage;
svgDoc.elements.forEach(function(el) {
if(el.svg === 'g'){
bansai.addGroup(el,backgroundLayer);
}else{
bansai.addPath(el,backgroundLayer);
}
});
console.log("Dump of indexed SVG label attributes:" + bansai.objToString(bansai.labels));
console.log("Dump of indexed SVG id attributes:" + bansai.objToString(bansai.ids));
},
createBackgroundLayer:function(){
return new Group().attr({x:0, y:0}).addTo(stage);
},
addGroup:function(node,parent){
var group = new Group();
node.elements.forEach(function(el){
if(el.svg === 'g'){
bansai.addGroup(el,group);
}else if(el.svg === 'path'){
bansai.addPath(el,group);
}
});
if(node.transform!==''){
m = new Matrix();
m.scale(node.transform[0][0],node.transform[1][1]);
m.rotate(node.transform[1][0]);
m.translate(node.transform[0][2],node.transform[1][2]);
group.attr('matrix',m);
}
bansai.ids[node.id] = group;
if (node.label !== '') {
if(!bansai.labels[node.label]){
bansai.labels[node.label] = [];
}
bansai.labels[node.label].push(group);
}
group.addTo(parent);
},
addPath:function(node,parent){
path = new Path(node.path).attr({
fillColor: node.attr.fillColor,
fillOpacity: node.attr.fillOpacity,
strokeColor: node.attr.strokeColor,
strokeWidth: node.attr.strokeWidth
});
if(node.transform!==''){
m = new Matrix();
m.scale(node.transform[0][0],node.transform[1][1]);
m.rotate(node.transform[1][0]);
m.translate(node.transform[0][2],node.transform[1][2]);
path.attr('matrix',m);
}
if(node.attr.filters!==''){
node.attr.filters.forEach(function(el) {
switch(el.svg){
case 'feGaussianBlur':
f = new filter.Blur(el.stdDeviation);
path.attr('filters',f);
break;
}
});
}
if(node.attr.fillGradient!==''){
//console.log('Got a fill gradient');
gradientType = node.attr.fillGradient.svg;
switch(gradientType){
case 'linearGradient':
//console.log('Type, linear gradient');
gradient = node.attr.fillGradient;
stops = [];
// Set up stops array
gradient.stops.forEach(function(el){
color = bansai.hexToRgba(el.stopColor,el.stopOpacity);
color = ('rgba('+color.r+','+color.g+','+color.b+','+color.a+')');
//console.log('stop color / offset ' + color + ' / ' + el.offset);
stops.push(color,el.offset);
});
bonsaiGradient = bonsai.gradient.linear('top',stops);
//console.log(bonsaiGradient);
path.attr({
fillGradient:bonsaiGradient
});
/*
fillGradient = bonsai.gradient.linear('left', [
['rgb(255,25,5)',20],
['green', 60],
['yellow',20]
]);
bonsai.Path.rect(400, 0, 100, 250).attr({
'fillGradient' : fillGradient
}).addTo(stage);
'fillGradient':{
'gradientUnits':'userSpaceOnUse',
'y2':'115.21932',
'svg':'linearGradient',
'stops':[
{
'stop-color':'#000000',
'stop-opacity':'1',
'id':'stop14',
'offset':'0'
},
{
'stop-color':'#000000',
'stop-opacity':'0',
'id':'stop16',
'offset':'1'
}
],
'x2':'400',
'collect':'always',
'y1':'489.50504',
'x1':'397.14285',
'id':'linearGradient18'
}
*/
break;
case 'radialGradient':
//console.log('Type, radial gradient');
/*
'fillGradient':{
'fx':'355.71429',
'fy':'308.07648',
'stops':[
{
'stop-color':'#000000',
'stop-opacity':'1',
'id':'stop14',
'offset':'0'
},
{
'stop-color':'#000000',
'stop-opacity':'0',
'id':'stop16',
'offset':'1'
}
],
'gradientUnits':'userSpaceOnUse',
'collect':'always',
'cy':'308.07648',
'cx':'355.71429',
'gradientTransform':[
[
1.0,
0.0,
0.0
],
[
0.0,
1.0370439,
-11.41234
]
],
'svg':'radialGradient',
'r':'154.25716',
'id':'radialGradient827'
}
*/
break;
default:
//console.log('Unrecognized gradient');
}
}
if(node.attr.strokeGradient!==''){
//console.log('Got a stroke gradient');
gradientType = node.attr.strokeGradient.svg;
switch(gradientType){
case 'linearGradient':
//console.log('Type, linear gradient');
break;
case 'radialGradient':
console.log('Type, radial gradient');
break;
default:
console.log('Unrecognized gradient');
}
}
bansai.ids[node.id] = path;
if (node.label !== '') {
if(!bansai.labels[node.label]){
bansai.labels[node.label] = [];
}
bansai.labels[node.label].push(path);
}
path.addTo(parent);
},
addFlowRoot:function (node,parent){
},
addFlowPara:function (node,parent){
},
addFlowRegion:function(node,parent){
},
getRotationAngle:function(coords){
if(coords.length < 4) return false;
var deg2rad = Math.PI/180;
var rad2deg = 180/Math.PI;
var dx = coords[0] - coords[1];
var dy = coords[2] - coords[3];
var rads = Math.atan2(dx,dy);
var degs = rads * rad2deg;
return degs;
},
setRotationAngle:function(coords, direction){
var resultCoords = [];
if(direction === "left"){
resultCoords = [100,0,0,0];
} else if(direction === "right"){
resultCoords = [0,100,0,0];
} else if(direction === "down"){
resultCoords = [0,0,0,100];
} else if(direction === "up"){
resultCoords = [0,0,100,0];
} else if(typeof direction === "number"){
var pointOfAngle = function(a) {
return {
x:Math.cos(a),
y:Math.sin(a)
};
};
var degreesToRadians = function(d) {
return (d * (180 / Math.PI));
};
var eps = Math.pow(2, -52);
var angle = (direction % 360);
var startPoint = pointOfAngle(degreesToRadians(180 - angle));
var endPoint = pointOfAngle(degreesToRadians(360 - angle));
if(startPoint.x <= 0 || Math.abs(startPoint.x) <= eps)
startPoint.x = 0;
if(startPoint.y <= 0 || Math.abs(startPoint.y) <= eps)
startPoint.y = 0;
if(endPoint.x <= 0 || Math.abs(endPoint.x) <= eps)
endPoint.x = 0;
if(endPoint.y <= 0 || Math.abs(endPoint.y) <= eps)
endPoint.y = 0;
resultCoords = [startPoint.x,endPoint.x,startPoint.y,endPoint.y];
}
return resultCoords;
},
objToString:function(obj) {
var str = '';
for (var p in obj) {
if (obj.hasOwnProperty(p)) {
str += p + '::' + obj[p] + '\n';
}
}
return "\n" + str;
},
hexToRgba:function(hex,alpha) {
// Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
hex = hex.replace(shorthandRegex, function(m, r, g, b) {
return r + r + g + g + b + b;
});
var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result ? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16),
a: alpha
} : null;
}
};
var svgDoc = {/*bonsai_content*/};
bansai.init(stage,svgDoc);
/*
Reference your shapes using their Inkscape label,
var cloud = bansai.labels['cloud'];
cloud.animate('10s',{x:250});
or by their id,
var sun = bansai.ids['g9675'];
*/
}
});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment