API Documentation for: NEXT
Show:

File:SVGExporter.js

/*
* SVGExporter
* Visit http://gskinner.com/ for documentation, updates & examples.
*
* Copyright ©2014 gskinner.com, inc.
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/

// http://tutorials.jenkov.com/svg/svg-transformation.html

(function () {
	"use strict";

	/**
	 * Description
	 * @class SVGExporter
	 * @constructor
	 **/
	var SVGExporter = function (stage) {
		this.initialize(stage);
	};
	var p = SVGExporter.prototype;
	
// shortcuts:
	var c = createjs;

	var att = function(el, attr, value) {
		el.setAttribute(attr, value);
		return el;
	};
	
	var atts = function(el, o) {
		for (var n in o) {
			el.setAttribute(n, o[n]);
		}
		return el;
	};
	
	var create = function(name, o) {
		return atts(document.createElementNS(SVGExporter.SVG_NS, name), o);
	};
	
	var add = function(parent, child) {
		if (!child) { return; }
		parent.appendChild(child);
		return child;
	};
	
	var mtx = null;
	
// static:
	SVGExporter.SVG_NS = "http://www.w3.org/2000/svg";
	SVGExporter.XLINK_NS = "http://www.w3.org/1999/xlink";
	SVGExporter.BLEND_MAP = {multiply:1, screen:1, overlay:1, darken:1, lighten:1, "color-dodge":1, "color-burn":1, "hard-light":1, "soft-light":1, difference:1, exclusion:1, hue:1, saturation:1, color:1, luminosity:1};
	
// properties:
	p.svg = null;
	p.stage = null;
	p.defs = null;
	p.uids = {};

// initialization:
	/**
	 * Initialization method.
	 * @method initialize
	 * @protected
	 **/
	p.initialize = function (stage) {
		this.stage = stage;
		mtx = stage.getMatrix();
		var svg = this.svg = create("svg");
		svg.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:xlink", SVGExporter.XLINK_NS);
		atts(svg, {width:stage.canvas.width, height:stage.canvas.height, style:"border: 1px solid black; overflow: hidden;"});
		add(svg, this.defs = create("defs"));
		add(svg, this.exportContainer(stage));
	};

// public properties:

// private properties:

