AUI.add('aui-drawing-base', function(A) {
var Lang = A.Lang,
	isArray = Lang.isArray,
	isFunction = Lang.isFunction,
	isNumber = Lang.isNumber,
	isObject = Lang.isObject,
	isString = Lang.isString,
	isUndefined = Lang.isUndefined,

	DOC = A.config.doc,

	ColorUtil = A.ColorUtil,
	Util,
	UA = A.UA,

	EMPTY_FN = Lang.emptyFn,

	MAP_ATTRS_AVAILABLE = {
		blur: 0,
		'clip-rect': '0 0 1e9 1e9',
		cursor: 'default',
		cx: 0,
		cy: 0,
		fill: '#fff',
		'fill-opacity': 1,
		font: '10px "Arial"',
		'font-family': '"Arial"',
		'font-size': '10',
		'font-style': 'normal',
		'font-weight': 400,
		gradient: 0,
		height: 0,
		href: 'http://alloy.liferay.com/',
		opacity: 1,
		path: 'M0,0',
		r: 0,
		rotation: 0,
		rx: 0,
		ry: 0,
		scale: '1 1',
		src: '',
		stroke: '#000',
		'stroke-dasharray': '',
		'stroke-linecap': 'butt',
		'stroke-linejoin': 'butt',
		'stroke-miterlimit': 0,
		'stroke-opacity': 1,
		'stroke-width': 1,
		target: '_blank',
		'text-anchor': 'middle',
		title: 'AlloyUI',
		translation: '0 0',
		width: 0,
		x: 0,
		y: 0
	},

	MAP_COMMAND_SVG_QUADRATIC = {
		Q: 1,
		T: 1
	},

	MAP_ELEMENTS = {
		circle: 1,
		ellipse: 1,
		image: 1,
		path: 1,
		rect: 1,
		text: 1
	},

	MAP_TYPES_IMAGE_TEXT = {
		image:1,
		text: 1
	},

	MAP_TYPES_CIRCLE_ELLIPSE = {
		circle: 1,
		ellipse: 1
	},

	MATH = Math,
	MATH_MAX = MATH.max,
	MATH_MIN = MATH.min,
	MATH_POW = MATH.pow,

	NAME = 'drawing',

	REGEX_GRADIENT_VALUES = /^([^:]*):?([\d\.]*)/,

	REGEX_ISURL = /^url\(['"]?([^\)]+?)['"]?\)$/i,

	REGEX_PATH_COMMAND = /([achlmqstvz])[\s,]*((-?\d*\.?\d*(?:e[-+]?\d+)?\s*,?\s*)+)/ig,
	REGEX_PATH_TO_STRING = /,?([achlmqrstvxz]),?/gi,
	REGEX_PATH_VALUES = /(-?\d*\.?\d*(?:e[-+]?\d+)?)\s*,?\s*/ig,

	REGEX_RADIAL_GRADIENT = /^r(?:\(([^,]+?)\s*,\s*([^\)]+?)\))?/,

	REGEX_SEPARATOR = /[, ]+/,
	REGEX_SEPARATOR_GRADIENT = /\s*\-\s*/,

	STR_EMPTY = '',
	STR_FILL = 'fill',

	STR_MS_PROG_ID_PREFIX = ' progid:DXImageTransform.Microsoft',

	STR_SPACE = ' ',

	TO_LOWER_CASE = String.prototype.toLowerCase,
	TO_UPPER_CASE = String.prototype.toUpperCase;

var Drawing = A.Component.create(
	{
		ATTRS: {
			height: {
				value: 342
			},
			width: {
				value: 512
			},
			x: {
				value: null
			},
			y: {
				value: null
			}
		},

		UI_ATTRS: ['x', 'y'],

		create: function(config) {
			var args = arguments;
			var firstArg = args[0];

			if (isArray(firstArg)) {
				args = args[0];

				config = Util.getConfig(args);

				var paper = new Drawing(config).render();

				var set = paper.createSet();
				var element;

				for (var i = 0, length = args.length; i < length; i++) {
					element = args[i] || {};

					if (MAP_ELEMENTS.hasOwnProperty(element.type)) {
						set.push(
							paper[element.type]().attr(element)
						);
					}
				}

				return paper;
			}
			else if (isObject(firstArg) && !(firstArg.nodeName || firstArg instanceof A.Node)) {
				config = firstArg;
			}
			else {
				config = Util.getConfig(args);
			}

			return new Drawing(config).render();
		},
		NAME: NAME,
		prototype: {
			CONTENT_TEMPLATE: null,

			renderUI: function() {
				var instance = this;

				instance.customAttributes = {};

				Drawing.Impl.createCanvas.call(instance);
			},

			circle: function(x, y, r) {
				var instance = this;

				return Drawing.Impl.createCircle.call(instance, x || 0, y || 0, r || 0);
			},

			clear: function() {
				var instance = this;

				return Drawing.Impl.clear.call(instance);
			},

			createSet: function(itemsArray) {
				var instance = this;

				if (arguments.length > 1) {
					itemsArray = Array.prototype.splice.call(arguments, 0, arguments.length);
				}

				return new Set(itemsArray);
			},

			ellipse: function(x, y, rx, ry) {
				var instance = this;

				return Drawing.Impl.createEllipse.call(instance, x || 0, y || 0, rx || 0, ry || 0);
			},

			image: function(src, x, y, w, h) {
				var instance = this;

				return Drawing.Impl.createImage.call(instance, src || 'about:blank', x || 0, y || 0, w || 0, h || 0);
			},

			path: function(pathString) {
				var instance = this;

				if (isString(pathString)) {
					pathString = A.substitute.apply(A, arguments);
				}
				else if (!isUndefined(pathString) && !isArray(pathString)) {
					pathString += STR_EMPTY;
				}

				return Drawing.Impl.createPath.call(instance, pathString);
			},

			rect: function(x, y, w, h, r) {
				var instance = this;

				return Drawing.Impl.createRectangle.call(instance, x || 0, y || 0, w || 0, h || 0, r || 0);
			},

			remove: function() {
				var instance = this;

				return Drawing.Impl.remove.call(instance);
			},

			safari: EMPTY_FN,

			text: function(x, y, text) {
				var instance = this;

				return Drawing.Impl.createText.call(instance, x || 0, y || 0, text || STR_EMPTY);
			},

			_uiSetX: function(value) {
				var instance = this;

				instance.get('boundingBox').setXY([value, instance.get('y')]);
			},

			_uiSetY: function(value) {
				var instance = this;

				instance.get('boundingBox').setXY([instance.get('x'), value]);
			}
		}
	}
);

Util = {
	getConfig: function(args) {
		var config = {};
		var firstArg = args[0];
		var firstConfigArg = 'boundingBox';

		var indexHeight = 2;
		var indexWidth = 1;
		var indexY = -1;

		if (isNumber(firstArg)) {
			firstConfigArg = 'x';
			indexHeight = 3;
			indexWidth = 2;
			indexY = 1;
		}

		config[firstConfigArg] = firstArg;

		config.height = args[indexHeight];
		config.width = args[indexWidth];
		config.y = args[indexY];

		return config;
	},

	translate: function(x, y) {
		if (x == null) {
			return {
				x: this._.tx,
				y: this._.ty,
				toString: Util.XYToString
			};
		}

		this._.tx += +x;
		this._.ty += +y;

		switch (this.type) {
			case 'circle':
			case 'ellipse':
				this.attr(
					{
						cx: +x + this.attrs.cx,
						cy: +y + this.attrs.cy
					}
				);
			break;

			case 'rect':
			case 'image':
			case 'text':
				this.attr(
					{
						x: +x + this.attrs.x,
						y: +y + this.attrs.y
					}
				);
			break;

			case 'path':
				var path = Util.pathToRelative(this.attrs.path);

				path[0][1] += +x;
				path[0][2] += +y;

				this.attr(
					{
						path: path
					}
				);
			break;
		}

		return this;
	}
};

Drawing.getColor = function(value) {
	var start = Drawing.getColor.start = Drawing.getColor.start || {
		h: 0,
		s: 1,
		b: value || 0.75
	};

	var rgb = ColorUtil.hsb2rgb(start.h, start.s, start.b);

	start.h += 0.075;

	if (start.h > 1) {
		start.h = 0;
		start.s -= 0.2;

		if (start.s <= 0) {
			Drawing.getColor.start = {
				h: 0,
				s: 1,
				b: start.b
			}
		}
	}

	return rgb.hex;
};

Drawing.getColor.reset = function() {
	delete Drawing.start;
};

Util.tear = function(el, drawing) {
	el == drawing.top && (drawing.top = el.prev);

	el == drawing.bottom && (drawing.bottom = el.next);

	el.next && (el.next.prev = el.prev);

	el.prev && (el.prev.next = el.next);
};

Util.toFront = function(el, drawing) {
	if (drawing.top === el) {
		return;
	}

	Util.tear(el, drawing);

	el.next = null;

	el.prev = drawing.top;

	drawing.top.next = el;

	drawing.top = el;
};

Util.toBack = function(el, drawing) {
	if (drawing.bottom === el) {
		return;
	}

	Util.tear(el, drawing);

	el.next = drawing.bottom;

	el.prev = null;

	drawing.bottom.prev = el;

	drawing.bottom = el;
};

Util.insertAfter = function(el, el2, drawing) {
	Util.tear(el, drawing);

	el2 == drawing.top && (drawing.top = el);

	el2.next && (el2.next.prev = el);

	el.next = el2.next;

	el.prev = el2;

	el2.next = el;
};

Util.insertBefore = function(el, el2, drawing) {
	Util.tear(el, drawing);

	el2 == drawing.bottom && (drawing.bottom = el);

	el2.prev && (el2.prev.next = el);

	el.prev = el2.prev;

	el2.prev = el;

	el.next = el2;
};

var Element = function(node, drawing) {
	var instance = this;

	instance.createElement.apply(instance, arguments);
};

Util.removed = function(methodname) {
	return function() {
		throw new Error('AlloyUI: you are calling to method "' + methodname + '" of removed object');
	};
};

Util._path2string = function() {
	return this.join(',').replace(REGEX_PATH_TO_STRING, '$1');
};

Util.parsePathString = A.cached(
	function(pathString) {
		if (!pathString) {
			return null;
		}

		var paramCounts = {
			a: 7,
			c: 6,
			h: 1,
			l: 2,
			m: 2,
			q: 4,
			s: 4,
			t: 2,
			v: 1,
			z: 0
		};

		var data = [];

		if (isArray(pathString) && isArray(pathString[0])) { // rough assumption
			data = pathClone(pathString);
		}

		if (!data.length) {
			String(pathString).replace(REGEX_PATH_COMMAND, function(a, b, c) {
				var params = [];
				var name = TO_LOWER_CASE.call(b);

				c.replace(
					REGEX_PATH_VALUES,
					function(a, b) {
						b && params.push(+b);
					}
				);

				if (name == 'm' && params.length > 2) {
					data.push([b].concat(params.splice(0, 2)));
					name = 'l';
					b = b == 'm' ? 'l' : 'L';
				}

				while (params.length >= paramCounts[name]) {
					data.push([b].concat(params.splice(0, paramCounts[name])));

					if (!paramCounts[name]) {
						break;
					}
				}
			});
		}

		data.toString = Util._path2string;

		return data;
	}
);

Util.findDotsAtSegment = function(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t) {
	var t1 = 1 - t;
	var x = MATH_POW(t1, 3) * p1x + MATH_POW(t1, 2) * 3 * t * c1x + t1 * 3 * t * t * c2x + MATH_POW(t, 3) * p2x;
	var y = MATH_POW(t1, 3) * p1y + MATH_POW(t1, 2) * 3 * t * c1y + t1 * 3 * t * t * c2y + MATH_POW(t, 3) * p2y;

	var mx = p1x + 2 * t * (c1x - p1x) + t * t * (c2x - 2 * c1x + p1x);
	var my = p1y + 2 * t * (c1y - p1y) + t * t * (c2y - 2 * c1y + p1y);
	var nx = c1x + 2 * t * (c2x - c1x) + t * t * (p2x - 2 * c2x + c1x);
	var ny = c1y + 2 * t * (c2y - c1y) + t * t * (p2y - 2 * c2y + c1y);

	var ax = (1 - t) * p1x + t * c1x;
	var ay = (1 - t) * p1y + t * c1y;
	var cx = (1 - t) * c2x + t * p2x;
	var cy = (1 - t) * c2y + t * p2y;

	var alpha = (90 - MATH.atan((mx - nx) / (my - ny)) * 180 / MATH.PI);

	if (mx > nx || my < ny) {
		alpha += 180;
	}

	return {
		x: x,
		y: y,
		m: {
			x: mx,
			y: my
		},
		n: {
			x: nx,
			y: ny
		},
		start: {
			x: ax,
			y: ay
		},
		end: {
			x: cx,
			y: cy
		},
		alpha: alpha
	};
};

Util.pathDimensions = A.cached(
	function(path) {
		if (!path) {
			return {x: 0, y: 0, width: 0, height: 0};
		}

		path = Util.path2curve(path);

		var x = 0;
		var y = 0;
		var X = [];
		var Y = [];
		var p;

		for (var i = 0, length = path.length; i < length; i++) {
			p = path[i];

			if (p[0] == "M") {
				x = p[1];
				y = p[2];
				X.push(x);
				Y.push(y);
			}
			else {
				var dim = Util.curveDim(x, y, p[1], p[2], p[3], p[4], p[5], p[6]);

				X = X.concat(dim.min.x, dim.max.x);
				Y = Y.concat(dim.min.y, dim.max.y);
				x = p[5];
				y = p[6];
			}
		}
		var xmin = MATH_MIN.apply(0, X);
		var ymin = MATH_MIN.apply(0, Y);

		return {
			x: xmin,
			y: ymin,
			width: MATH_MAX.apply(0, X) - xmin,
			height: MATH_MAX.apply(0, Y) - ymin
		};
	}
);

Util.pathClone = function(pathArray) {
	var res = [];

	if (!isArray(pathArray) || !isArray(pathArray && pathArray[0])) { // rough assumption
		pathArray = Util.parsePathString(pathArray);
	}

	for (var i = 0, length = pathArray.length; i < length; i++) {
		res[i] = [];

		for (var j = 0, jLength = pathArray[i].length; j < jLength; j++) {
			res[i][j] = pathArray[i][j];
		}
	}

	res.toString = Util._path2string;

	return res;
};

Util._pathToRelative = A.cached(
	function(pathArray) {
		if (!isArray(pathArray) || !isArray(pathArray && pathArray[0])) { // rough assumption
			pathArray = Util.parsePathString(pathArray);
		}

		var res = [];
		var x = 0;
		var y = 0;
		var mx = 0;
		var my = 0;
		var start = 0;

		if (pathArray[0][0] == 'M') {
			x = pathArray[0][1];
			y = pathArray[0][2];
			mx = x;
			my = y;
			start++;

			res.push(['M', x, y]);
		}

		for (var i = start, length = pathArray.length; i < length; i++) {
			var r = res[i] = [];
			var pa = pathArray[i];

			if (pa[0] != TO_LOWER_CASE.call(pa[0])) {
				r[0] = TO_LOWER_CASE.call(pa[0]);

				switch (r[0]) {
					case 'a':
						r[1] = pa[1];
						r[2] = pa[2];
						r[3] = pa[3];
						r[4] = pa[4];
						r[5] = pa[5];
						r[6] = +(pa[6] - x).toFixed(3);
						r[7] = +(pa[7] - y).toFixed(3);
					break;

					case 'v':
						r[1] = +(pa[1] - y).toFixed(3);
					break;

					case 'm':
						mx = pa[1];
						my = pa[2];
					default:
						for (var j = 1, jLength = pa.length; j < jLength; j++) {
							r[j] = +(pa[j] - ((j % 2) ? x : y)).toFixed(3);
						}
				}
			}
			else {
				r = res[i] = [];

				if (pa[0] == 'm') {
					mx = pa[1] + x;
					my = pa[2] + y;
				}

				for (var k = 0, kLength = pa.length; k < kLength; k++) {
					res[i][k] = pa[k];
				}
			}

			var len = res[i].length;

			switch (res[i][0]) {
				case 'z':
					x = mx;
					y = my;
				break;

				case 'h':
					x += +res[i][len - 1];
				break;

				case 'v':
					y += +res[i][len - 1];
				break;

				default:
					x += +res[i][len - 2];
					y += +res[i][len - 1];
			}
		}

		res.toString = Util._path2string;

		return res;
	}
);

Util.pathToRelative = function(pathArray) {
	return Util.pathClone(Util._pathToRelative(pathArray));
};

Util._pathToAbsolute = A.cached(
	function(pathArray) {
		if (!isArray(pathArray) || !isArray(pathArray && pathArray[0])) { // rough assumption
			pathArray = Util.parsePathString(pathArray);
		}

		var res = [];
		var x = 0;
		var y = 0;
		var mx = 0;
		var my = 0;
		var start = 0;

		if (pathArray[0][0] == 'M') {
			x = +pathArray[0][1];
			y = +pathArray[0][2];
			mx = x;
			my = y;
			start++;

			res[0] = ['M', x, y];
		}

		for (var i = start, length = pathArray.length; i < length; i++) {
			var r = res[i] = [];
			var pa = pathArray[i];

			if (pa[0] != TO_UPPER_CASE.call(pa[0])) {
				r[0] = TO_UPPER_CASE.call(pa[0]);

				switch (r[0]) {
					case 'A':
						r[1] = pa[1];
						r[2] = pa[2];
						r[3] = pa[3];
						r[4] = pa[4];
						r[5] = pa[5];
						r[6] = +(pa[6] + x);
						r[7] = +(pa[7] + y);
					break;

					case 'V':
						r[1] = +pa[1] + y;
					break;

					case 'H':
						r[1] = +pa[1] + x;
					break;

					case 'M':
						mx = +pa[1] + x;
						my = +pa[2] + y;
					default:
						for (var j = 1, jLength = pa.length; j < jLength; j++) {
							r[j] = +pa[j] + ((j % 2) ? x : y);
						}
				}
			}
			else {
				for (var k = 0, kLength = pa.length; k < kLength; k++) {
					res[i][k] = pa[k];
				}
			}

			switch (r[0]) {
				case 'Z':
					x = mx;
					y = my;
				break;

				case 'H':
					x = r[1];
				break;

				case 'V':
					y = r[1];
				break;

				case 'M':
					mx = res[i][res[i].length - 2];
					my = res[i][res[i].length - 1];
				default:
					x = res[i][res[i].length - 2];
					y = res[i][res[i].length - 1];
			}
		}

		res.toString = Util._path2string;

		return res;
	}
);

Util.pathToAbsolute = function(pathArray) {
	return Util.pathClone(Util._pathToAbsolute(pathArray));
};

Util.lineToCubicCurve = function(x1, y1, x2, y2) {
	return [x1, y1, x2, y2, x2, y2];
};

Util.quadraticToCubicCurve = function(x1, y1, ax, ay, x2, y2) {
	var _13 = 1 / 3;
	var _23 = 2 / 3;

	return [
			_13 * x1 + _23 * ax,
			_13 * y1 + _23 * ay,
			_13 * x2 + _23 * ax,
			_13 * y2 + _23 * ay,
			x2,
			y2
		];
};

Util.arcToCubicCurve = function(x1, y1, rx, ry, angle, large_arc_flag, sweep_flag, x2, y2, recursive) {
	// for more information of where this math came from visit:
	// http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
	var PI = MATH.PI;
	var _120 = PI * 120 / 180;
	var rad = PI / 180 * (+angle || 0);
	var res = [];
	var xy;
	var rotate = A.cached(
		function(x, y, rad) {
			var X = x * MATH.cos(rad) - y * MATH.sin(rad),
				Y = x * MATH.sin(rad) + y * MATH.cos(rad);
			return {x: X, y: Y};
		}
	);

	if (!recursive) {
		xy = rotate(x1, y1, -rad);
		x1 = xy.x;
		y1 = xy.y;
		xy = rotate(x2, y2, -rad);
		x2 = xy.x;
		y2 = xy.y;

		var cos = MATH.cos(PI / 180 * angle);
		var sin = MATH.sin(PI / 180 * angle);
		var x = (x1 - x2) / 2;
		var y = (y1 - y2) / 2;

		var h = (x * x) / (rx * rx) + (y * y) / (ry * ry);

		if (h > 1) {
			h = MATH.sqrt(h);
			rx = h * rx;
			ry = h * ry;
		}

		var rx2 = rx * rx;
		var ry2 = ry * ry;
		var k = (large_arc_flag == sweep_flag ? -1 : 1) *
				MATH.sqrt(MATH.abs((rx2 * ry2 - rx2 * y * y - ry2 * x * x) / (rx2 * y * y + ry2 * x * x)));
		var cx = k * rx * y / ry + (x1 + x2) / 2;
		var cy = k * -ry * x / rx + (y1 + y2) / 2;
		var f1 = MATH.asin(((y1 - cy) / ry).toFixed(7));
		var f2 = MATH.asin(((y2 - cy) / ry).toFixed(7));

		f1 = x1 < cx ? PI - f1 : f1;
		f2 = x2 < cx ? PI - f2 : f2;
		f1 < 0 && (f1 = PI * 2 + f1);
		f2 < 0 && (f2 = PI * 2 + f2);

		if (sweep_flag && f1 > f2) {
			f1 = f1 - PI * 2;
		}

		if (!sweep_flag && f2 > f1) {
			f2 = f2 - PI * 2;
		}
	}
	else {
		f1 = recursive[0];
		f2 = recursive[1];
		cx = recursive[2];
		cy = recursive[3];
	}

	var df = f2 - f1;

	if (MATH.abs(df) > _120) {
		var f2old = f2;
		var x2old = x2;
		var y2old = y2;

		f2 = f1 + _120 * (sweep_flag && f2 > f1 ? 1 : -1);
		x2 = cx + rx * MATH.cos(f2);
		y2 = cy + ry * MATH.sin(f2);

		res = Util.arcToCubicCurve(x2, y2, rx, ry, angle, 0, sweep_flag, x2old, y2old, [f2, f2old, cx, cy]);
	}

	df = f2 - f1;

	var c1 = MATH.cos(f1);
	var s1 = MATH.sin(f1);
	var c2 = MATH.cos(f2);
	var s2 = MATH.sin(f2);
	var t = MATH.tan(df / 4);

	var hx = 4 / 3 * rx * t;
	var hy = 4 / 3 * ry * t;

	var m1 = [x1, y1];
	var m2 = [x1 + hx * s1, y1 - hy * c1];

	var m3 = [x2 + hx * s2, y2 - hy * c2];
	var m4 = [x2, y2];

	m2[0] = 2 * m1[0] - m2[0];
	m2[1] = 2 * m1[1] - m2[1];

	if (recursive) {
		return [m2, m3, m4].concat(res);
	}
	else {
		res = [m2, m3, m4].concat(res).join().split(',');

		var newres = [];

		for (var i = 0, length = res.length; i < length; i++) {
			newres[i] = i % 2 ? rotate(res[i - 1], res[i], rad).y : rotate(res[i], res[i + 1], rad).x;
		}

		return newres;
	}
};

Util.findDotAtSegment = function(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t) {
	var t1 = 1 - t;

	return {
		x: MATH_POW(t1, 3) * p1x + MATH_POW(t1, 2) * 3 * t * c1x + t1 * 3 * t * t * c2x + MATH_POW(t, 3) * p2x,
		y: MATH_POW(t1, 3) * p1y + MATH_POW(t1, 2) * 3 * t * c1y + t1 * 3 * t * t * c2y + MATH_POW(t, 3) * p2y
	};
};

Util.curveDim = A.cached(
	function(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y) {
		var a = (c2x - 2 * c1x + p1x) - (p2x - 2 * c2x + c1x);
		var b = 2 * (c1x - p1x) - 2 * (c2x - c1x);
		var c = p1x - c1x;

		var t1 = (-b + MATH.sqrt(b * b - 4 * a * c)) / 2 / a;
		var t2 = (-b - MATH.sqrt(b * b - 4 * a * c)) / 2 / a;

		var y = [p1y, p2y];
		var x = [p1x, p2x];
		var dot;

		MATH.abs(t1) > 1e12 && (t1 = 0.5);
		MATH.abs(t2) > 1e12 && (t2 = 0.5);

		if (t1 > 0 && t1 < 1) {
			dot = Util.findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t1);

			x.push(dot.x);
			y.push(dot.y);
		}

		if (t2 > 0 && t2 < 1) {
			dot = Util.findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t2);

			x.push(dot.x);
			y.push(dot.y);
		}

		a = (c2y - 2 * c1y + p1y) - (p2y - 2 * c2y + c1y);
		b = 2 * (c1y - p1y) - 2 * (c2y - c1y);

		c = p1y - c1y;

		t1 = (-b + MATH.sqrt(b * b - 4 * a * c)) / 2 / a;

		t2 = (-b - MATH.sqrt(b * b - 4 * a * c)) / 2 / a;

		MATH.abs(t1) > 1e12 && (t1 = 0.5);
		MATH.abs(t2) > 1e12 && (t2 = 0.5);

		if (t1 > 0 && t1 < 1) {
			dot = Util.findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t1);
			x.push(dot.x);
			y.push(dot.y);
		}

		if (t2 > 0 && t2 < 1) {
			dot = Util.findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t2);
			x.push(dot.x);
			y.push(dot.y);
		}

		return {
			min: {
				x: MATH_MIN.apply(0, x),
				y: MATH_MIN.apply(0, y)
			},
			max: {
				x: MATH_MAX.apply(0, x),
				y: MATH_MAX.apply(0, y)
			}
		};
	}
);

Util._path2curve = A.cached(
	function(path, path2) {
		var p = Util.pathToAbsolute(path);
		var p2 = path2 && Util.pathToAbsolute(path2);

		var attrs = {
			x: 0,
			y: 0,
			bx: 0,
			by: 0,
			X: 0,
			Y: 0,
			qx: null,
			qy: null
		};

		var attrs2 = {
			x: 0,
			y: 0,
			bx: 0,
			by: 0,
			X: 0,
			Y: 0,
			qx: null,
			qy: null
		};

		var processPath = function(path, d) {
			var nx;
			var ny;

			if (!path) {
				return ['C', d.x, d.y, d.x, d.y, d.x, d.y];
			}

			if (!(path[0] in MAP_COMMAND_SVG_QUADRATIC)) {
				d.qx = d.qy = null
			}

			switch (path[0]) {
				case 'M':
					d.X = path[1];
					d.Y = path[2];
				break;

				case 'A':
					path = ['C'].concat(Util.arcToCubicCurve.apply(0, [d.x, d.y].concat(path.slice(1))));
				break;

				case 'S':
					nx = d.x + (d.x - (d.bx || d.x));
					ny = d.y + (d.y - (d.by || d.y));
					path = ['C', nx, ny].concat(path.slice(1));
				break;

				case 'T':
					d.qx = d.x + (d.x - (d.qx || d.x));
					d.qy = d.y + (d.y - (d.qy || d.y));
					path = ['C'].concat(Util.quadraticToCubicCurve(d.x, d.y, d.qx, d.qy, path[1], path[2]));
				break;

				case 'Q':
					d.qx = path[1];
					d.qy = path[2];
					path = ['C'].concat(Util.quadraticToCubicCurve(d.x, d.y, path[1], path[2], path[3], path[4]));
				break;

				case 'L':
					path = ['C'].concat(Util.lineToCubicCurve(d.x, d.y, path[1], path[2]));
				break;

				case 'H':
					path = ['C'].concat(Util.lineToCubicCurve(d.x, d.y, path[1], d.y));
				break;

				case 'V':
					path = ['C'].concat(Util.lineToCubicCurve(d.x, d.y, d.x, path[1]));
				break;

				case 'Z':
					path = ['C'].concat(Util.lineToCubicCurve(d.x, d.y, d.X, d.Y));
				break;
			}

			return path;
		};

		var fixArc = function(pp, i) {
			if (pp[i].length > 7) {
				pp[i].shift();

				var pi = pp[i];

				while (pi.length) {
					pp.splice(i++, 0, ['C'].concat(pi.splice(0, 6)));
				}

				pp.splice(i, 1);

				length = MATH_MAX(p.length, p2 && p2.length || 0);
			}
		};

		var fixM = function(path1, path2, a1, a2, i) {
			if (path1 && path2 && path1[i][0] == 'M' && path2[i][0] != 'M') {
				path2.splice(i, 0, ['M', a2.x, a2.y]);
				a1.bx = 0;
				a1.by = 0;

				a1.x = path1[i][1];
				a1.y = path1[i][2];

				length = MATH_MAX(p.length, p2 && p2.length || 0);
			}
		};

		for (var i = 0, length = MATH_MAX(p.length, p2 && p2.length || 0); i < length; i++) {
			p[i] = processPath(p[i], attrs);

			fixArc(p, i);

			p2 && (p2[i] = processPath(p2[i], attrs2));
			p2 && fixArc(p2, i);

			fixM(p, p2, attrs, attrs2, i);
			fixM(p2, p, attrs2, attrs, i);

			var seg = p[i];
			var seg2 = p2 && p2[i];
			var seglen = seg.length;
			var seg2len = p2 && seg2.length;

			attrs.x = seg[seglen - 2];
			attrs.y = seg[seglen - 1];

			attrs.bx = parseFloat(seg[seglen - 4]) || attrs.x;
			attrs.by = parseFloat(seg[seglen - 3]) || attrs.y;

			attrs2.bx = p2 && (parseFloat(seg2[seg2len - 4]) || attrs2.x);
			attrs2.by = p2 && (parseFloat(seg2[seg2len - 3]) || attrs2.y);

			attrs2.x = p2 && seg2[seg2len - 2];
			attrs2.y = p2 && seg2[seg2len - 1];
		}

		return p2 ? [p, p2] : p;
	}
);

Util.path2curve = function(path1, path2) {
	return Util.pathClone(Util._path2curve(path1, path2));
};

Util.parseDots = A.cached(
	function(gradient) {
		var dots = [];

		for (var i = 0, length = gradient.length; i < length; i++) {
			var dot = {};
			var par = gradient[i].match(REGEX_GRADIENT_VALUES);

			dot.color = ColorUtil.getRGB(par[1]);

			if (dot.color.error) {
				return null;
			}

			dot.color = dot.color.hex;

			par[2] && (dot.offset = par[2] + '%');

			dots.push(dot);
		}

		for (i = 1, length = dots.length - 1; i < length; i++) {
			if (!dots[i].offset) {
				var start = parseFloat(dots[i - 1].offset || 0);
				var end = 0;

				for (var j = i + 1; j < length; j++) {
					if (dots[j].offset) {
						end = dots[j].offset;
						break;
					}
				}

				if (!end) {
					end = 100;
					j = length;
				}

				end = parseFloat(end);

				var d = (end - start) / (j - i + 1);

				for (; i < j; i++) {
					start += d;
					dots[i].offset = start + '%';
				}

			}
		}

		return dots;
	}
);

Util.XYToString = function() {
	return this.x + STR_SPACE + this.y;
};

Util.getPointAtSegmentLength = A.cached(
	function(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, length) {
		var len = 0;
		var old;

		for (var i = 0; i < 1.01; i+=.01) {
			var dot = Util.findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, i);

			i && (len += MATH_POW(MATH_POW(old.x - dot.x, 2) + MATH_POW(old.y - dot.y, 2), 0.5));

			if (len >= length) {
				return dot;
			}

			old = dot;
		}
	}
);

var curvesLengths = {};

Util.getPointAtSegmentLength = function(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, length) {
	var len = 0;
	var precision = 100;
	var name = [p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y].join();
	var cache = curvesLengths[name];
	var old;
	var dot;

	if (!cache) {
		cache = {
			data: []
		};

		curvesLengths[name] = cache;
	}

	if (cache.timer) {
		clearTimeout(cache.timer);
	}

	cache.timer = setTimeout(
		function() {
			delete curvesLengths[name];
		},
		2000
	);

	if (length != null) {
		var total = getPointAtSegmentLength(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y);

		precision = ~~total * 10;
	}

	for (var i = 0; i < precision + 1; i++) {
		if (cache.data[length] > i) {
			dot = cache.data[i * precision];
		}
		else {
			dot = Util.findDotsAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, i / precision);
			cache.data[i] = dot;
		}

		if (i) {
			len += MATH_POW(MATH_POW(old.x - dot.x, 2) + MATH_POW(old.y - dot.y, 2), 0.5);
		}

		if (length != null && len >= length) {
			return dot;
		}

		if (length == null) {
			return len;
		}
	}
};

Util.getLengthFactory = function(istotal, subpath) {
	return function(path, length, onlystart) {
		path = Util.path2curve(path);

		var x;
		var y;
		var p;
		var l;
		var sp = '';
		var subpaths = {};
		var point;
		var len = 0;

		for (var i = 0, length = path.length; i < length; i++) {
			p = path[i];

			if (p[0] == 'M') {
				x = +p[1];
				y = +p[2];
			}
			else {
				l = Util.getPointAtSegmentLength(x, y, p[1], p[2], p[3], p[4], p[5], p[6]);

				if (len + l > length) {
					if (subpath && !subpaths.start) {
						point = Util.getPointAtSegmentLength(x, y, p[1], p[2], p[3], p[4], p[5], p[6], length - len);
						sp += [
							'C',
							point.start.x,
							point.start.y,
							point.m.x,
							point.m.y,
							point.x,
							point.y
						];

						if (onlystart) {
							return sp;
						}

						subpaths.start = sp;

						sp = [
							'M',
							point.x,
							point.y + 'C',
							point.n.x,
							point.n.y,
							point.end.x,
							point.end.y,
							p[5],
							p[6]
						].join();

						len += l;
						x = +p[5];
						y = +p[6];

						continue;
					}

					if (!istotal && !subpath) {
						point = Util.getPointAtSegmentLength(x, y, p[1], p[2], p[3], p[4], p[5], p[6], length - len);
						return {
							x: point.x,
							y: point.y,
							alpha: point.alpha
						};
					}
				}

				len += l;
				x = +p[5];
				y = +p[6];
			}

			sp += p;
		}

		subpaths.end = sp;
		point = istotal ? len : subpath ? subpaths : Util.findDotsAtSegment(x, y, p[1], p[2], p[3], p[4], p[5], p[6], 1);

		point.alpha && (point = {
			x: point.x,
			y: point.y,
			alpha: point.alpha
		});

		return point;
	};
};

Util.getTotalLength = Util.getLengthFactory(1);
Util.getPointAtLength = Util.getLengthFactory();
Util.getSubpathsAtLength = Util.getLengthFactory(0, 1);

Util.applyAttributes = function(element, name, value) {
	var params;

	if (value != null) {
		params = {};

		params[name] = value;
	}
	else if (name != null && isObject(name)) {
		params = name;
	}

	var customAttributes = element.paper.customAttributes;

	for (var key in customAttributes) {
		if (
			customAttributes.hasOwnProperty(key) &&
			params.hasOwnProperty(key) &&
			isFunction(customAttributes[key])
		) {
			var subParams = customAttributes[key].apply(element, [].concat(params[key]));

			element.attrs[key] = params[key];

			for (var subKey in subParams) {
				if (subParams.hasOwnProperty(subKey)) {
					params[subKey] = subParams[subKey];
				}
			}
		}
	}

	return params;
};