// public methods:
	p.getUID = function(id) {
		if (this.uids[id] == null) {
			this.uids[id] = 0;
			return id;
		}
		return id+"_"+(++this.uids[id]);
	};
	
	// makes it easy to override this to add unsupported types:
	p.exportElement = function(o) {
		if (o instanceof c.Container) { return this.exportContainer(o); }
		if (o instanceof c.Bitmap) { return this.exportBitmap(o); }
		if (o instanceof c.Sprite) { return this.exportSprite(o); }
		if (o instanceof c.Shape) { return this.exportShape(o); }
	};

	p.exportContainer = function(o) {
		var group = this.addTransform(create("g"), o);
		for (var i= 0, l=o.getNumChildren(); i<l; i++) {
			add(group, this.exportElement(o.getChildAt(i)));
		}
		return group;
	};
	
	p.exportBitmap = function(o) {
		return this.addTransform(this._getImage(o.image), o);
	};
	
	p.exportSprite = function(o) {
		var ss = o.spriteSheet;
		var frame = ss.getFrame(o.currentFrame);
		var r = frame.rect;
		var sprite = this.addTransform(this._getImage(frame.image), o, -r.x - frame.regX, -r.y - frame.regY);
		var id = this.getUID(sprite.id+"_mask");
		var mask = create("clipPath", {id:id});
		add(mask, create("rect", {x: r.x, y: r.y, width: r.width, height: r.height}));
		add(this.defs, mask);
		att(sprite, "clip-path", "url(#"+id+")");
		return sprite;
	};
	
	p.exportShape = function(o) {
		var shape = this.addTransform(create("g"), o), G = c.Graphics;
		
		var q = o.graphics.getInstructions();
		var active = [], fill=null, stroke=null, strokeStyle=null, closed = false;
		
		for (var i= 0, l= q.length; i<l; i++) {
			var cmd = q[i], isStrokeOrPath = false;
			
			if (cmd instanceof G.Fill) {
				fill = cmd;
				isStrokeOrPath = closed = true;
			} else if (cmd instanceof G.Stroke) {
				stroke = cmd;
				isStrokeOrPath = closed = true;
			} else if (cmd instanceof G.StrokeStyle) {
				strokeStyle = cmd;
				isStrokeOrPath = closed = true;
			}
			if ((closed && !isStrokeOrPath) || i == l-1) {
				this.exportPath(active, this.getFillAndStroke(fill, stroke, strokeStyle), shape);
				active.length = 0;
				closed = false; 
			}
			if (!isStrokeOrPath) {
				active.push(cmd);
			}
		}
		
		return shape;
	};
	
	p.exportPath = function(arr, style, parent) {
		var o, G = c.Graphics;
		for (var i= 0, l=arr.length; i<l; i++) {
			var cmd = arr[i];
			if (cmd instanceof G.Rect) { o = create("rect", {x:cmd.x, y:cmd.y, width:cmd.w, height:cmd.h}); }
			else if (cmd instanceof G.Circle) { o = create("circle", {cx:cmd.x, cy:cmd.y, r:cmd.radius}); }
			else if (cmd instanceof G.Ellipse) { o = create("ellipse", {cx:cmd.x+cmd.w/2, cy:cmd.y+cmd.h/2, rx:cmd.w/2, ry:cmd.h/2}); }
			else if (cmd instanceof G.RoundRect) {
				if (cmd.radiusTL == cmd.radiusTR && cmd.radiusTL == cmd.radiusBR && cmd.radiusTL == cmd.radiusBL) { o = create("rect", {x:cmd.x, y:cmd.y, width:cmd.w, height:cmd.h, rx:cmd.radiusTL}); }
				else { /* TODO: complex drawRoundRect */ }
			}
			
			if (o) {
				if (parent) { add(parent, o); }
				if (style) { att(o, "style", style); }
			}
		}
	};
	
	p.getFillAndStroke = function(fill, stroke, strokeStyle) {
		var style = "";
		if (fill) { style += "fill:"+fill.style+";"; }
		if (stroke) { style += "stroke:"+stroke.style+";"; }
		if (strokeStyle) {
			if (strokeStyle.width != 1) { style += "stroke-width:"+strokeStyle.width+";"; }
			var caps = strokeStyle.caps == null ? "butt" : strokeStyle.caps;
			if (caps != "none") { style += "stroke-linecap:"+caps+";" }
			if (strokeStyle.joints && strokeStyle.joints != "miter") { style += "stroke-linejoin:"+strokeStyle.joints+";"; }
			else if (strokeStyle.miterLimit != 4) { style += "stroke-miterlimit:"+(strokeStyle.miterLimit?strokeStyle.miterLimit:10)+";"; }
			
		}
		return style;
	};
	
	p._getImage = function(image) {
		var bmp = create("image");
		bmp.setAttributeNS(SVGExporter.XLINK_NS, "xlink:href", image.src);
		atts(bmp, {width: image.width, height: image.height});
		return bmp;
	};
	
	p.addTransform = function(el, o, x, y) {
		if (o.alpha != 1) { att(el, "opacity", o.alpha.toFixed(4)); }
		if (!o.visible) { att(el, "display", "none"); }
		att(el, "id", this.getUID(o.name||"element"));
		var blend = SVGExporter.BLEND_MAP[o.compositeOperation];
		if (blend) {
			console.log(o.compositeOperation);
			att(el, "style", "mix-blend-mode:"+ o.compositeOperation);
		}
		
		mtx = o.getMatrix(mtx);
		if (x && y) { mtx.append(1,0,0,1,x,y); }
		if (mtx.isIdentity()) { return el; }
		att(el, "transform", "matrix("+[mtx.a.toFixed(4), mtx.b.toFixed(4), mtx.c.toFixed(4), mtx.d.toFixed(4), mtx.tx.toFixed(4), mtx.ty.toFixed(2)]+")");
		return el;
	};

// private methods:

	window.SVGExporter = SVGExporter;
})();