Element.prototype = {
	attr: EMPTY_FN,
	blur: EMPTY_FN,
	clone: function() {
		if (this.removed) {
			return null;
		}

		var attr = this.attr();

		delete attr.scale;
		delete attr.translation;
		return this.paper[this.type]().attr(attr);
	},
	createElement: EMPTY_FN,
	detach: function(type, fn) {
		var instance = this;

		A.detach(Element._getEventType(type), fn, (instance.shape || instance.node || DOC));

		return instance;
	},

	getRegion: EMPTY_FN,

	getPointAtLength: function(length) {
		if (this.type != 'path') {
			return;
		}

		return Util.getPointAtLength(this.attrs.path, length);
	},
	getSubpath: function(from, to) {
		if (this.type != 'path') {
			return;
		}

		if (MATH.abs(this.getTotalLength() - to) < '1e-6') {
			return Util.getSubpathsAtLength(this.attrs.path, from).end;
		}

		var a = Util.getSubpathsAtLength(this.attrs.path, to, 1);

		return from ? Util.getSubpathsAtLength(a, from).end : a;
	},
	getTotalLength: function() {
		if (this.type != 'path') {
			return;
		}

		if (this.node.getTotalLength) {
			return this.node.getTotalLength();
		}

		return Util.getTotalLength(this.attrs.path);
	},
	hide: EMPTY_FN,
	hover: function(overFn, outFn) {
		return this.on('mouseover', overFn).on('mouseout', outFn);
	},
	insertAfter: EMPTY_FN,
	insertBefore: EMPTY_FN,
	on: function(type, fn, context) {
		var instance = this;

		var realType = Element._getEventType(type);

		A.on(realType, A.bind(fn, context || instance), instance.shape || instance.node || DOC, Element._getEventConfig(realType, type));

		return instance;
	},
	remove: EMPTY_FN,
	resetScale: function() {
		if (this.removed) {
			return this;
		}

		this._.sx = 1;
		this._.sy = 1;
		this.attrs.scale = '1 1';
	},
	rotate: EMPTY_FN,
	scale: function(x, y, cx, cy) {
		if (this.removed) {
			return this;
		}

		if (x == null && y == null) {
			return {
				x: this._.sx,
				y: this._.sy,
				toString: Util.XYToString
			};
		}

		y = y || x;

		!+y && (y = x);

		var dx;
		var dy;
		var dcx;
		var dcy;
		var a = this.attrs;

		if (x != 0) {
			var bb = this.getRegion();
			var rcx = bb.x + bb.width / 2;
			var rcy = bb.y + bb.height / 2;
			var kx = MATH.abs(x / this._.sx);
			var ky = MATH.abs(y / this._.sy);

			cx = (+cx || cx == 0) ? cx : rcx;
			cy = (+cy || cy == 0) ? cy : rcy;

			var posx = ~~(this._.sx > 0);
			var posy = ~~(this._.sy > 0);
			var dirx = ~~(x / MATH.abs(x));
			var diry = ~~(y / MATH.abs(y));
			var dkx = kx * dirx;
			var dky = ky * diry;
			var s = this.node.style;

			var ncx = cx + MATH.abs(rcx - cx) * dkx * (rcx > cx == posx ? 1 : -1);
			var ncy = cy + MATH.abs(rcy - cy) * dky * (rcy > cy == posy ? 1 : -1);
			var fr = (x * dirx > y * diry ? ky : kx);

			switch (this.type) {
				case 'rect':
				case 'image':
					var neww = a.width * kx;
					var newh = a.height * ky;

					this.attr({
						height: newh,
						r: a.r * fr,
						width: neww,
						x: ncx - neww / 2,
						y: ncy - newh / 2
					});
				break;

				case 'circle':
				case 'ellipse':
					this.attr({
						rx: a.rx * dirx * kx,
						ry: a.ry * diry * ky,
						r: a.r * MATH_MIN(dirx * kx, diry * ky),
						cx: ncx,
						cy: ncy
					});
				break;

				case 'text':
					this.attr({
						x: ncx,
						y: ncy
					});
				break;

				case 'path':
					var path = Util.pathToRelative(a.path);
					var skip = true;
					var fx = posx ? dkx : kx;
					var fy = posy ? dky : ky;

					for (var i = 0, length = path.length; i < length; i++) {
						var p = path[i];
						var P0 = TO_UPPER_CASE.call(p[0]);

						if (P0 == 'M' && skip) {
							continue;
						}
						else {
							skip = false;
						}

						if (P0 == 'A') {
							p[path[i].length - 2] *= fx;
							p[path[i].length - 1] *= fy;
							p[1] *= kx;
							p[2] *= ky;
							p[5] = +(dirx + diry ? !!+p[5] : !+p[5]);
						}
						else if (P0 == 'H') {
							for (var j = 1, jLength = p.length; j < jLength; j++) {
								p[j] *= fx;
							}
						}
						else if (P0 == 'V') {
							for (j = 1, jLength = p.length; j < jLength; j++) {
								p[j] *= fy;
							}
						 }
						else {
							for (j = 1, jLength = p.length; j < jLength; j++) {
								p[j] *= (j % 2) ? fx : fy;
							}
						}
					}

					var dim2 = Util.pathDimensions(path);

					dx = ncx - dim2.x - dim2.width / 2;
					dy = ncy - dim2.y - dim2.height / 2;

					path[0][1] += dx;
					path[0][2] += dy;

					this.attr({path: path});
				break;
			}

			if (this.type in MAP_TYPES_IMAGE_TEXT && (dirx != 1 || diry != 1)) {
				if (this.transformations) {
					this.transformations[2] = 'scale('.concat(dirx, ',', diry, ')');
					this.node.setAttribute('transform', this.transformations.join(STR_SPACE));

					dx = (dirx == -1) ? -a.x - (neww || 0) : a.x;
					dy = (diry == -1) ? -a.y - (newh || 0) : a.y;

					this.attr(
						{
							x: dx,
							y: dy
						}
					);

					a.fx = dirx - 1;
					a.fy = diry - 1;
				}
				else {
					this.node.filterMatrix = STR_MS_PROG_ID_PREFIX + '.Matrix(M11='.concat(dirx,
						', M12=0, M21=0, M22=', diry,
						', Dx=0, Dy=0, sizingmethod="auto expand", filtertype="bilinear")');

					s.filter = (this.node.filterMatrix || STR_EMPTY) + (this.node.filterOpacity || STR_EMPTY);
				}
			}
			else {
				if (this.transformations) {
					this.transformations[2] = STR_EMPTY;
					this.node.setAttribute('transform', this.transformations.join(STR_SPACE));

					a.fx = 0;
					a.fy = 0;
				}
				else {
					this.node.filterMatrix = STR_EMPTY;

					s.filter = (this.node.filterMatrix || STR_EMPTY) + (this.node.filterOpacity || STR_EMPTY);
				}
			}

			a.scale = [x, y, cx, cy].join(STR_SPACE);

			this._.sx = x;
			this._.sy = y;
		}

		return this;
	},
	show: EMPTY_FN,
	toBack: EMPTY_FN,
	toFront: EMPTY_FN,
	toString: function() {
		return '"' + TO_UPPER_CASE.call(this.type) + '": AlloyUI Drawing Element';
	},
	translate: EMPTY_FN,
	unhover: function(overFn, outFn) {
		return this.detach('mouseover', overFn).detach('mouseout', outFn);
	}
};

Element._getEventConfig = EMPTY_FN;

Element._getEventType = function(eventType) {
	return eventType;
};

Drawing.Element = Element;

var Set = A.Component.create(
	{
		NAME: 'set',

		EXTENDS: A.ArrayList,
		prototype: {
			pop: function() {
				var instance = this;

				return instance._items.pop();
			},
			push: function() {
				var instance = this;

				var items = instance._items;
				var args = arguments;
				var length = args.length;
				var item;

				for (var i = 0; i < length; i++) {
					item = args[i];

					if (item && item instanceof Element || item instanceof Set) {
						items[items.length] = args[i];
					}
				}

				return instance;
			},

			clone: function() {
				var instance = this;

				var setList = new Set();

				setList._items = instance._items;

				return setList;
			},

			invoke: function(methodName) {
				var instance = this;

				var args = Array.prototype.slice.call(arguments, 1);
				var item;
				var items = instance._items;

				for (var i = 0, length = items.length; i < length; i++) {
					item = items[i];

					if (methodName in item) {
						item[methodName].apply(items[i], args);
					}
				}

				return instance;
			}
		}
	}
);

Drawing.Set = Set;

Set.addMethod = function(method) {
	return A.ArrayList.addMethod(Drawing.Set.prototype, method);
};

var setListMethods = A.Object.keys(Element.prototype);

Set.addMethod(setListMethods);

Set.prototype.getRegion = Set.prototype.getBBox =  function() {
	var instance = this;

	var x = [];
	var y = [];
	var w = [];
	var h = [];

	var items = instance._items;

	for (var i = items.length; i--;) {
		var box = items[i].getRegion();

		x.push(box.x);
		y.push(box.y);

		w.push(box.x + box.width);
		h.push(box.y + box.height);
	}

	x = MATH_MIN.apply(0, x);
	y = MATH_MIN.apply(0, y);

	return {
		x: x,
		y: y,
		width: MATH_MAX.apply(0, w) - x,
		height: MATH_MAX.apply(0, h) - y
	};
};

Set.prototype.attr = function(name, value) {
	var instance = this;

	var items = instance._items;

	if (name && isArray(name) && isObject(name[0])) {
		for (var i = 0, length = name.length; i < length; i++) {
			items[i].attr(name[i]);
		}
	}
	else {
		for (var i = 0, length = items.length; i < length; i++) {
			items[i].attr(name, value);
		}
	}

	return instance;
};

Set.prototype.toString = function() {
	return this._items.join(',');
};

// Expose shared object/common constants
Drawing.REGEX_ISURL = REGEX_ISURL;
Drawing.REGEX_SEPARATOR = REGEX_SEPARATOR;
Drawing.REGEX_SEPARATOR_GRADIENT = REGEX_SEPARATOR_GRADIENT;
Drawing.REGEX_RADIAL_GRADIENT = REGEX_RADIAL_GRADIENT;

Drawing.MAP_ATTRS_AVAILABLE = MAP_ATTRS_AVAILABLE;
Drawing.MAP_TYPES_IMAGE_TEXT = MAP_TYPES_IMAGE_TEXT;
Drawing.MAP_TYPES_CIRCLE_ELLIPSE = MAP_TYPES_CIRCLE_ELLIPSE;

Drawing.STR_EMPTY = STR_EMPTY;
Drawing.STR_FILL = STR_FILL;
Drawing.STR_SPACE = STR_SPACE;
Drawing.STR_MS_PROG_ID_PREFIX = STR_MS_PROG_ID_PREFIX;

Drawing.Util = Util;
Drawing.Element = Element;

A.Drawing = Drawing;

}, '1.0.1' ,{requires:['aui-base','aui-color-util','substitute']});
AUI.add('aui-drawing-svg', function(A) {
var Lang = A.Lang,
	isArray = Lang.isArray,
	isFunction = Lang.isFunction,
	isObject = Lang.isObject,
	isString = Lang.isString,
	isUndefined = Lang.isUndefined,

	CONFIG = A.config,
	DOC = CONFIG.doc,

	ColorUtil = A.ColorUtil,

	Drawing = A.Drawing,
	Element = Drawing.Element,
	Impl,
	Util = Drawing.Util,

	DRAWING_PROTOTYPE = Drawing.prototype,
	ELEMENT_PROTOTYPE = Element.prototype,

	MATH = Math,
	MATH_MAX = MATH.max,
	MATH_POW = MATH.pow,

	MAP_ATTRS_AVAILABLE = Drawing.MAP_ATTRS_AVAILABLE,

	MAP_TYPES_IMAGE_TEXT = Drawing.MAP_TYPES_IMAGE_TEXT,
	MAP_TYPES_CIRCLE_ELLIPSE = Drawing.MAP_TYPES_CIRCLE_ELLIPSE,

	REGEX_ISURL = Drawing.REGEX_ISURL,
	REGEX_RADIAL_GRADIENT = Drawing.REGEX_RADIAL_GRADIENT,
	REGEX_SEPARATOR = Drawing.REGEX_SEPARATOR,
	REGEX_SEPARATOR_GRADIENT = Drawing.REGEX_SEPARATOR_GRADIENT,

	STR_CREATED_DESCRIPTION = 'Created with AlloyUI',
	STR_EMPTY = Drawing.STR_EMPTY,
	STR_FILL = Drawing.STR_FILL,
	STR_SPACE = Drawing.STR_SPACE,

	TEXT_LEADING = 1.2,

	TO_LOWER_CASE = String.prototype.toLowerCase,
	TO_UPPER_CASE = String.prototype.toUpperCase;

DRAWING_PROTOTYPE.svgns = 'http://www.w3.org/2000/svg';
DRAWING_PROTOTYPE.xlink = 'http://www.w3.org/1999/xlink';

Util.CREATE_ELEMENT = function(elementType) {
	var el = DOC.createElementNS(DRAWING_PROTOTYPE.svgns, elementType);

	el.style.webkitTapHighlightColor = 'rgba(0,0,0,0)';

	return el;
};

Util.SET_ATTRS = function(el, attr) {
	for (var key in attr) {
		if (attr.hasOwnProperty(key)) {
			el.setAttribute(key, String(attr[key]));
		}
	}

	return el;
};

Util.TUNE_TEXT = function(el, params) {
	if (el.type != 'text' || !(params.hasOwnProperty('text') || params.hasOwnProperty('font') || params.hasOwnProperty('font-size') || params.hasOwnProperty('x') || params.hasOwnProperty('y'))) {
		return;
	}

	var texts;

	var a = el.attrs;
	var node = el.node;
	var fontSize = 10;

	if (node.firstChild) {
		fontSize = parseInt(DOC.defaultView.getComputedStyle(node.firstChild, STR_EMPTY).getPropertyValue('font-size'), 10);
	}

	if (params.hasOwnProperty('text')) {
		a.text = params.text;

		while (node.firstChild) {
			node.removeChild(node.firstChild);
		}

		texts = String(params.text).split('\n');

		for (var i = 0, length = texts.length; i < length; i++) {
			if (texts[i]) {
				var tspan = Util.CREATE_ELEMENT('tspan');

				if (i) {
					Util.SET_ATTRS(
						tspan,
						{
							dy: fontSize * TEXT_LEADING,
							x: a.x
						}
					);
				}

				tspan.appendChild(DOC.createTextNode(texts[i]));
				node.appendChild(tspan);
			}
		}
	}
	else {
		texts = node.getElementsByTagName('tspan');

		for (i = 0, length = texts.length; i < length; i++) {
			if (i) {
				Util.SET_ATTRS(
					texts[i],
					{
						dy: fontSize * TEXT_LEADING,
						x: a.x
					}
				);
			}
		}
	}

	Util.SET_ATTRS(
		node,
		{
			y: a.y
		}
	);

	var bb = el.getRegion();
	var diff = a.y - (bb.y + bb.height / 2);

	if (diff && isFinite(diff)) {
		Util.SET_ATTRS(
			node,
			{
				y: a.y + diff
			}
		);
	}
};

Util.UPDATE_POSITION = function(obj) {
	var bbox = obj.getRegion();

	Util.SET_ATTRS(
		obj.pattern,
		{
			patternTransform: A.substitute('translate({0},{1})', [bbox.x, bbox.y])
		}
	);
};

Impl = Drawing.Impl = {
	addGradientFill: function(obj, gradient, SVG) {
		var type = 'linear';
		var fx = 0.5;
		var fy = 0.5;
		var objStyle = obj.style;
		var vector;

		gradient = String(gradient).replace(REGEX_RADIAL_GRADIENT, function(all, _fx, _fy) {
			type = 'radial';

			if (_fx && _fy) {
				fx = parseFloat(_fx);
				fy = parseFloat(_fy);

				var dir = ((fy > 0.5) * 2 - 1);

				MATH_POW(fx - 0.5, 2) + MATH_POW(fy - 0.5, 2) > 0.25 &&
					(fy = MATH.sqrt(0.25 - MATH_POW(fx - 0.5, 2)) * dir + 0.5) &&
					fy != 0.5 &&
					(fy = fy.toFixed(5) - 1e-5 * dir);
			}

			return STR_EMPTY;
		});

		gradient = gradient.split(REGEX_SEPARATOR_GRADIENT);

		if (type == 'linear') {
			var angle = gradient.shift();

			angle = -parseFloat(angle);

			if (isNaN(angle)) {
				return null;
			}

			vector = [0, 0, MATH.cos(angle * MATH.PI / 180), MATH.sin(angle * MATH.PI / 180)];

			var max = 1 / (MATH_MAX(MATH.abs(vector[2]), MATH.abs(vector[3])) || 1);

			vector[2] *= max;
			vector[3] *= max;

			if (vector[2] < 0) {
				vector[0] = -vector[2];
				vector[2] = 0;
			}

			if (vector[3] < 0) {
				vector[1] = -vector[3];
				vector[3] = 0;
			}
		}

		var dots = Util.parseDots(gradient);

		if (!dots) {
			return null;
		}

		var id = obj.getAttribute(STR_FILL);

		id = id.match(/^url\(#(.*)\)$/);
		id && SVG.defs.removeChild(DOC.getElementById(id[1]));

		var el = Util.CREATE_ELEMENT(type + 'Gradient');

		el.id = A.guid();

		var elAttrs;

		if (type == 'radial') {
			elAttrs = {
				fx: fx,
				fy: fy
			};
		}
		else {
			elAttrs = {
				x1: vector[0],
				y1: vector[1],
				x2: vector[2],
				y2: vector[3]
			};
		}

		Util.SET_ATTRS(el, elAttrs);

		SVG.defs.appendChild(el);

		for (var i = 0, length = dots.length; i < length; i++) {
			var stop = Util.CREATE_ELEMENT('stop');

			var offset = dots[i].offset;

			if (!offset) {
				if (!i) {
					offset = '0%';
				}
				else {
					offset = '100%';
				}
			}

			Util.SET_ATTRS(
				stop,
				{
					offset: offset,
					'stop-color': dots[i].color || '#fff'
				}
			);

			el.appendChild(stop);
		}

		Util.SET_ATTRS(
			obj,
			{
				fill: 'url(#' + el.id + ')',
				opacity: 1,
				'fill-opacity': 1
			}
		);

		objStyle.fill = STR_EMPTY;
		objStyle.opacity = 1;
		objStyle.fillOpacity = 1;

		return 1;
	},

	clear: function() {
		var instance = this;

		var canvas = instance.canvas;

		while (canvas.firstChild) {
			canvas.removeChild(canvas.firstChild);
		}

		instance.bottom = instance.top = null;

		instance.desc = Util.CREATE_ELEMENT('desc');

		instance.desc.appendChild(DOC.createTextNode(STR_CREATED_DESCRIPTION));

		canvas.appendChild(instance.desc);

		instance.defs = Util.CREATE_ELEMENT('defs');

		canvas.appendChild(instance.defs);
	},

	createCanvas: function() {
		var instance = this;

		var contentBox = instance.get('contentBox');

		var x = instance.get('x');
		var y = instance.get('y');
		var width = instance.get('width');
		var height = instance.get('height');

		var canvas = Util.CREATE_ELEMENT('svg');

		Util.SET_ATTRS(
			canvas,
			{
				xmlns: 'http://www.w3.org/2000/svg',
				version: 1.1,
				width: width,
				height: height
			}
		);

		contentBox.prepend(canvas);

		instance.canvas = canvas;

		instance.clear();
	},

	createCircle: function(x, y, r) {
		var instance = this;

		var el = Util.CREATE_ELEMENT('circle');

		instance.canvas.appendChild(el);

		var circle = new Element(el, instance);

		circle.attrs = {
			cx: x,
			cy: y,
			r: r,
			fill: 'none',
			stroke: '#000'
		};

		circle.type = 'circle';

		Util.SET_ATTRS(el, circle.attrs);

		return circle;
	},

	createEllipse: function(x, y, rx, ry) {
		var instance = this;

		var el = Util.CREATE_ELEMENT('ellipse');

		instance.canvas.appendChild(el);

		var ellipse = new Element(el, instance);

		ellipse.attrs = {
			cx: x,
			cy: y,
			rx: rx,
			ry: ry,
			fill: 'none',
			stroke: '#000'
		};

		ellipse.type = 'ellipse';

		Util.SET_ATTRS(el, ellipse.attrs);

		return ellipse;
	},

	createImage: function(src, x, y, w, h) {
		var instance = this;

		var el = Util.CREATE_ELEMENT('image');

		Util.SET_ATTRS(
			el,
			{
				x: x,
				y: y,
				width: w,
				height: h,
				preserveAspectRatio: 'none'
			}
		);

		el.setAttributeNS(instance.xlink, 'href', src);

		instance.canvas.appendChild(el);

		var image = new Element(el, instance);

		image.attrs = {
			x: x,
			y: y,
			width: w,
			height: h,
			src: src
		};

		image.type = 'image';

		return image;
	},

	createPath: function(pathString) {
		var instance = this;

		var el = Util.CREATE_ELEMENT('path');

		instance.canvas.appendChild(el);

		var path = new Element(el, instance);

		path.type = 'path';

		Impl.setFillAndStroke(
			path,
			{
				fill: 'none',
				stroke: '#000',
				path: pathString
			}
		);

		return path;
	},

	createRectangle: function(x, y, w, h, r) {
		var instance = this;

		var el = Util.CREATE_ELEMENT('rect');

		instance.canvas.appendChild(el);

		var rect = new Element(el, instance);

		rect.attrs = {
			x: x,
			y: y,
			width: w,
			height: h,
			r: r,
			rx: r,
			ry: r,
			fill: 'none',
			stroke: '#000'
		};

		rect.type = 'rect';

		Util.SET_ATTRS(el, rect.attrs);

		return rect;
	},

	createText: function(x, y, text) {
		var instance = this;

		var el = Util.CREATE_ELEMENT('text');

		Util.SET_ATTRS(
			el,
			{
				x: x,
				y: y,
				'text-anchor': 'middle'
			}
		);

		instance.canvas.appendChild(el);

		var textElement = new Element(el, instance);

		textElement.attrs = {
			x: x,
			y: y,
			'text-anchor': 'middle',
			text: text,
			font: MAP_ATTRS_AVAILABLE.font,
			stroke: 'none',
			fill: '#000'
		};

		textElement.type = 'text';

		Impl.setFillAndStroke(textElement, textElement.attrs);

		return textElement;
	},

	setFillAndStroke: function(obj, params) {
		var dasharray = {
			'': [0],
			'none': [0],
			'-': [3, 1],
			'.': [1, 1],
			'-.': [3, 1, 1, 1],
			'-..': [3, 1, 1, 1, 1, 1],
			'. ': [1, 3],
			'- ': [4, 3],
			'--': [8, 3],
			'- .': [4, 3, 1, 3],
			'--.': [8, 3, 1, 3],
			'--..': [8, 3, 1, 3, 1, 3]
		};

		var node = obj.node;
		var attrs = obj.attrs;
		var rot = obj.rotate();

		var addDashes = function(obj, value) {
			value = dasharray[TO_LOWER_CASE.call(value)];

			if (value) {
				var width = obj.attrs['stroke-width'] || '1';

				var butt = {
						round: width,
						square: width,
						butt: 0
					}[obj.attrs['stroke-linecap'] || params['stroke-linecap']] || 0;

				var dashes = [];

				var i = value.length;

				while (i--) {
					dashes[i] = value[i] * width + ((i % 2) ? 1 : -1) * butt;
				}

				Util.SET_ATTRS(
					node,
					{
						'stroke-dasharray': dashes.join(',')
					}
				);
			}
		};

		if (params.hasOwnProperty('rotation')) {
			rot = params.rotation;
		}

		var rotxy = String(rot).split(REGEX_SEPARATOR);

		if (!(rotxy.length - 1)) {
			rotxy = null;
		}
		else {
			rotxy[1] = +rotxy[1];
			rotxy[2] = +rotxy[2];
		}

		parseFloat(rot) && obj.rotate(0, true);

		for (var att in params) {
			if (params.hasOwnProperty(att)) {
				if (!MAP_ATTRS_AVAILABLE.hasOwnProperty(att)) {
					continue;
				}

				var value = params[att];

				attrs[att] = value;

				switch (att) {
					case 'blur':
						obj.blur(value);
					break;

					case 'rotation':
						obj.rotate(value, true);
					break;

					case 'href':
					case 'title':
					case 'target':
						var pn = node.parentNode;

						if (TO_LOWER_CASE.call(pn.tagName) != 'a') {
							var hl = Util.CREATE_ELEMENT('a');

							pn.insertBefore(hl, node);
							hl.appendChild(node);
							pn = hl;
						}

						pn.setAttributeNS(obj.paper.xlink, att, value);
					break;

					case 'cursor':
						node.style.cursor = value;
					break;

					case 'clip-rect':
						var rect = String(value).split(REGEX_SEPARATOR);

						if (rect.length == 4) {
							obj.clip && obj.clip.parentNode.parentNode.removeChild(obj.clip.parentNode);

							var el = Util.CREATE_ELEMENT('clipPath');
							var rc = Util.CREATE_ELEMENT('rect');

							el.id = A.guid();

							Util.SET_ATTRS(
								rc,
								{
									x: rect[0],
									y: rect[1],
									width: rect[2],
									height: rect[3]
								}
							);

							el.appendChild(rc);

							obj.paper.defs.appendChild(el);

							Util.SET_ATTRS(
								node,
								{
									'clip-path': 'url(#' + el.id + ')'
								}
							);

							obj.clip = rc;
						}

						if (!value) {
							var clip = DOC.getElementById(node.getAttribute('clip-path').replace(/(^url\(#|\)$)/g, STR_EMPTY));
							clip && clip.parentNode.removeChild(clip);

							Util.SET_ATTRS(
								node,
								{
									'clip-path': STR_EMPTY
								}
							);

							delete obj.clip;
						}
					break;

					case 'path':
						if (obj.type == 'path') {
							var d = 'M0,0';

							if (value) {
								d = Util.pathToAbsolute(value);
							}

							Util.SET_ATTRS(
								node,
								{
									d: d
								}
							);
						}
					break;

					case 'width':
						node.setAttribute(att, value);

						if (attrs.fx) {
							att = 'x';
							value = attrs.x;
						}
						else {
							break;
						}

					case 'x':
						if (attrs.fx) {
							value = -attrs.x - (attrs.width || 0);
						}

					case 'rx':
						if (att == 'rx' && obj.type == 'rect') {
							break;
						}

					case 'cx':
						rotxy && (att == 'x' || att == 'cx') && (rotxy[1] += value - attrs[att]);

						node.setAttribute(att, value);

						obj.pattern && Util.UPDATE_POSITION(obj);

					break;

					case 'height':
						node.setAttribute(att, value);

						if (attrs.fy) {
							att = 'y';
							value = attrs.y;
						}
						else {
							break;
						}

					case 'y':
						if (attrs.fy) {
							value = -attrs.y - (attrs.height || 0);
						}

					case 'ry':
						if (att == 'ry' && obj.type == 'rect') {
							break;
						}

					case 'cy':
						rotxy && (att == 'y' || att == 'cy') && (rotxy[2] += value - attrs[att]);

						node.setAttribute(att, value);

						obj.pattern && Util.UPDATE_POSITION(obj);

					break;

					case 'r':
						if (obj.type == 'rect') {
							Util.SET_ATTRS(
								node,
								{
									rx: value,
									ry: value
								}
							);
						}
						else {
							node.setAttribute(att, value);
						}

					break;

					case 'src':
						if (obj.type == 'image') {
							node.setAttributeNS(obj.paper.xlink, 'href', value);
						}

					break;

					case 'stroke-width':
						node.style.strokeWidth = value;
						// Need following line for Firefox
						node.setAttribute(att, value);

						if (attrs['stroke-dasharray']) {
							addDashes(obj, attrs['stroke-dasharray']);
						}

					break;

					case 'stroke-dasharray':
						addDashes(obj, value);

					break;

					case 'translation':
						var xy = String(value).split(REGEX_SEPARATOR);

						xy[0] = +xy[0] || 0;
						xy[1] = +xy[1] || 0;

						if (rotxy) {
							rotxy[1] += xy[0];
							rotxy[2] += xy[1];
						}

						Util.translate.call(obj, xy[0], xy[1]);

					break;

					case 'scale':
						xy = String(value).split(REGEX_SEPARATOR);

						obj.scale(
							+xy[0] || 1,
							+xy[1] || +xy[0] || 1,
							isNaN(parseFloat(xy[2])) ? null : +xy[2],
							isNaN(parseFloat(xy[3])) ? null : +xy[3]
						);

					break;

					case STR_FILL:
						var isURL = String(value).match(REGEX_ISURL);

						if (isURL) {
							el = Util.CREATE_ELEMENT('pattern');

							var ig = Util.CREATE_ELEMENT('image');

							el.id = A.guid();

							Util.SET_ATTRS(
								el,
								{
									x: 0,
									y: 0,
									patternUnits: 'userSpaceOnUse',
									height: 1,
									width: 1
								}
							);

							Util.SET_ATTRS(
								ig,
								{
									x: 0,
									y: 0
								}
							);

							ig.setAttributeNS(obj.paper.xlink, 'href', isURL[1]);

							el.appendChild(ig);

							var img = DOC.createElement('img');

							img.style.cssText = 'position: absolute; left: -9999em; top: -9999em';

							img.onload = function() {
								var attrDimensions = {
									width: this.offsetWidth,
									height: this.offsetHeight
								};

								Util.SET_ATTRS(el, attrDimensions);
								Util.SET_ATTRS(ig, attrDimensions);

								DOC.body.removeChild(this);

								obj.paper.safari();
							};

							DOC.body.appendChild(img);

							img.src = isURL[1];

							obj.paper.defs.appendChild(el);

							node.style.fill = 'url(#' + el.id + ')';

							Util.SET_ATTRS(
								node,
								{
									fill: 'url(#' + el.id + ')'
								}
							);

							obj.pattern = el;

							obj.pattern && Util.UPDATE_POSITION(obj);

							break;
						}

						var color = ColorUtil.getRGB(value);

						if (!color.error) {
							delete params.gradient;
							delete attrs.gradient;

							if (!isUndefined(attrs.opacity) && isUndefined(params.opacity)) {
								Util.SET_ATTRS(
									node,
									{
										opacity: attrs.opacity
									}
								);
							}

							if (!isUndefined(attrs['fill-opacity']) && isUndefined(params['fill-opacity'])) {
								Util.SET_ATTRS(
									node,
									{
										'fill-opacity': attrs['fill-opacity']
									}
								);
							}
						}
						else if ((MAP_TYPES_CIRCLE_ELLIPSE.hasOwnProperty(obj.type) || String(value).charAt(0) != 'r') && Impl.addGradientFill(node, value, obj.paper)) {

							attrs.gradient = value;
							attrs.fill = 'none';

							break;
						}

						if (color.hasOwnProperty('o')) {
							Util.SET_ATTRS(
								node,
								{
									'fill-opacity': color.o > 1 ? color.o / 100 : color.o
								}
							);
						}
					case 'stroke':
						color = ColorUtil.getRGB(value);
						node.setAttribute(att, color.hex);

						if (att == 'stroke' && color.hasOwnProperty('o')) {
							Util.SET_ATTRS(
								node,
								{
									'stroke-opacity': color.o > 1 ? color.o / 100 : color.o
								}
							);
						}

					break;

					case 'gradient':
						if (MAP_TYPES_CIRCLE_ELLIPSE.hasOwnProperty(obj.type) || String(value).charAt(0) != 'r') {
							Impl.addGradientFill(node, value, obj.paper);
						}

					break;

					case 'opacity':
						if (attrs.gradient && !attrs.hasOwnProperty('stroke-opacity')) {
							Util.SET_ATTRS(
								node,
								{
									'stroke-opacity': value > 1 ? value / 100 : value
								}
							);
						}
						// fall through
					case 'fill-opacity':
						if (attrs.gradient) {
							var gradient = DOC.getElementById(node.getAttribute(STR_FILL).replace(/^url\(#|\)$/g, STR_EMPTY));

							if (gradient) {
								var stops = gradient.getElementsByTagName('stop');

								stops[stops.length - 1].setAttribute('stop-opacity', value);
							}

							break;
						}
					default:
						if (att == 'font-size') {
							value = parseInt(value, 10) + 'px';
						}

						var cssrule = att.replace(/(\-.)/g, function(w) {
							return TO_UPPER_CASE.call(w.substring(1));
						});

						node.style[cssrule] = value;

						// Need following line for Firefox
						node.setAttribute(att, value);

					break;
				}
			}
		}

		Util.TUNE_TEXT(obj, params);

		if (rotxy) {
			obj.rotate(rotxy.join(STR_SPACE));
		}
		else {
			parseFloat(rot) && obj.rotate(rot, true);
		}
	},

	remove: function() {
		var instance = this;

		var contentBox = instance.get('contentBox');

		contentBox.removeChild(instance.canvas);

		for (var i in instance) {
			if (DRAWING_PROTOTYPE.hasOwnProperty(i)) {
				instance[i] = Util.removed(i);
			}
		}
	}
};

DRAWING_PROTOTYPE._uiSetWidth = function(value) {
	var instance = this;

	Drawing.superclass._uiSetWidth.apply(instance, arguments);

	instance.canvas.setAttribute('width', value);
};

DRAWING_PROTOTYPE._uiSetHeight = function(value) {
	var instance = this;

	Drawing.superclass._uiSetHeight.apply(instance, arguments);

	instance.canvas.setAttribute('height', value);
};

/*
	------ ELEMENT IMPL
*/

ELEMENT_PROTOTYPE.createElement = function(node, drawing) {
	var instance = this;

	instance[0] = node;
	instance.id = A.guid();

	instance.node = node;

	instance.paper = drawing;
	instance.attrs = instance.attrs || {};
	instance.transformations = []; // rotate, translate, scale

	this._ = {
		tx: 0,
		ty: 0,
		rt: {
			deg: 0,
			cx: 0,
			cy: 0
		},
		sx: 1,
		sy: 1
	};

	if (!drawing.bottom) {
		drawing.bottom = instance;
	}

	instance.prev = drawing.top;

	if (drawing.top) {
		drawing.top.next = instance;
	}

	drawing.top = instance;

	instance.next = null;
};

ELEMENT_PROTOTYPE.rotate = function(deg, cx, cy) {
	var instance = this;

	if (instance.removed) {
		return instance;
	}

	if (deg == null) {
		if (instance._.rt.cx) {
			return [
				instance._.rt.deg,
				instance._.rt.cx,
				instance._.rt.cy
			].join(STR_SPACE);
		}

		return instance._.rt.deg;
	}

	var bbox = instance.getRegion();

	deg = String(deg).split(REGEX_SEPARATOR);

	if (deg.length - 1) {
		cx = parseFloat(deg[1]);
		cy = parseFloat(deg[2]);
	}

	deg = parseFloat(deg[0]);

	if (cx != null) {
		instance._.rt.deg = deg;
	}
	else {
		instance._.rt.deg += deg;
	}

	(cy == null) && (cx = null);

	instance._.rt.cx = cx;
	instance._.rt.cy = cy;

	cx = cx == null ? bbox.x + bbox.width / 2 : cx;
	cy = cy == null ? bbox.y + bbox.height / 2 : cy;

	if (instance._.rt.deg) {
		instance.transformations[0] = A.substitute('rotate({0} {1} {2})', [instance._.rt.deg, cx, cy]);

		if (instance.clip) {
			Util.SET_ATTRS(
				instance.clip,
				{
					transform: A.substitute("rotate({0} {1} {2})", [-instance._.rt.deg, cx, cy])
				}
			);
		}
	}
	else {
		instance.transformations[0] = STR_EMPTY;

		if (instance.clip) {
			Util.SET_ATTRS(
				instance.clip,
				{
					transform: STR_EMPTY
				}
			);
		}
	}

	Util.SET_ATTRS(
		instance.node,
		{
			transform: instance.transformations.join(STR_SPACE)
		}
	);

	return instance;
};

ELEMENT_PROTOTYPE.hide = function() {
	var instance = this;

	!instance.removed && (instance.node.style.display = 'none');

	return this;
};

ELEMENT_PROTOTYPE.show = function() {
	var instance = this;

	!instance.removed && (instance.node.style.display = '');

	return instance;
};

ELEMENT_PROTOTYPE.remove = function() {
	var instance = this;

	if (instance.removed) {
		return;
	}

	Util.tear(instance, instance.paper);

	instance.node.parentNode.removeChild(instance.node);

	for (var i in instance) {
		delete instance[i];
	}

	instance.removed = true;
};

ELEMENT_PROTOTYPE.getRegion = function() {
	var instance = this;

	if (instance.removed) {
		return instance;
	}

	if (instance.type == 'path') {
		return Util.pathDimensions(instance.attrs.path);
	}

	var hide = false;

	if (instance.node.style.display == 'none') {
		instance.show();

		hide = true;
	}

	var bbox = {};

	try {
		bbox = instance.node.getBBox();
	} catch(e) {
		// Firefox 3.0.x plays badly here
	} finally {
		bbox = bbox || {};
	}

	if (instance.type == 'text') {
		bbox = {
			x: bbox.x,
			y: Infinity,
			width: 0,
			height: 0
		};

		for (var i = 0, length = instance.node.getNumberOfChars(); i < length; i++) {
			var bb = instance.node.getExtentOfChar(i);

			if (bb.y < bbox.y) {
				bbox.y = bb.y;
			}

			if (bb.y + bb.height - bbox.y > bbox.height) {
				bbox.height = bb.y + bb.height - bbox.y;
			}

			if (bb.x + bb.width - bbox.x > bbox.width) {
				bbox.width = bb.x + bb.width - bbox.x;
			}
		}
	}

	if (hide) {
		instance.hide();
	}

	return bbox;
};

ELEMENT_PROTOTYPE.getBBox = ELEMENT_PROTOTYPE.getRegion;

ELEMENT_PROTOTYPE.attr = function(name, value) {
	var instance = this;

	if (instance.removed) {
		return instance;
	}

	if (name == null) {
		var res = {};

		for (var i in instance.attrs) {
			if (instance.attrs.hasOwnProperty(i)) {
				res[i] = instance.attrs[i];
			}
		}

		instance._.rt.deg && (res.rotation = instance.rotate());

		(instance._.sx != 1 || instance._.sy != 1) && (res.scale = instance.scale());

		res.gradient && res.fill == 'none' && (res.fill = res.gradient) && delete res.gradient;

		return res;
	}

	if (value == null && isString(name)) {
		if (name == 'translation') {
			return Util.translate.call(instance);
		}

		if (name == 'rotation') {
			return instance.rotate();
		}

		if (name == 'scale') {
			return instance.scale();
		}

		if (name == STR_FILL && instance.attrs.fill == 'none' && instance.attrs.gradient) {
			return instance.attrs.gradient;
		}

		return instance.attrs[name];
	}

	if (value == null && isArray(name)) {
		var values = {};

		for (var j = 0, jLength = name.length; j < jLength; j++) {
			values[name[j]] = instance.attr(name[j]);
		}

		return values;
	}

	var params = Util.applyAttributes(instance, name, value);

	Impl.setFillAndStroke(instance, params);

	return instance;
};

ELEMENT_PROTOTYPE.toFront = function() {
	var instance = this;

	if (instance.removed) {
		return instance;
	}

	instance.node.parentNode.appendChild(instance.node);

	var drawing = instance.paper;

	drawing.top != instance && Util.toFront(instance, drawing);

	return instance;
};

ELEMENT_PROTOTYPE.toBack = function() {
	var instance = this;

	if (instance.removed) {
		return instance;
	}

	if (instance.node.parentNode.firstChild != instance.node) {
		instance.node.parentNode.insertBefore(instance.node, instance.node.parentNode.firstChild);

		Util.toBack(instance, instance.paper);
	}

	return instance;
};

ELEMENT_PROTOTYPE.insertAfter = function(element) {
	var instance = this;

	if (instance.removed) {
		return instance;
	}

	var node = element.node || element instanceof Drawing.Set && element.item(element.size()).node;

	if (node.nextSibling) {
		node.parentNode.insertBefore(instance.node, node.nextSibling);
	}
	else {
		node.parentNode.appendChild(instance.node);
	}

	Util.insertAfter(instance, element, instance.paper);

	return instance;
};

ELEMENT_PROTOTYPE.insertBefore = function(element) {
	var instance = this;

	if (instance.removed) {
		return instance;
	}

	var node = element.node || element instanceof Drawing.Set && element.item(0).node;

	node.parentNode.insertBefore(instance.node, node);

	Util.insertBefore(instance, element, instance.paper);

	return instance;
};

ELEMENT_PROTOTYPE.blur = function(size) {
	// Experimental. No Safari support. Use it on your own risk.
	var instance = this;

	if (+size !== 0) {
		var fltr = CREATE_ELEMENT('filter');
		var blur = CREATE_ELEMENT('feGaussianBlur');

		instance.attrs.blur = size;

		fltr.id = A.guid();

		Util.SET_ATTRS(
			blur,
			{
				stdDeviation: +size || 1.5
			}
		);

		fltr.appendChild(blur);

		instance.paper.defs.appendChild(fltr);

		instance._blur = fltr;

		Util.SET_ATTRS(
			instance.node,
			{
				filter: 'url(#' + fltr.id + ')'
			}
		);
	}
	else {
		if (instance._blur) {
			instance._blur.parentNode.removeChild(instance._blur);

			delete instance._blur;
			delete instance.attrs.blur;
		}

		instance.node.removeAttribute('filter');
	}
};

}, '1.0.1' ,{requires:['aui-drawing-base','substitute']});
AUI.add('aui-drawing-vml', function(A) {
var Lang = A.Lang,
	isArray = Lang.isArray,
	isNumber = Lang.isNumber,
	isObject = Lang.isObject,
	isString = Lang.isString,
	isUndefined = Lang.isUndefined,

	CONFIG = A.config,
	DOC = CONFIG.doc,

	ColorUtil = A.ColorUtil,

	Drawing = A.Drawing,
	Element = Drawing.Element,
	Impl,
	Util = Drawing.Util,

	setDefaultUnit = function(value) {
		return isNumber(value) ? value + 'px' : value;
	},

	DEFAULT_VML_COORD_SIZE = 1e3 + STR_SPACE + 1e3,

	DRAWING_PROTOTYPE = Drawing.prototype,
	ELEMENT_PROTOTYPE = Element.prototype,

	MAP_ATTRS_AVAILABLE = Drawing.MAP_ATTRS_AVAILABLE,

	MAP_PATH_LIKE = {
		path: 1,
		rect: 1
	},

	MAP_COMMAND_VML = {
		M: 'm',
		L: 'l',
		C: 'c',
		Z: 'x',
		m: 't',
		l: 'r',
		c: 'v',
		z: 'x'
	},

	MAP_KEYWORDS_VML_STROKE_DASH_ARRAY = {
		'-': 'shortdash',
		'.': 'shortdot',
		'-.': 'shortdashdot',
		'-..': 'shortdashdotdot',
		'. ': 'dot',
		'- ': 'dash',
		'--': 'longdash',
		'- .': 'dashdot',
		'--.': 'longdashdot',
		'--..': 'longdashdotdot'
	},

	MAP_TYPES_CIRCLE_ELLIPSE = Drawing.MAP_TYPES_CIRCLE_ELLIPSE,

	MATH = Math,
	MATH_POW = MATH.pow,
	MATH_ROUND = MATH.round,

	STR_MS_PROG_ID_PREFIX = Drawing.STR_MS_PROG_ID_PREFIX,

	REGEX_ISURL = Drawing.REGEX_ISURL,
	REGEX_RADIAL_GRADIENT = Drawing.REGEX_RADIAL_GRADIENT,
	REGEX_SEPARATOR = Drawing.REGEX_SEPARATOR,
	REGEX_SEPARATOR_GRADIENT = Drawing.REGEX_SEPARATOR_GRADIENT,

	REGEX_VML_BITES = /([clmz]),?([^clmz]*)/gi,
	REGEX_VML_VAL = /-?[^,\s-]+/g,
	REGEX_VML_BLUR = / progid:\S+Blur\([^\)]+\)/g,

	STR_EMPTY = Drawing.STR_EMPTY,
	STR_FILL = Drawing.STR_FILL,
	STR_SPACE = Drawing.STR_SPACE,

	TEXT_LEADING = 1.2,

	TPL_RECTANGLE = 'M{0},{1}l{2},0,0,{3},{4},0z',

	TPL_RECTANGLE_ROUNDED_CORNER = 'M{0},{1}l{2},0a{3},{3},0,0,1,{3},{3}l0,{5}a{3},{3},0,0,1,{4},{3}l{6},0a{3},{3},0,0,1,{4},{4}l0,{7}a{3},{3},0,0,1,{3},{4}z',
	TPL_CSS_TEXT_DEFAULT = 'position: absolute; left: -9999em; top: -9999em; padding: 0; margin: 0; line-height: 1; display: inline;',
	TPL_CSS_TEXT_CANVAS = 'display: inline-block; position: relative; clip: rect(0 {1} {0} 0); overflow: hidden; height: {0}; width: {1};',
	TPL_CSS_TEXT_ELEMENT = 'position: absolute; left: 0; top: 0; height: {0}; width: {1};',

	TO_LOWER_CASE = String.prototype.toLowerCase,
	TO_UPPER_CASE = String.prototype.toUpperCase,

	ZOOM = 10;

DOC.createStyleSheet().addRule('.rvml', 'behavior:url(#default#VML)');

try {
	if (!DOC.namespaces.rvml) {
		DOC.namespaces.add('rvml', 'urn:schemas-microsoft-com:vml');
	}

	Util.CREATE_ELEMENT = function(tagName) {
		return DOC.createElement('<rvml:' + tagName + ' class="rvml">');
	};
} catch (e) {
	Util.CREATE_ELEMENT = function(tagName) {
		return DOC.createElement('<' + tagName + ' xmlns="urn:schemas-microsoft.com:vml" class="rvml">');
	};
}

Util.path2vml = function(path) {
	var total =  /[ahqstv]/ig;
	var command = Util.pathToAbsolute;
	var res;

	if (String(path).match(total)) {
		command = Util.path2curve;
	}

	total = /[clmz]/g;

	if (command == Util.pathToAbsolute && !String(path).match(total)) {
		res = String(path).replace(
			REGEX_VML_BITES,
			function(all, command, args) {
				var vals = [];
				var isMove = TO_LOWER_CASE.call(command) == 'm';
				var res = MAP_COMMAND_VML[command];

				args.replace(
					REGEX_VML_VAL,
					function(value) {
						if (isMove && vals.length == 2) {
							res += vals + MAP_COMMAND_VML[command == 'm' ? 'l' : 'L'];
							vals = [];
						}
						vals.push(MATH_ROUND(value * ZOOM));
					}
				);

				return res + vals;
			}
		);

		return res;
	}

	var pa = command(path);
	var p;
	var r;

	res = [];

	for (var i = 0, length = pa.length; i < length; i++) {
		p = pa[i];
		r = TO_LOWER_CASE.call(pa[i][0]);

		if (r == 'z') {
			r = 'x';
		}

		for (var j = 1, pLength = p.length; j < pLength; j++) {
			r += MATH_ROUND(p[j] * ZOOM) + (j != pLength - 1 ? ',' : STR_EMPTY);
		}

		res.push(r);
	}

	return res.join(STR_SPACE);
};

Util.rectPath = function(x, y, w, h, r) {
	if (r) {
		return A.substitute(
			TPL_RECTANGLE_ROUNDED_CORNER,
			[
				x + r,
				y,
				w - r * 2,
				r,
				-r,
				h - r * 2,
				r * 2 - w,
				r * 2 - h
			]
		);
	}
	else {
		return A.substitute(
			TPL_RECTANGLE,
			[
				x,
				y,
				w,
				h,
				-w
			]
		);
	}
};

Impl = Drawing.Impl = {
	addGradientFill: function(obj, gradient) {
		obj.attrs = obj.attrs || {};

		var attrs = obj.attrs;
		var fill;
		var type = 'linear';
		var fxfy = '.5 .5';
		var angle;

		obj.attrs.gradient = gradient;

		gradient = String(gradient).replace(REGEX_RADIAL_GRADIENT, function(all, fx, fy) {
			type = 'radial';

			if (fx && fy) {
				fx = parseFloat(fx);
				fy = parseFloat(fy);

				if (MATH_POW(fx - 0.5, 2) + MATH_POW(fy - 0.5, 2) > 0.25) {
					fy = MATH.sqrt(0.25 - MATH_POW(fx - 0.5, 2)) * ((fy > 0.5) * 2 - 1) + 0.5;
				}

				fxfy = fx + STR_SPACE + fy;
			}

			return STR_EMPTY;
		});

		gradient = gradient.split(REGEX_SEPARATOR_GRADIENT);

		if (type == 'linear') {
			angle = gradient.shift();

			angle = -parseFloat(angle);

			if (isNaN(angle)) {
				return null;
			}
		}

		var dots = Util.parseDots(gradient);

		if (!dots) {
			return null;
		}

		obj = obj.shape || obj.node;

		fill = obj.getElementsByTagName(STR_FILL)[0] || Util.CREATE_ELEMENT(STR_FILL);

		if (!fill.parentNode) {
			obj.appendChild(fill);
		}

		if (dots.length) {
			fill.on = true;
			fill.method = 'none';

			fill.color = dots[0].color;
			fill.color2 = dots[dots.length - 1].color;

			var colors = [];

			for (var i = 0, length = dots.length; i < length; i++) {
				if (dots[i].offset) {
					colors.push(dots[i].offset + STR_SPACE + dots[i].color);
				}
			}

			if (fill.colors) {
				fill.colors.value = colors.length ? colors.join() : '0% ' + fill.color;
			}

			if (type == 'radial') {
				fill.type = 'gradientradial';
				fill.focus = '100%';
				fill.focussize = fxfy;
				fill.focusposition = fxfy;
			}
			else {
				fill.type = 'gradient';
				fill.angle = (270 - angle) % 360;
			}
		}

		return 1;
	},

	clear: function() {
		var instance = this;

		instance.canvas.innerHTML = STR_EMPTY;

		instance.span = DOC.createElement('span');

		instance.span.style.cssText = TPL_CSS_TEXT_DEFAULT;

		instance.canvas.appendChild(instance.span);

		instance.bottom = instance.top = null;
	},

	createCanvas: function() {
		var instance = this;

		var contentBox = instance.get('contentBox');

		var x = instance.get('x');
		var y = instance.get('y');
		var width = setDefaultUnit(instance.get('width'));
		var height = setDefaultUnit(instance.get('height'));

		var canvas = DOC.createElement('div');

		instance.set('width', 1e3);
		instance.set('height', 1e3);

		instance.coordsize = ZOOM * 1e3 + STR_SPACE + ZOOM * 1e3;
		instance.coordorigin = '0 0';

		var span = DOC.createElement('span');
		span.style.cssText = TPL_CSS_TEXT_DEFAULT;

		canvas.appendChild(span);

		instance.span = span;
		instance.canvas = canvas;

		var canvasStyle = canvas.style;

		canvas.style.cssText = A.substitute(TPL_CSS_TEXT_CANVAS, [height, width]);

		contentBox.prepend(canvas);
	},

	createCircle: function(x, y, r) {
		var instance = this;

		var group = Util.CREATE_ELEMENT('group');
		var oval = Util.CREATE_ELEMENT('oval');
		var ovalStyle = oval.style;

		group.style.cssText = A.substitute(TPL_CSS_TEXT_ELEMENT, [setDefaultUnit(instance.get('height')), setDefaultUnit(instance.get('width'))]);
		group.coordsize = DEFAULT_VML_COORD_SIZE;
		group.coordorigin = instance.coordorigin;
		group.appendChild(oval);

		var circle = new Element(oval, group, instance);

		circle.type = 'circle';

		Impl.setFillAndStroke(
			circle,
			{
				stroke: '#000',
				fill: 'none'
			}
		);

		circle.attrs.cx = x;
		circle.attrs.cy = y;
		circle.attrs.r = r;

		circle.setBox(
			{
				x: x - r,
				y: y - r,
				width: r * 2,
				height: r * 2
			}
		);

		instance.canvas.appendChild(group);

		return circle;
	},

	createEllipse: function(x, y, rx, ry) {
		var instance = this;

		var group = Util.CREATE_ELEMENT('group');
		var oval = Util.CREATE_ELEMENT('oval');

		group.style.cssText = A.substitute(TPL_CSS_TEXT_ELEMENT, [setDefaultUnit(instance.get('height')), setDefaultUnit(instance.get('width'))]);
		group.coordsize = DEFAULT_VML_COORD_SIZE;
		group.coordorigin = instance.coordorigin;
		group.appendChild(oval);

		var ellipse = new Element(oval, group, instance);

		ellipse.type = 'ellipse';

		Impl.setFillAndStroke(
			ellipse,
			{
				stroke: '#000'
			}
		);

		ellipse.attrs.cx = x;
		ellipse.attrs.cy = y;
		ellipse.attrs.rx = rx;
		ellipse.attrs.ry = ry;

		ellipse.setBox(
			{
				x: x - rx,
				y: y - ry,
				width: rx * 2,
				height: ry * 2
			}
		);

		instance.canvas.appendChild(group);

		return ellipse;
	},

	createImage: function(src, x, y, w, h) {
		var instance = this;

		var group = Util.CREATE_ELEMENT('group');
		var oval = Util.CREATE_ELEMENT('image');
		var ovalStyle = oval.style;

		group.style.cssText = A.substitute(TPL_CSS_TEXT_ELEMENT, [setDefaultUnit(instance.get('height')), setDefaultUnit(instance.get('width'))]);
		group.coordsize = DEFAULT_VML_COORD_SIZE;
		group.coordorigin = instance.coordorigin;

		oval.src = src;

		group.appendChild(oval);

		var ellipse = new Element(oval, group, instance);

		ellipse.type = 'image';
		ellipse.attrs.src = src;
		ellipse.attrs.x = x;
		ellipse.attrs.y = y;
		ellipse.attrs.w = w;
		ellipse.attrs.h = h;

		ellipse.setBox(
			{
				x: x,
				y: y,
				width: w,
				height: h
			}
		);

		instance.canvas.appendChild(group);

		return ellipse;
	},

	createPath: function(pathString) {
		var instance = this;

		var group = Util.CREATE_ELEMENT('group');

		group.style.cssText = A.substitute(TPL_CSS_TEXT_ELEMENT, [setDefaultUnit(instance.get('height')), setDefaultUnit(instance.get('width'))]);
		group.coordsize = instance.coordsize;
		group.coordorigin = instance.coordorigin;

		var el = Util.CREATE_ELEMENT('shape');
		var shapeStyle = el.style;

		shapeStyle.width = instance.get('width') + 'px';
		shapeStyle.height = instance.get('height') + 'px';

		el.coordsize = DEFAULT_VML_COORD_SIZE;
		el.coordorigin = instance.coordorigin;

		group.appendChild(el);

		var path = new Element(el, group, instance);

		var attr = {
			fill: 'none',
			stroke: '#000'
		};

		if (pathString) {
			attr.path = pathString;
		}

		path.isAbsolute = true;
		path.type = 'path';
		path.path = [];
		path.Path = STR_EMPTY;

		Impl.setFillAndStroke(path, attr);

		instance.canvas.appendChild(group);

		return path;
	},

	createRectangle: function(x, y, w, h, r) {
		var instance = this;

		var path = Util.rectPath(x, y, w, h, r);
		var rect = instance.path(path);
		var attrs = rect.attrs;

		rect.X = attrs.x = x;
		rect.Y = attrs.y = y;
		rect.W = attrs.width = w;
		rect.H = attrs.height = h;

		attrs.r = r;
		attrs.path = path;

		rect.type = 'rect';

		return rect;
	},

	createText: function(x, y, text) {
		var instance = this;

		var group = Util.CREATE_ELEMENT('group');
		var el = Util.CREATE_ELEMENT('shape');
		var shapeStyle = el.style;
		var path = Util.CREATE_ELEMENT('path');
		var ps = path.style;
		var textpath = Util.CREATE_ELEMENT('textpath');

		var width = setDefaultUnit(instance.get('width'));
		var height = setDefaultUnit(instance.get('height'));

		group.style.cssText = A.substitute(TPL_CSS_TEXT_ELEMENT, [height, width]);
		group.coordsize = DEFAULT_VML_COORD_SIZE;
		group.coordorigin = instance.coordorigin;

		path.v = A.substitute(
			'm{0},{1}l{2},{1}',
			[
				MATH_ROUND(x * 10),
				MATH_ROUND(y * 10),
				MATH_ROUND(x * 10) + 1
			]
		);

		path.textpathok = true;

		shapeStyle.width = width;
		shapeStyle.height = height;

		textpath.string = String(text);
		textpath.on = true;

		el.appendChild(textpath);
		el.appendChild(path);

		group.appendChild(el);

		var textElement = new Element(textpath, group, instance);

		textElement.shape = el;
		textElement.textpath = path;
		textElement.type = 'text';
		textElement.attrs.text = text;
		textElement.attrs.x = x;
		textElement.attrs.y = y;
		textElement.attrs.w = 1;
		textElement.attrs.h = 1;

		Impl.setFillAndStroke(
			textElement,
			{
				font: MAP_ATTRS_AVAILABLE.font,
				stroke: 'none',
				fill: '#000'
			}
		);

		textElement.setBox();

		instance.canvas.appendChild(group);

		return textElement;
	},

	remove: function() {
		var instance = this;

		instance.canvas.parentNode.removeChild(instance.canvas);

		for (var i in instance) {
			if (DRAWING_PROTOTYPE.hasOwnProperty(i)) {
				instance[i] = Util.removed(i);
			}
		}

		return true;
	},

	setFillAndStroke: function(obj, params) {
		obj.attrs = obj.attrs || {};

		var node = obj.node;
		var a = obj.attrs;

		var nodeStyle = node.style;
		var xy;

		var newpath = (params.x != a.x || params.y != a.y || params.width != a.width || params.height != a.height || params.r != a.r) && obj.type == 'rect';

		var res = obj;

		for (var par in params) {
			if (params.hasOwnProperty(par)) {
				a[par] = params[par];
			}
		}

		if (newpath) {
			a.path = Util.rectPath(a.x, a.y, a.width, a.height, a.r);
			obj.X = a.x;
			obj.Y = a.y;
			obj.W = a.width;
			obj.H = a.height;
		}

		if (params.href) {
			node.href = params.href;
		}

		if (params.title) {
			node.title = params.title;
		}

		if (params.target) {
			node.target = params.target;
		}

		if (params.cursor) {
			nodeStyle.cursor = params.cursor;
		}

		if ('blur' in params) {
			obj.blur(params.blur);
		}

		if (params.path && obj.type == 'path' || newpath) {
			node.path = Util.path2vml(a.path);
		}

		if (params.rotation != null) {
			obj.rotate(params.rotation, true);
		}

		if (params.translation) {
			xy = String(params.translation).split(REGEX_SEPARATOR);

			Util.translate.call(obj, xy[0], xy[1]);

			if (obj._.rt.cx != null) {
				obj._.rt.cx +=+ xy[0];
				obj._.rt.cy +=+ xy[1];

				obj.setBox(obj.attrs, xy[0], xy[1]);
			}
		}

		if (params.scale) {
			xy = String(params.scale).split(REGEX_SEPARATOR);
			obj.scale(+xy[0] || 1, +xy[1] || +xy[0] || 1, +xy[2] || null, +xy[3] || null);
		}

		if ('clip-rect' in params) {
			var rect = String(params['clip-rect']).split(REGEX_SEPARATOR);

			if (rect.length == 4) {
				rect[2] = +rect[2] + (+rect[0]);
				rect[3] = +rect[3] + (+rect[1]);

				var div = node.clipRect || DOC.createElement('div');
				var dstyle = div.style;
				var group = node.parentNode;

				dstyle.clip = A.substitute('rect({1}px {2}px {3}px {0}px)', rect);

				if (!node.clipRect) {
					dstyle.position = 'absolute';
					dstyle.top = 0;
					dstyle.left = 0;

					dstyle.width = obj.paper.get('width') + 'px';
					dstyle.height = obj.paper.get('height') + 'px';

					group.parentNode.insertBefore(div, group);

					div.appendChild(group);
					node.clipRect = div;
				}
			}

			if (!params['clip-rect'] && node.clipRect) {
				node.clipRect.style.clip = STR_EMPTY;
			}
		}

		if (obj.type == 'image' && params.src) {
			node.src = params.src;
		}

		if (obj.type == 'image' && params.opacity) {
			node.filterOpacity = STR_MS_PROG_ID_PREFIX + '.Alpha(opacity=' + (params.opacity * 100) + ')';

			nodeStyle.filter = (node.filterMatrix || STR_EMPTY) + (node.filterOpacity || STR_EMPTY);
		}

		if (params.font) {
			nodeStyle.font = params.font;
		}

		if (params['font-family']) {
			nodeStyle.fontFamily = '"' + params['font-family'].split(',')[0].replace(/^['"]+|['"]+$/g, STR_EMPTY) + '"';
		}

		if (params['font-size']) {
			nodeStyle.fontSize = params['font-size'];
		}

		if (params['font-weight']) {
			nodeStyle.fontWeight = params['font-weight'];
		}

		if (params['font-style']) {
			nodeStyle.fontStyle = params['font-style'];
		}

		if (params.opacity != null ||
			params['stroke-width'] != null ||
			params.fill != null ||
			params.stroke != null ||
			params['stroke-width'] != null ||
			params['stroke-opacity'] != null ||
			params['fill-opacity'] != null ||
			params['stroke-dasharray'] != null ||
			params['stroke-miterlimit'] != null ||
			params['stroke-linejoin'] != null ||
			params['stroke-linecap'] != null) {

			node = obj.shape || node;

			var fill = (node.getElementsByTagName(STR_FILL) && node.getElementsByTagName(STR_FILL)[0]);
			var newfill = false;
			var opacity;

			if (!fill) {
				newfill = fill = Util.CREATE_ELEMENT(STR_FILL);
			}

			if ('fill-opacity' in params || 'opacity' in params) {
				opacity = ((+a['fill-opacity'] + 1 || 2) - 1) * ((+a.opacity + 1 || 2) - 1) * ((+ColorUtil.getRGB(params.fill).o + 1 || 2) - 1);

				if (opacity < 0) {
					opacity = 0;
				}
				else if (opacity > 1) {
					opacity = 1;
				}

				fill.opacity = opacity;
			}

			if (params.fill) {
				fill.on = true;
			}

			if (fill.on == null || params.fill == 'none') {
				fill.on = false;
			}

			if (fill.on && params.fill) {
				var isURL = params.fill.match(REGEX_ISURL);

				if (isURL) {
					fill.src = isURL[1];
					fill.type = 'tile';
				}
				else {
					var fillColor = ColorUtil.getRGB(params.fill);

					fill.color = fillColor.hex;
					fill.src = STR_EMPTY;
					fill.type = 'solid';

					if (
						fillColor.error &&
						(res.type in MAP_TYPES_CIRCLE_ELLIPSE || String(params.fill).charAt(0) != 'r') &&
						Impl.addGradientFill(res, params.fill)
					) {
						a.fill = 'none';
						a.gradient = params.fill;
					}
				}
			}

			if (newfill) {
				node.appendChild(fill);
			}

			var stroke = (node.getElementsByTagName('stroke') && node.getElementsByTagName('stroke')[0]);
			var newstroke = false;

			if (!stroke) {
				newstroke = stroke = Util.CREATE_ELEMENT('stroke');
			}

			if ((params.stroke && params.stroke != 'none') ||
				params['stroke-width'] ||
				params['stroke-opacity'] != null ||
				params['stroke-dasharray'] ||
				params['stroke-miterlimit'] ||
				params['stroke-linejoin'] ||
				params['stroke-linecap']) {

				stroke.on = true;
			}

			if (params.stroke == 'none' || stroke.on == null || params.stroke == 0 || params['stroke-width'] == 0) {
				stroke.on = false;
			}

			var strokeColor = ColorUtil.getRGB(params.stroke);

			if (stroke.on && params.stroke) {
				stroke.color = strokeColor.hex;
			}

			opacity = ((+a['stroke-opacity'] + 1 || 2) - 1) * ((+a.opacity + 1 || 2) - 1) * ((+strokeColor.o + 1 || 2) - 1);

			var width = (parseFloat(params['stroke-width']) || 1) * 0.75;

			if (opacity < 0) {
				opacity = 0;
			}
			else if (opacity > 1) {
				opacity = 1;
			}

			if (params['stroke-width'] == null) {
				width = a['stroke-width'];
			}

			if (params['stroke-width']) {
				stroke.weight = width;
			}

			if (width && width < 1 && (opacity *= width)) {
				stroke.weight = 1;
			}

			stroke.opacity = opacity;

			if (params['stroke-linejoin']) {
				stroke.joinstyle = params['stroke-linejoin'] || 'miter';
			}

			stroke.miterlimit = params['stroke-miterlimit'] || 8;

			var paramStrokeLinecap = params['stroke-linecap'];

			if (paramStrokeLinecap) {
				stroke.endcap = 'round';

				if (paramStrokeLinecap == 'butt') {
					 stroke.endcap = 'flat';
				}
				else if (paramStrokeLinecap == 'square') {
					stroke.endcap = paramStrokeLinecap;
				}
			}

			var paramStrokeDashArray = params['stroke-dasharray'];

			if (paramStrokeDashArray) {
				stroke.dashstyle = STR_EMPTY;

				if (MAP_KEYWORDS_VML_STROKE_DASH_ARRAY.hasOwnProperty(paramStrokeDashArray)) {
					stroke.dashstyle = MAP_KEYWORDS_VML_STROKE_DASH_ARRAY[params['stroke-dasharray']];
				}
			}

			if (newstroke) {
				node.appendChild(stroke);
			}
		}

		if (res.type == 'text') {
			nodeStyle = res.paper.span.style;

			if (a.font) {
				nodeStyle.font = a.font;
			}

			if (a['font-family']) {
				nodeStyle.fontFamily = a['font-family'];
			}

			if (a['font-size']) {
				nodeStyle.fontSize = a['font-size'];
			}

			if (a['font-weight']) {
				nodeStyle.fontWeight = a['font-weight'];
			}

			if (a['font-style']) {
				nodeStyle.fontStyle = a['font-style'];
			}

			if (res.node.string) {
				res.paper.span.innerHTML = String(res.node.string).replace(/</g, '&#60;').replace(/&/g, '&#38;').replace(/\n/g, '<br>');
			}

			res.W = a.w = res.paper.span.offsetWidth;
			res.H = a.h = res.paper.span.offsetHeight;
			res.X = a.x;
			res.Y = a.y + MATH_ROUND(res.H / 2);

			// text-anchor emulation
			switch (a['text-anchor']) {
				case 'start':
					res.node.style['v-text-align'] = 'left';
					res.bbx = MATH_ROUND(res.W / 2);
				break;

				case 'end':
					res.node.style['v-text-align'] = 'right';
					res.bbx = -MATH_ROUND(res.W / 2);
				break;

				default:
					res.node.style['v-text-align'] = 'center';
				break;
			}
		}
	}
};

ELEMENT_PROTOTYPE.createElement = function(node, group, drawing) {
	var instance = this;

	instance[0] = node;
	instance.id = A.guid();

	instance.node = node;

	instance.X = 0;
	instance.Y = 0;

	instance.attrs = {};
	instance.Group = group;
	instance.paper = drawing;

	instance._ = {
		tx: 0,
		ty: 0,
		rt: {
			deg: 0
		},
		sx: 1,
		sy: 1
	};

	if (!drawing.bottom) {
		drawing.bottom = instance;
	}

	instance.prev = drawing.top;

	if (drawing.top) {
		drawing.top.next = instance;
	}

	drawing.top = instance;

	instance.next = null;
};

ELEMENT_PROTOTYPE.rotate = function(deg, cx, cy) {
	if (this.removed) {
		return this;
	}

	if (deg == null) {
		if (this._.rt.cx) {
			return [
				this._.rt.deg,
				this._.rt.cx,
				this._.rt.cy
			].join(STR_SPACE);
		}

		return this._.rt.deg;
	}

	deg = String(deg).split(REGEX_SEPARATOR);

	if (deg.length - 1) {
		cx = parseFloat(deg[1]);
		cy = parseFloat(deg[2]);
	}

	deg = parseFloat(deg[0]);

	if (cx != null) {
		this._.rt.deg = deg;
	}
	else {
		this._.rt.deg += deg;
	}

	if (cy == null) {
		cx = null;
	}

	this._.rt.cx = cx;
	this._.rt.cy = cy;

	this.setBox(this.attrs, cx, cy);

	this.Group.style.rotation = this._.rt.deg;

	// gradient fix for rotation. TODO
	// var fill = (this.shape || this.node).getElementsByTagName(STR_FILL);
	// fill = fill[0] || {};
	// var b = ((360 - this._.rt.deg) - 270) % 360;
	// !R.is(fill.angle, "undefined") && (fill.angle = b);

	return this;
};

ELEMENT_PROTOTYPE.setBox = function(params, cx, cy) {
	if (this.removed) {
		return this;
	}

	var gs = this.Group.style;
	var os = (this.shape && this.shape.style) || this.node.style;

	params = params || {};

	for (var i in params) {
		if (params.hasOwnProperty(i)) {
			this.attrs[i] = params[i];
		}
	}

	cx = cx || this._.rt.cx;
	cy = cy || this._.rt.cy;

	var attr = this.attrs;
	var x;
	var y;
	var w;
	var h;

	switch (this.type) {
		case 'circle':
			x = attr.cx - attr.r;
			y = attr.cy - attr.r;
			w = h = attr.r * 2;
		break;

		case 'ellipse':
			x = attr.cx - attr.rx;
			y = attr.cy - attr.ry;
			w = attr.rx * 2;
			h = attr.ry * 2;
		break;

		case 'image':
			x = +attr.x;
			y = +attr.y;
			w = attr.width || 0;
			h = attr.height || 0;
		break;

		case 'text':
			this.textpath.v = [
				'm', MATH_ROUND(attr.x),
				', ',
				MATH_ROUND(attr.y - 2),
				'l',
				MATH_ROUND(attr.x) + 1,
				', ',
				MATH_ROUND(attr.y - 2)
			].join(STR_EMPTY);

			x = attr.x - MATH_ROUND(this.W / 2);
			y = attr.y - this.H / 2;
			w = this.W;
			h = this.H;

		break;

		case 'rect':
		case 'path':
			if (!this.attrs.path) {
				x = 0;
				y = 0;
				w = this.paper.get('width');
				h = this.paper.get('height');
			}
			else {
				var dim = Util.pathDimensions(this.attrs.path);

				x = dim.x;
				y = dim.y;
				w = dim.width;
				h = dim.height;
			}
		break;

		default:
			x = 0;
			y = 0;
			w = this.paper.get('width');
			h = this.paper.get('height');
		break;
	}

	cx = (cx == null) ? x + w / 2 : cx;
	cy = (cy == null) ? y + h / 2 : cy;

	var paperWidth = this.paper.get('width');
	var paperHeight = this.paper.get('height');

	var left = cx - paperWidth / 2;
	var top = cy - paperHeight / 2;
	var t;

	if (gs.left != (t = left + 'px')) {
		gs.left = t;
	}

	if (gs.top != (t = top + 'px')) {
		gs.top = t;
	}

	this.X = MAP_PATH_LIKE.hasOwnProperty(this.type) ? -left : x;
	this.Y = MAP_PATH_LIKE.hasOwnProperty(this.type) ? -top : y;

	this.W = w;
	this.H = h;

	if (MAP_PATH_LIKE.hasOwnProperty(this.type)) {
		if (os.left != (t = -left * ZOOM + 'px')) {
			os.left = t;
		}

		if (os.top != (t = -top * ZOOM + 'px')) {
			os.top = t;
		}
	}
	else if (this.type == 'text') {
		if (os.left != (t = -left + 'px')) {
			os.left = t;
		}

		if (os.top != (t = -top + 'px')) {
			os.top = t;
		}
	}
	else {
		if (gs.width != (t = paperWidth + 'px')) {
			gs.width = t;
		}

		if (gs.height != (t = paperHeight + 'px')) {
			gs.height = t;
		}

		if (os.left != (t = x - left + 'px')) {
			os.left = t;
		}

		if (os.top != (t = y - top + 'px')) {
			os.top = t;
		}

		if (os.width != (t = w + 'px')) {
			os.width = t;
		}

		if (os.height != (t = h + 'px')) {
			os.height = t;
		}
	}
};

ELEMENT_PROTOTYPE.hide = function() {
	if (!this.removed) {
		this.Group.style.display = 'none';
	}

	return this;
};

ELEMENT_PROTOTYPE.show = function() {
	if (!this.removed) {
		this.Group.style.display = 'block';
	}

	return this;
};

ELEMENT_PROTOTYPE.getRegion = function() {
	if (this.removed) {
		return this;
	}

	if (MAP_PATH_LIKE.hasOwnProperty(this.type)) {
		return Util.pathDimensions(this.attrs.path);
	}

	return {
		x: this.X + (this.bbx || 0),
		y: this.Y,
		width: this.W,
		height: this.H
	};
};

ELEMENT_PROTOTYPE.remove = function() {
	if (this.removed) {
		return;
	}

	Util.tear(this, this.paper);

	this.node.parentNode.removeChild(this.node);

	this.Group.parentNode.removeChild(this.Group);

	if (this.shape) {
		this.shape.parentNode.removeChild(this.shape);
	}

	for (var i in this) {
		delete this[i];
	}

	this.removed = true;
};

ELEMENT_PROTOTYPE.attr = function(name, value) {
	var instance = this;

	if (instance.removed) {
		return instance;
	}

	var i;

	if (name == null) {
		var res = {};

		for (i in instance.attrs) {
			if (instance.attrs.hasOwnProperty(i)) {
				res[i] = instance.attrs[i];
			}
		}

		if (instance._.rt.deg) {
			res.rotation = instance.rotate();
		}

		if (instance._.sx != 1 || instance._.sy != 1) {
			res.scale = instance.scale();
		}

		if (res.gradient && res.fill == 'none') {
			res.fill = res.gradient;

			delete res.gradient;
		}

		return res;
	}

	if (value == null && isString(name)) {
		if (name == 'translation') {
			return Util.translate.call(instance);
		}

		if (name == 'rotation') {
			return instance.rotate();
		}

		if (name == 'scale') {
			return instance.scale();
		}

		if (name == STR_FILL && instance.attrs.fill == 'none' && instance.attrs.gradient) {
			return instance.attrs.gradient;
		}

		return instance.attrs[name];
	}

	if (instance.attrs && value == null && isArray(name)) {
		var length;
		var values = {};

		for (i = 0, length = name.length; i < length; i++) {
			values[name[i]] = instance.attr(name[i]);
		}

		return values;
	}

	var params = Util.applyAttributes(instance, name, value);

	if (params) {
		if (params.text && instance.type == 'text') {
			instance.node.string = params.text;
		}

		Impl.setFillAndStroke(instance, params);

		if (
			params.gradient &&
			((MAP_TYPES_CIRCLE_ELLIPSE).hasOwnProperty(instance.type) || String(params.gradient).charAt(0) != 'r')
		) {
			Impl.addGradientFill(instance, params.gradient);
		}

		if (!MAP_PATH_LIKE.hasOwnProperty(instance.type) || instance._.rt.deg) {
			instance.setBox(instance.attrs);
		}
	}

	return instance;
};

ELEMENT_PROTOTYPE.toFront = function() {
	if (!this.removed) {
		this.Group.parentNode.appendChild(this.Group);
	}

	if (this.paper.top != this) {
		Util.toFront(this, this.paper);
	}

	return this;
};

ELEMENT_PROTOTYPE.toBack = function() {
	if (this.removed) {
		return this;
	}

	if (this.Group.parentNode.firstChild != this.Group) {
		this.Group.parentNode.insertBefore(this.Group, this.Group.parentNode.firstChild);
		Util.toBack(this, this.paper);
	}

	return this;
};

ELEMENT_PROTOTYPE.insertAfter = function(element) {
	if (this.removed) {
		return this;
	}

	if (element instanceof Drawing.Set) {
		element = element.item(element.size());
	}

	if (element.Group.nextSibling) {
		element.Group.parentNode.insertBefore(this.Group, element.Group.nextSibling);
	}
	else {
		element.Group.parentNode.appendChild(this.Group);
	}

	Util.insertAfter(this, element, this.paper);

	return this;
};

ELEMENT_PROTOTYPE.insertBefore = function(element) {
	if (this.removed) {
		return this;
	}

	if (element instanceof Drawing.Set) {
		element = element.item(0);
	}

	element.Group.parentNode.insertBefore(this.Group, element.Group);

	Util.insertBefore(this, element, this.paper);

	return this;
};

ELEMENT_PROTOTYPE.blur = function(size) {
	var s = this.node.runtimeStyle;
	var f = s.filter;

	f = f.replace(REGEX_VML_BLUR, STR_EMPTY);

	if (+size !== 0) {
		this.attrs.blur = size;

		s.filter = f + STR_SPACE + STR_MS_PROG_ID_PREFIX + '.Blur(pixelradius=' + (+size || 1.5) + ')';
		s.margin = A.substitute('-{0}px 0 0 -{0}px', [MATH_ROUND(+size || 1.5)]);
	}
	else {
		s.filter = f;
		s.margin = 0;

		delete this.attrs.blur;
	}
};

// We need to not sync the width and height of the boundingBox
DRAWING_PROTOTYPE._SYNC_UI_ATTRS = DRAWING_PROTOTYPE._SYNC_UI_ATTRS.join(',').replace(/,(height|width)/g, '').split(',');

DRAWING_PROTOTYPE._uiSetWidth = function(value) {
	var instance = this;

	Drawing.superclass._uiSetWidth.apply(instance, arguments);

	var canvasStyle = instance.canvas.style;

	var height = instance.get('height');

	if (isNumber(value)) {
		value += 'px';
	}

	if (isNumber(height)) {
		height += 'px';
	}

	canvasStyle.width = value;
	canvasStyle.clip = 'rect(0 ' + value + ' ' + height + ' 0)';
};

DRAWING_PROTOTYPE._uiSetHeight = function(value) {
	var instance = this;

	Drawing.superclass._uiSetHeight.apply(instance, arguments);

	var canvasStyle = instance.canvas.style;

	var width = instance.get('width');

	if (isNumber(value)) {
		value += 'px';
	}

	if (isNumber(width)) {
		width += 'px';
	}

	canvasStyle.height = value;
	canvasStyle.clip = 'rect(0 ' + width  + ' ' + value + ' 0)';
};

}, '1.0.1' ,{requires:['aui-drawing-base','substitute']});
AUI.add('aui-drawing-animate', function(A) {
var Lang = A.Lang,
	isFunction = Lang.isFunction,
	isString = Lang.isString,

	ColorUtil = A.ColorUtil,

	Drawing = A.Drawing,
	Element = Drawing.Element,
	Set = Drawing.Set,
	Util = Drawing.Util,

	ELEMENT_PROTOTYPE = Element.prototype,

	MAP_ATTRS_AVAILABLE = Drawing.MAP_ATTRS_AVAILABLE,

	MATH = Math,
	MATH_ABS = MATH.abs,
	MATH_MAX = MATH.max,
	MATH_MIN = MATH.min,
	MATH_POW = MATH.pow,
	MATH_ROUND = MATH.round,

	REGEX_ANIMATE_KEYFRAMES = /^(from|to|\d+%)$/,
	REGEX_BEZIER = /^cubic-bezier\(([^,]+),([^,]+),([^,]+),([^\)]+)\)/,
	REGEX_SEPARATOR = Drawing.REGEX_SEPARATOR,

	STR_EMPTY = Drawing.STR_EMPTY,
	STR_SPACE = Drawing.STR_SPACE,

	STR_NUMBER = 'number',

	MAP_ATTRS_AVAILABLE_ANIM = {
		along: 'along',
		blur: STR_NUMBER,
		'clip-rect': 'csv',
		cx: STR_NUMBER,
		cy: STR_NUMBER,
		fill: 'color',
		'fill-opacity': STR_NUMBER,
		'font-size': STR_NUMBER,
		height: STR_NUMBER,
		opacity: STR_NUMBER,
		path: 'path',
		r: STR_NUMBER,
		rotation: 'csv',
		rx: STR_NUMBER,
		ry: STR_NUMBER,
		scale: 'csv',
		stroke: 'color',
		'stroke-opacity': STR_NUMBER,
		'stroke-width': STR_NUMBER,
		translation: 'csv',
		width: STR_NUMBER,
		x: STR_NUMBER,
		y: STR_NUMBER
	};

var Animation = Drawing.Animation = {};

Animation.easing_formulas = {
	linear: function(n) {
		return n;
	},

	'<': function(n) {
		return MATH_POW(n, 3);
	},

	'>': function(n) {
		return MATH_POW(n - 1, 3) + 1;
	},

	'<>': function(n) {
		n = n * 2;

		if (n < 1) {
			return MATH_POW(n, 3) / 2;
		}

		n -= 2;

		return (MATH_POW(n, 3) + 2) / 2;
	},

	backIn: function(n) {
		var s = 1.70158;

		return n * n * ((s + 1) * n - s);
	},

	backOut: function(n) {
		n = n - 1;

		var s = 1.70158;

		return n * n * ((s + 1) * n + s) + 1;
	},

	elastic: function(n) {
		if (n == 0 || n == 1) {
			return n;
		}

		var p = 0.3;
		var s = p / 4;

		return MATH_POW(2, -10 * n) * MATH.sin((n - s) * (2 * MATH.PI) / p) + 1;
	},

	bounce: function(n) {
		var s = 7.5625;
		var p = 2.75;
		var l;

		if (n < (1 / p)) {
			l = s * n * n;
		}
		else {
			if (n < (2 / p)) {
				n -= (1.5 / p);
				l = s * n * n + 0.75;
			}
			else {
				if (n < (2.5 / p)) {
					n -= (2.25 / p);
					l = s * n * n + 0.9375;
				}
				else {
					n -= (2.625 / p);
					l = s * n * n + 0.984375;
				}
			}
		}

		return l;
	}
};

var animationElements = [];

Animation.elements = animationElements;

Animation.animation = function() {
	var Now = +new Date;

	for (var l = 0; l < animationElements.length; l++) {
		if (l != 'length' && animationElements.hasOwnProperty(l)) {
			var e = animationElements[l];

			if (e.stop || e.el.removed) {
				continue;
			}

			var time = Now - e.start;
			var ms = e.ms;
			var easing = e.easing;
			var from = e.from;
			var diff = e.diff;
			var to = e.to;
			var t = e.t;
			var that = e.el;
			var set = {};
			var now;

			if (time < ms) {
				var pos = easing(time / ms);

				for (var attr in from) {
					if (from.hasOwnProperty(attr)) {
						switch (MAP_ATTRS_AVAILABLE_ANIM[attr]) {
							case 'along':
								now = pos * ms * diff[attr];
								to.back && (now = to.len - now);

								var point = getPointAtLength(to[attr], now);

								that.translate(diff.sx - diff.x || 0, diff.sy - diff.y || 0);

								diff.x = point.x;
								diff.y = point.y;

								that.translate(point.x - diff.sx, point.y - diff.sy);

								to.rot && that.rotate(diff.r + point.alpha, point.x, point.y);

							break;

							case STR_NUMBER:
								now = +from[attr] + pos * ms * diff[attr];
							break;

							case 'color':
								now = 'rgb(' + [
									upto255(MATH_ROUND(from[attr].r + pos * ms * diff[attr].r)),
									upto255(MATH_ROUND(from[attr].g + pos * ms * diff[attr].g)),
									upto255(MATH_ROUND(from[attr].b + pos * ms * diff[attr].b))
								].join(',') + ')';

							break;

							case 'path':
								now = [];

								for (var i = 0, length = from[attr].length; i < length; i++) {
									now[i] = [from[attr][i][0]];

									for (var j = 1, jLength = from[attr][i].length; j < jLength; j++) {
										now[i][j] = +from[attr][i][j] + pos * ms * diff[attr][i][j];
									}

									now[i] = now[i].join(STR_SPACE);
								}

								now = now.join(STR_SPACE);
							break;

							case 'csv':
								switch (attr) {
									case 'translation':
										var x = pos * ms * diff[attr][0] - t.x;
										var y = pos * ms * diff[attr][1] - t.y;

										t.x += x;
										t.y += y;

										now = x + STR_SPACE + y;
									break;

									case 'rotation':
										now = +from[attr][0] + pos * ms * diff[attr][0];

										from[attr][1] && (now += ',' + from[attr][1] + ',' + from[attr][2]);
									break;

									case 'scale':
										now = [
											+from[attr][0] + pos * ms * diff[attr][0],
											+from[attr][1] + pos * ms * diff[attr][1],
											(2 in to[attr] ? to[attr][2] : STR_EMPTY),
											(3 in to[attr] ? to[attr][3] : STR_EMPTY)
										].join(STR_SPACE);
									break;

									case 'clip-rect':
										now = [];
										i = 4;

										while (i--) {
											now[i] = +from[attr][i] + pos * ms * diff[attr][i];
										}

									break;
								}

							break;

							default:
								var from2 = [].concat(from[attr]);
								var now = [];

								i = that.paper.customAttributes[attr].length;

								while (i--) {
									now[i] = +from[i] + pos * ms * diff[attr][i];
								}
							break;
						}

						set[attr] = now;
					}
				}

				that.attr(set);
				that._run && that._run.call(that);
			}
			else {
				if (to.along) {
					point = getPointAtLength(to.along, to.len * !to.back);
					that.translate(diff.sx - (diff.x || 0) + point.x - diff.sx, diff.sy - (diff.y || 0) + point.y - diff.sy);
					to.rot && that.rotate(diff.r + point.alpha, point.x, point.y);
				}

				if (t.x || t.y) {
					that.translate(-t.x, -t.y);
				}

				if (to.scale) {
					to.scale += STR_EMPTY;
				}

				that.attr(to);

				animationElements.splice(l--, 1);
			}
		}
	}

	if (A.UA.svg && that && that.paper) {
		that.paper.safari();
	}

	if (animationElements.length) {
		setTimeout(Animation.animation);
	}
};

var sortByKey = function (a, b) {
    return a.key - b.key;
};

var keyframesRun = function(attr, element, time, prev, prevCallback) {
	var diff = time - prev;

	element.timeouts.push(
		setTimeout(
			function() {
				if (isFunction(prevCallback)) {
					prevCallback.call(element);
				}

				element.animate(attr, diff, attr.easing);
			},
			prev
		)
	);
};

var upto255 = function(color) {
	return MATH_MAX(MATH_MIN(color, 255), 0);
};

function CubicBezierAtTime(t, p1x, p1y, p2x, p2y, duration) {
	var cx = 3 * p1x;
	var bx = 3 * (p2x - p1x) - cx;
	var ax = 1 - cx - bx;
	var cy = 3 * p1y;
	var by = 3 * (p2y - p1y) - cy;
	var ay = 1 - cy - by;

	function sampleCurveX(t) {
		return ((ax * t + bx) * t + cx) * t;
	}

	function solve(x, epsilon) {
		var t = solveCurveX(x, epsilon);

		return ((ay * t + by) * t + cy) * t;
	}

	function solveCurveX(x, epsilon) {
		var t0, t1, t2, x2, d2, i;

		for(t2 = x, i = 0; i < 8; i++) {
			x2 = sampleCurveX(t2) - x;

			if (MATH_ABS(x2) < epsilon) {
				return t2;
			}

			d2 = (3 * ax * t2 + 2 * bx) * t2 + cx;

			if (MATH_ABS(d2) < 1e-6) {
				break;
			}

			t2 = t2 - x2 / d2;
		}

		t0 = 0;
		t1 = 1;
		t2 = x;

		if (t2 < t0) {
			return t0;
		}

		if (t2 > t1) {
			return t1;
		}

		while (t0 < t1) {
			x2 = sampleCurveX(t2);

			if (MATH_ABS(x2 - x) < epsilon) {
				return t2;
			}

			if (x > x2) {
				t0 = t2;
			}
			else {
				t1 = t2;
			}

			t2 = (t1 - t0) / 2 + t0;
		}

		return t2;
	}

	return solve(t, 1 / (200 * duration));
}

ELEMENT_PROTOTYPE.animateWith = function(element, params, ms, easing, callback) {
	for (var i = 0, length = animationElements.length; i < length; i++) {
		if (animationElements[i].el.id == element.id) {
			params.start = animationElements[i].start;
		}
	}

	return this.animate(params, ms, easing, callback);
};

Animation.along = function(isBack) {
	return function(path, ms, rotate, callback) {
		var params = {back: isBack};

		isFunction(rotate) ? (callback = rotate) : (params.rot = rotate);

		path && path.constructor == Element && (path = path.attrs.path);
		path && (params.along = path);

		return this.animate(params, ms, callback);
	};
}

ELEMENT_PROTOTYPE.animateAlong = Animation.along();

ELEMENT_PROTOTYPE.animateAlongBack = Animation.along(1);

ELEMENT_PROTOTYPE.onAnimation = function(f) {
	var instance = this;

	instance._run = f || 0;

	return instance;
};

ELEMENT_PROTOTYPE.animate = function(params, ms, easing, callback) {
	var instance = this;

	instance.timeouts = instance.timeouts || [];

	if (isFunction(easing) || !easing) {
		callback = easing || null;
	}

	if (instance.removed) {
		callback && callback.call(instance);

		return instance;
	}

	var from = {};
	var to = {};
	var diff = {};
	var canAnimate = false;

	var customAttributes = instance.paper.customAttributes;

	for (var attr in params) {
		if (params.hasOwnProperty(attr)) {
			if (MAP_ATTRS_AVAILABLE_ANIM.hasOwnProperty(attr) || customAttributes.hasOwnProperty(attr)) {
				canAnimate = true;
				from[attr] = instance.attr(attr);

				if (from[attr] == null) {
					from[attr] = MAP_ATTRS_AVAILABLE[attr];
				}

				to[attr] = params[attr];

				switch (MAP_ATTRS_AVAILABLE_ANIM[attr]) {
					case 'along':
						var len = getTotalLength(params[attr]);
						var point = getPointAtLength(params[attr], len * !!params.back);
						var bb = instance.getRegion();

						diff[attr] = len / ms;
						diff.tx = bb.x;
						diff.ty = bb.y;
						diff.sx = point.x;
						diff.sy = point.y;
						to.rot = params.rot;
						to.back = params.back;
						to.len = len;

						if (params.rot) {
							diff.r = parseFloat(instance.rotate()) || 0;
						}

					break;

					case STR_NUMBER:
						diff[attr] = (to[attr] - from[attr]) / ms;

					break;

					case 'color':
						from[attr] = ColorUtil.getRGB(from[attr]);

						var toColour = ColorUtil.getRGB(to[attr]);

						diff[attr] = {
							r: (toColour.r - from[attr].r) / ms,
							g: (toColour.g - from[attr].g) / ms,
							b: (toColour.b - from[attr].b) / ms
						};

					break;

					case 'path':
						var pathes = Util.path2curve(from[attr], to[attr]);

						from[attr] = pathes[0];

						var toPath = pathes[1];

						diff[attr] = [];

						for (var i = 0, length = from[attr].length; i < length; i++) {
							diff[attr][i] = [0];

							for (var j = 1, jLength = from[attr][i].length; j < jLength; j++) {
								diff[attr][i][j] = (toPath[i][j] - from[attr][i][j]) / ms;
							}
						}

					break;

					case 'csv':
						var values = String(params[attr]).split(REGEX_SEPARATOR);
						var from2 = String(from[attr]).split(REGEX_SEPARATOR);

						switch (attr) {
							case 'translation':
								from[attr] = [0, 0];

								diff[attr] = [
									values[0] / ms,
									values[1] / ms
								];
							break;

							case 'rotation':
								from[attr] = (from2[1] == values[1] && from2[2] == values[2]) ? from2 : [0, values[1], values[2]];
								diff[attr] = [
									(values[0] - from[attr][0]) / ms,
									0,
									0
								];
							break;

							case 'scale':
								params[attr] = values;
								from[attr] = String(from[attr]).split(REGEX_SEPARATOR);

								diff[attr] = [
									(values[0] - from[attr][0]) / ms,
									(values[1] - from[attr][1]) / ms,
									0,
									0
								];
							break;

							case 'clip-rect':
								from[attr] = String(from[attr]).split(REGEX_SEPARATOR);
								diff[attr] = [];
								i = 4;

								while (i--) {
									diff[attr][i] = (values[i] - from[attr][i]) / ms;
								}

							break;
						}

						to[attr] = values;

					break;

					default:
						values = [].concat(params[attr]);
						from2 = [].concat(from[attr])
						diff[attr] = [];

						i = customAttributes[attr].length;

						while (i--) {
							diff[attr][i] = ((values[i] || 0) - (from2[i] || 0)) / ms;
						}

					break;
				}
			}
		}
	}

	if (!canAnimate) {
		var attrs = [];
		var lastcall;

		for (var key in params) {
			if (params.hasOwnProperty(key) && REGEX_ANIMATE_KEYFRAMES.test(key)) {
				attr = {
					value: params[key]
				};

				if (key == 'from') {
					key = 0;
				}
				else if (key == 'to') {
					key = 100;
				}

				attr.key = parseInt(key, 10);
				attrs.push(attr);
			}
		}

		attrs.sort(sortByKey);

		if (attrs[0].key) {
			attrs.unshift(
				{
					key: 0,
					value: instance.attrs
				}
			);
		}

		var prevAttr;
		var attrLength = attrs.length;

		for (var i = 0; i < attrLength; i++) {
			attr = attrs[i];
			prevAttr = attrs[i - 1];

			keyframesRun(
				attr.value,
				instance, ms / 100 * attr.key,
				ms / 100 * (prevAttr && prevAttr.key || 0),
				prevAttr && prevAttr.value.callback
			);
		}

		lastcall = attrs[attrLength - 1].value.callback;

		if (lastcall) {
			instance.timeouts.push(
				setTimeout(
					function() {
						lastcall.call(instance);
					},
					ms
				)
			);
		}
	}
	else {
		var easingFormula = Animation.easing_formulas[easing];

		if (!easingFormula) {
			easingFormula = String(easingFormula).match(REGEX_BEZIER);

			if (easingFormula && easingFormula.length == 5) {
				var curve = easingFormula;

				easingFormula = function(t) {
					return CubicBezierAtTime(t, +curve[1], +curve[2], +curve[3], +curve[4], ms);
				};
			}
			else {
				easingFormula = function(t) {
					return t;
				};
			}
		}

		animationElements.push(
			{
				start: params.start || +new Date,
				ms: ms,
				easing: easingFormula,
				from: from,
				diff: diff,
				to: to,
				el: instance,
				t: {
					x: 0,
					y: 0
				}
			}
		);

		if (isFunction(callback)) {
			instance._ac = setTimeout(
				function() {
					callback.call(instance);
				},
				ms
			);
		}

		if (animationElements.length == 1) {
			setTimeout(Animation.animation);
		}
	}

	return instance;
};

ELEMENT_PROTOTYPE.stop = function() {
	var instance = this;

	for (var i = 0; i < animationElements.length; i++) {
		if (animationElements[i].el.id == instance.id) {
			animationElements.splice(i--, 1);
		}
	}

	instance.timeouts = [];

	clearTimeout(instance._ac);

	delete instance._ac;

	return instance;
};

ELEMENT_PROTOTYPE.translate = function(x, y) {
	return this.attr(
		{
			translation: x + ' ' + y
		}
	);
};

Set.addMethod(['animateAlong', 'animateAlongBack', 'animateWith', 'onAnimation', 'stop', 'translate']);

Set.prototype.animate = function(params, ms, easing, callback) {
	var instance = this;

	if (isFunction(easing) || !easing) {
		callback = easing || null;
	}

	var items = instance._items;
	var length = items.length;
	var i = length;
	var item;
	var currentItem;
	var collector;

	if (callback) {
		collector = function() {
			if (!--length) {
				callback.call(instance);
			}
		};
	}

	easing = isString(easing) ? easing : collector;

	item = items[--i].animate(params, ms, easing, collector);

	while(i--) {
		currentItem = items[i];
		if (currentItem && !currentItem.removed) {
			currentItem.animateWith(item, params, ms, easing, collector);
		}
	}

	return instance;
};

}, '1.0.1' ,{requires:['aui-drawing-base']});
AUI.add('aui-drawing-drag', function(A) {
var Lang = A.Lang,

	DOC = A.config.doc,

	Drawing = A.Drawing,
	Element = Drawing.Element,

	ELEMENT_PROTOTYPE = Element.prototype,

	MAP_EVENT_CONFIG = {
		standAlone: true
	},

	MAP_EVENT_GESTURES = {
		mousedown: 'gesturemovestart',
		mousemove: 'gesturemove',
		mouseup: 'gesturemoveend'
	},

	SUPPORTS_TOUCH = 'createTouch' in DOC;

var Drag = Drawing.Drag = {
	drags: []
};

Element._getEventType = function(eventType) {
	return MAP_EVENT_GESTURES[eventType] || eventType;
};

Element._getEventConfig = function(eventType, originalType) {
	return MAP_EVENT_GESTURES[originalType] ? MAP_EVENT_CONFIG : null;
};

Drag.dragMove = function(e) {
	var x = e.clientX;
	var y = e.clientY;
	var dragi;
	var drags = Drag.drags;
	var j = drags.length;

	while (j--) {
		dragi = drags[j];

		if (SUPPORTS_TOUCH) {
			var i = e.touches.length;
			var touch;

			while (i--) {
				touch = e.touches[i];

				if (touch.identifier == dragi.el._drag.id) {
					x = touch.clientX;
					y = touch.clientY;

					(e.originalEvent ? e.originalEvent : e).preventDefault();

					break;
				}
			}
		}
		else {
			e.preventDefault();
		}

		if (dragi.move) {
			dragi.move.call(dragi.el, x - dragi.el._drag.x, y - dragi.el._drag.y, x, y);
		}
	}
};

Drag.dragUp = function() {
	Drag._dragMoveHandle.detach();
	Drag._dragUpHandle.detach();

	var drags = Drag.drags;
	var i = drags.length;
	var dragi;

	while (i--) {
		dragi = drags[i];
		dragi.el._drag = {};

		if (dragi.end) {
			dragi.end.call(dragi.el);
		}
	}

	Drag.drags = [];
};

ELEMENT_PROTOTYPE.drag = function(onmove, onstart, onend) {
	var instance = this;

	instance._drag = {};

	instance.on(
		'mousedown',
		function(e) {
			var element = this;

			(e.originalEvent || e).preventDefault();

			element._drag.x = e.clientX;
			element._drag.y = e.clientY;
			element._drag.id = e.identifier;

			if (onstart) {
				onstart.call(element, e.clientX, e.clientY);
			}

			if (!Drag.drags.length) {
				var realMoveEventName = Element._getEventType('mousemove');
				var realUpEventName = Element._getEventType('mouseup');

				Drag._dragMoveHandle = A.on(realMoveEventName, Drag.dragMove, DOC, Element._getEventConfig(realMoveEventName, 'mousemove'));
				Drag._dragUpHandle = A.on(realUpEventName, Drag.dragUp, DOC, Element._getEventConfig(realUpEventName, 'mouseup'));
			}

			Drag.drags.push(
				{
					el: this,
					move: onmove,
					end: onend
				}
			);
		}
	);

	return instance;
};

ELEMENT_PROTOTYPE.undrag = function(onmove, onstart, onend) {
	var instance = this;

	var drags = Drag.drags;

	var i = drags.length;

	while (i--) {
		drags[i].el == instance && (drags[i].move == onmove && drags[i].end == onend) && drags.splice(i, 1);

		if (!drags.length) {
			Drag._dragMoveHandle.detach();
			Drag._dragUpHandle.detach();
		}
	}
};

Drawing.Set.addMethod(['drag', 'undrag']);

}, '1.0.1' ,{requires:['aui-drawing-base','event-gestures']});
AUI.add('aui-drawing-fonts', function(A) {
var Lang = A.Lang,
	isString = Lang.isString,

	Drawing = A.Drawing,

	DRAWING_PROTOTYPE = Drawing.prototype,

	GLYPH_FN_REPLACE = function(command) {
		return GLYPH_MAP_COMMAND[command] || 'M';
	},

	GLYPH_MAP_COMMAND = {
		l: 'L',
		c: 'C',
		x: 'z',
		t: 'm',
		r: 'l',
		v: 'c'
	},

	MAP_FONT_WEIGHT = {
		bold: 700,
		bolder: 800,
		lighter: 300,
		normal: 400
	},

	MATH = Math,
	MATH_MAX = MATH.max,
	MATH_MIN = MATH.min,

	REGEX_GLYPH_COMMANDS = /[mlcxtrv]/g,
	REGEX_SEPARATOR = Drawing.REGEX_SEPARATOR,

	STR_EMPTY = Drawing.STR_EMPTY;

Drawing.registerFont = function(font) {
	if (!font.face) {
		return font;
	}

	this.fonts = this.fonts || {};

	var fontCollection = this.fonts;

	var fontcopy = {
		w: font.w,
		face: {},
		glyphs: {}
	};

	var family = font.face['font-family'];
	var fontFace = font.face;
	var fontCopyFace = fontcopy.face;

	for (var prop in fontFace) {
		if (fontFace.hasOwnProperty(prop)) {
			fontCopyFace[prop] = fontFace[prop];
		}
	}

	if (fontCollection[family]) {
		fontCollection[family].push(fontcopy);
	}
	else {
		fontCollection[family] = [fontcopy];
	}

	if (!font.svg) {
		fontCopyFace['units-per-em'] = parseInt(fontFace['units-per-em'], 10);

		for (var glyph in font.glyphs) {
			if (font.glyphs.hasOwnProperty(glyph)) {
				var path = font.glyphs[glyph];

				var d = path.d;

				if (d) {
					d = d.replace(REGEX_GLYPH_COMMANDS, GLYPH_FN_REPLACE);

					d = 'M' + d + 'z';
				}

				fontcopy.glyphs[glyph] = {
					w: path.w,
					k: {},
					d: d
				};

				if (path.k) {
					for (var k in path.k) {
						if (path.hasOwnProperty(k)) {
							fontcopy.glyphs[glyph].k[k] = path.k[k];
						}
					}
				}
			}
		}
	}

	return font;
};

DRAWING_PROTOTYPE.getFont = function(family, weight, style, stretch) {
	var instance = this;

	stretch = stretch || 'normal';
	style = style || 'normal';

	weight = +weight || MAP_FONT_WEIGHT[weight] || 400;

	if (!Drawing.fonts) {
		return;
	}

	var fontCollection = Drawing.fonts;

	var font = fontCollection[family];

	if (!font) {
		var name = new RegExp('(^|\\s)' + family.replace(/[^\w\d\s+!~.:_-]/g, STR_EMPTY) + '(\\s|$)', 'i');

		for (var fontName in fontCollection) {
			if (fontCollection.hasOwnProperty(fontName)) {
				if (name.test(fontName)) {
					font = fontCollection[fontName];

					break;
				}
			}
		}
	}

	var thefont;

	if (font) {
		var fontFace;
		for (var i = 0, length = font.length; i < length; i++) {
			thefont = font[i];
			fontFace = thefont.face;

			if (
				fontFace['font-weight'] == weight &&
				(fontFace['font-style'] == style || !fontFace['font-style']) &&
				fontFace['font-stretch'] == stretch
			) {
				break;
			}
		}
	}

	return thefont;
};

DRAWING_PROTOTYPE.print = function(x, y, string, font, size, origin, letterSpacing) {
	var instance = this;

	origin = origin || 'middle'; // baseline|middle
	letterSpacing = MATH_MAX(MATH_MIN(letterSpacing || 0, 1), -1);

	var out = instance.createSet();
	var letters = String(string).split(STR_EMPTY);
	var shift = 0;
	var path = STR_EMPTY;
	var scale;

	isString(font) && (font = instance.getFont(font));

	if (font) {
		scale = (size || 16) / font.face['units-per-em'];

		var bb = font.face.bbox.split(REGEX_SEPARATOR);
		var top = +bb[0];
		var height = +bb[1] + (origin == 'baseline' ? bb[3] - bb[1] + (+font.face.descent) : (bb[3] - bb[1]) / 2);

		for (var i = 0, length = letters.length; i < length; i++) {
			var prev = i && font.glyphs[letters[i - 1]] || {};
			var curr = font.glyphs[letters[i]];

			shift += i ? (prev.w || font.w) + (prev.k && prev.k[letters[i]] || 0) + (font.w * letterSpacing) : 0;

			curr && curr.d && out.push(
				instance.path(curr.d).attr(
					{
						fill: '#000',
						stroke: 'none',
						translation: [shift, 0]
					}
				)
			);
		}

		out.scale(scale, scale, top, height).translate(x - top, y - height);
	}

	return out;
};

}, '1.0.1' ,{requires:['aui-drawing-base']});
AUI.add('aui-drawing-safari', function(A) {
A.Drawing.prototype.safari = function() {
	var instance = this;

	var rect = instance.rect(-99, -99, instance.get('width') + 99, instance.get('height') + 99).attr(
		{
			stroke: 'none'
		}
	);

	setTimeout(
		function() {
			rect.remove();
		},
		0
	);
};

}, '1.0.1' ,{requires:['aui-drawing-base']});


AUI.add('aui-drawing', function(A){}, '1.0.1' ,{plugins:{'aui-drawing-vml': {condition: {trigger: 'aui-drawing-base',test: function(A){return A.UA.vml;}}},'aui-drawing-svg': {condition: {trigger: 'aui-drawing-base',test: function(A){return A.UA.svg;}}}, 'aui-drawing-safari': {condition: {trigger: 'aui-drawing-base',test: function(A){var UA = A.UA; return UA.safari && (UA.version.major < 4 || (UA.iphone || UA.ipad));}}}}, skinnable:false, use:['aui-drawing-base', 'aui-drawing-animate', 'aui-drawing-drag', 'aui-drawing-fonts']});

