diff options
Diffstat (limited to '')
-rw-r--r-- | debian/missing-sources/two.js | 10244 |
1 files changed, 10244 insertions, 0 deletions
diff --git a/debian/missing-sources/two.js b/debian/missing-sources/two.js new file mode 100644 index 00000000..859d3b41 --- /dev/null +++ b/debian/missing-sources/two.js @@ -0,0 +1,10244 @@ +/** + * two.js + * a two-dimensional drawing api meant for modern browsers. It is renderer + * agnostic enabling the same api for rendering in multiple contexts: webgl, + * canvas2d, and svg. + * + * Copyright (c) 2012 - 2017 jonobr1 / http://jonobr1.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +this.Two = (function(previousTwo) { + + var root = typeof window != 'undefined' ? window : typeof global != 'undefined' ? global : null; + var toString = Object.prototype.toString; + var _ = { + // http://underscorejs.org/ • 1.8.3 + _indexAmount: 0, + natural: { + slice: Array.prototype.slice, + indexOf: Array.prototype.indexOf, + keys: Object.keys, + bind: Function.prototype.bind, + create: Object.create + }, + identity: function(value) { + return value; + }, + isArguments: function(obj) { + return toString.call(obj) === '[object Arguments]'; + }, + isFunction: function(obj) { + return toString.call(obj) === '[object Function]'; + }, + isString: function(obj) { + return toString.call(obj) === '[object String]'; + }, + isNumber: function(obj) { + return toString.call(obj) === '[object Number]'; + }, + isDate: function(obj) { + return toString.call(obj) === '[object Date]'; + }, + isRegExp: function(obj) { + return toString.call(obj) === '[object RegExp]'; + }, + isError: function(obj) { + return toString.call(obj) === '[object Error]'; + }, + isFinite: function(obj) { + return isFinite(obj) && !isNaN(parseFloat(obj)); + }, + isNaN: function(obj) { + return _.isNumber(obj) && obj !== +obj; + }, + isBoolean: function(obj) { + return obj === true || obj === false || toString.call(obj) === '[object Boolean]'; + }, + isNull: function(obj) { + return obj === null; + }, + isUndefined: function(obj) { + return obj === void 0; + }, + isEmpty: function(obj) { + if (obj == null) return true; + if (isArrayLike && (_.isArray(obj) || _.isString(obj) || _.isArguments(obj))) return obj.length === 0; + return _.keys(obj).length === 0; + }, + isElement: function(obj) { + return !!(obj && obj.nodeType === 1); + }, + isArray: Array.isArray || function(obj) { + return toString.call(obj) === '[object Array]'; + }, + isObject: function(obj) { + var type = typeof obj; + return type === 'function' || type === 'object' && !!obj; + }, + toArray: function(obj) { + if (!obj) { + return []; + } + if (_.isArray(obj)) { + return slice.call(obj); + } + if (isArrayLike(obj)) { + return _.map(obj, _.identity); + } + return _.values(obj); + }, + range: function(start, stop, step) { + if (stop == null) { + stop = start || 0; + start = 0; + } + step = step || 1; + + var length = Math.max(Math.ceil((stop - start) / step), 0); + var range = Array(length); + + for (var idx = 0; idx < length; idx++, start += step) { + range[idx] = start; + } + + return range; + }, + indexOf: function(list, item) { + if (!!_.natural.indexOf) { + return _.natural.indexOf.call(list, item); + } + for (var i = 0; i < list.length; i++) { + if (list[i] === item) { + return i; + } + } + return -1; + }, + has: function(obj, key) { + return obj != null && hasOwnProperty.call(obj, key); + }, + bind: function(func, ctx) { + var natural = _.natural.bind; + if (natural && func.bind === natural) { + return natural.apply(func, slice.call(arguments, 1)); + } + var args = slice.call(arguments, 2); + return function() { + func.apply(ctx, args); + }; + }, + extend: function(base) { + var sources = slice.call(arguments, 1); + for (var i = 0; i < sources.length; i++) { + var obj = sources[i]; + for (var k in obj) { + base[k] = obj[k]; + } + } + return base; + }, + defaults: function(base) { + var sources = slice.call(arguments, 1); + for (var i = 0; i < sources.length; i++) { + var obj = sources[i]; + for (var k in obj) { + if (base[k] === void 0) { + base[k] = obj[k]; + } + } + } + return base; + }, + keys: function(obj) { + if (!_.isObject(obj)) { + return []; + } + if (_.natural.keys) { + return _.natural.keys(obj); + } + var keys = []; + for (var k in obj) { + if (_.has(obj, k)) { + keys.push(k); + } + } + return keys; + }, + values: function(obj) { + var keys = _.keys(obj); + var values = []; + for (var i = 0; i < keys.length; i++) { + var k = keys[i]; + values.push(obj[k]); + } + return values; + }, + each: function(obj, iteratee, context) { + var ctx = context || this; + var keys = !isArrayLike(obj) && _.keys(obj); + var length = (keys || obj).length; + for (var i = 0; i < length; i++) { + var k = keys ? keys[i] : i; + iteratee.call(ctx, obj[k], k, obj); + } + return obj; + }, + map: function(obj, iteratee, context) { + var ctx = context || this; + var keys = !isArrayLike(obj) && _.keys(obj); + var length = (keys || obj).length; + var result = []; + for (var i = 0; i < length; i++) { + var k = keys ? keys[i] : i; + result[i] = iteratee.call(ctx, obj[k], k, obj); + } + return result; + }, + once: function(func) { + var init = false; + return function() { + if (!!init) { + return func; + } + init = true; + return func.apply(this, arguments); + } + }, + after: function(times, func) { + return function() { + while (--times < 1) { + return func.apply(this, arguments); + } + } + }, + uniqueId: function(prefix) { + var id = ++_._indexAmount + ''; + return prefix ? prefix + id : id; + } + }; + + /** + * Constants + */ + + var sin = Math.sin, + cos = Math.cos, + atan2 = Math.atan2, + sqrt = Math.sqrt, + round = Math.round, + abs = Math.abs, + PI = Math.PI, + TWO_PI = PI * 2, + HALF_PI = PI / 2, + pow = Math.pow, + min = Math.min, + max = Math.max; + + /** + * Localized variables + */ + + var count = 0; + var slice = _.natural.slice; + var perf = ((root.performance && root.performance.now) ? root.performance : Date); + var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1; + var getLength = function(obj) { + return obj == null ? void 0 : obj['length']; + }; + var isArrayLike = function(collection) { + var length = getLength(collection); + return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX; + }; + + /** + * Cross browser dom events. + */ + var dom = { + + temp: (root.document ? root.document.createElement('div') : {}), + + hasEventListeners: _.isFunction(root.addEventListener), + + bind: function(elem, event, func, bool) { + if (this.hasEventListeners) { + elem.addEventListener(event, func, !!bool); + } else { + elem.attachEvent('on' + event, func); + } + return dom; + }, + + unbind: function(elem, event, func, bool) { + if (dom.hasEventListeners) { + elem.removeEventListeners(event, func, !!bool); + } else { + elem.detachEvent('on' + event, func); + } + return dom; + }, + + getRequestAnimationFrame: function() { + + var lastTime = 0; + var vendors = ['ms', 'moz', 'webkit', 'o']; + var request = root.requestAnimationFrame, cancel; + + if(!request) { + for (var i = 0; i < vendors.length; i++) { + request = root[vendors[i] + 'RequestAnimationFrame'] || request; + cancel = root[vendors[i] + 'CancelAnimationFrame'] + || root[vendors[i] + 'CancelRequestAnimationFrame'] || cancel; + } + + request = request || function(callback, element) { + var currTime = new Date().getTime(); + var timeToCall = Math.max(0, 16 - (currTime - lastTime)); + var id = root.setTimeout(function() { callback(currTime + timeToCall); }, timeToCall); + lastTime = currTime + timeToCall; + return id; + }; + // cancel = cancel || function(id) { + // clearTimeout(id); + // }; + } + + request.init = _.once(loop); + + return request; + + } + + }; + + /** + * @class + */ + var Two = root.Two = function(options) { + + // Determine what Renderer to use and setup a scene. + + var params = _.defaults(options || {}, { + fullscreen: false, + width: 640, + height: 480, + type: Two.Types.svg, + autostart: false + }); + + _.each(params, function(v, k) { + if (k === 'fullscreen' || k === 'autostart') { + return; + } + this[k] = v; + }, this); + + // Specified domElement overrides type declaration only if the element does not support declared renderer type. + if (_.isElement(params.domElement)) { + var tagName = params.domElement.tagName.toLowerCase(); + // TODO: Reconsider this if statement's logic. + if (!/^(CanvasRenderer-canvas|WebGLRenderer-canvas|SVGRenderer-svg)$/.test(this.type+'-'+tagName)) { + this.type = Two.Types[tagName]; + } + } + + this.renderer = new Two[this.type](this); + Two.Utils.setPlaying.call(this, params.autostart); + this.frameCount = 0; + + if (params.fullscreen) { + + var fitted = _.bind(fitToWindow, this); + _.extend(document.body.style, { + overflow: 'hidden', + margin: 0, + padding: 0, + top: 0, + left: 0, + right: 0, + bottom: 0, + position: 'fixed' + }); + _.extend(this.renderer.domElement.style, { + display: 'block', + top: 0, + left: 0, + right: 0, + bottom: 0, + position: 'fixed' + }); + dom.bind(root, 'resize', fitted); + fitted(); + + + } else if (!_.isElement(params.domElement)) { + + this.renderer.setSize(params.width, params.height, this.ratio); + this.width = params.width; + this.height = params.height; + + } + + this.scene = this.renderer.scene; + + Two.Instances.push(this); + raf.init(); + + }; + + _.extend(Two, { + + /** + * Access to root in other files. + */ + + root: root, + + /** + * Primitive + */ + + Array: root.Float32Array || Array, + + Types: { + webgl: 'WebGLRenderer', + svg: 'SVGRenderer', + canvas: 'CanvasRenderer' + }, + + Version: 'v0.7.0', + + Identifier: 'two_', + + Properties: { + hierarchy: 'hierarchy', + demotion: 'demotion' + }, + + Events: { + play: 'play', + pause: 'pause', + update: 'update', + render: 'render', + resize: 'resize', + change: 'change', + remove: 'remove', + insert: 'insert', + order: 'order', + load: 'load' + }, + + Commands: { + move: 'M', + line: 'L', + curve: 'C', + close: 'Z' + }, + + Resolution: 8, + + Instances: [], + + noConflict: function() { + root.Two = previousTwo; + return this; + }, + + uniqueId: function() { + var id = count; + count++; + return id; + }, + + Utils: _.extend(_, { + + performance: perf, + + defineProperty: function(property) { + + var object = this; + var secret = '_' + property; + var flag = '_flag' + property.charAt(0).toUpperCase() + property.slice(1); + + Object.defineProperty(object, property, { + enumerable: true, + get: function() { + return this[secret]; + }, + set: function(v) { + this[secret] = v; + this[flag] = true; + } + }); + + }, + + /** + * Release an arbitrary class' events from the two.js corpus and recurse + * through its children and or vertices. + */ + release: function(obj) { + + if (!_.isObject(obj)) { + return; + } + + if (_.isFunction(obj.unbind)) { + obj.unbind(); + } + + if (obj.vertices) { + if (_.isFunction(obj.vertices.unbind)) { + obj.vertices.unbind(); + } + _.each(obj.vertices, function(v) { + if (_.isFunction(v.unbind)) { + v.unbind(); + } + }); + } + + if (obj.children) { + _.each(obj.children, function(obj) { + Two.Utils.release(obj); + }); + } + + }, + + xhr: function(path, callback) { + + var xhr = new XMLHttpRequest(); + xhr.open('GET', path); + + xhr.onreadystatechange = function() { + if (xhr.readyState === 4 && xhr.status === 200) { + callback(xhr.responseText); + } + }; + + xhr.send(); + return xhr; + + }, + + Curve: { + + CollinearityEpsilon: pow(10, -30), + + RecursionLimit: 16, + + CuspLimit: 0, + + Tolerance: { + distance: 0.25, + angle: 0, + epsilon: 0.01 + }, + + // Lookup tables for abscissas and weights with values for n = 2 .. 16. + // As values are symmetric, only store half of them and adapt algorithm + // to factor in symmetry. + abscissas: [ + [ 0.5773502691896257645091488], + [0,0.7745966692414833770358531], + [ 0.3399810435848562648026658,0.8611363115940525752239465], + [0,0.5384693101056830910363144,0.9061798459386639927976269], + [ 0.2386191860831969086305017,0.6612093864662645136613996,0.9324695142031520278123016], + [0,0.4058451513773971669066064,0.7415311855993944398638648,0.9491079123427585245261897], + [ 0.1834346424956498049394761,0.5255324099163289858177390,0.7966664774136267395915539,0.9602898564975362316835609], + [0,0.3242534234038089290385380,0.6133714327005903973087020,0.8360311073266357942994298,0.9681602395076260898355762], + [ 0.1488743389816312108848260,0.4333953941292471907992659,0.6794095682990244062343274,0.8650633666889845107320967,0.9739065285171717200779640], + [0,0.2695431559523449723315320,0.5190961292068118159257257,0.7301520055740493240934163,0.8870625997680952990751578,0.9782286581460569928039380], + [ 0.1252334085114689154724414,0.3678314989981801937526915,0.5873179542866174472967024,0.7699026741943046870368938,0.9041172563704748566784659,0.9815606342467192506905491], + [0,0.2304583159551347940655281,0.4484927510364468528779129,0.6423493394403402206439846,0.8015780907333099127942065,0.9175983992229779652065478,0.9841830547185881494728294], + [ 0.1080549487073436620662447,0.3191123689278897604356718,0.5152486363581540919652907,0.6872929048116854701480198,0.8272013150697649931897947,0.9284348836635735173363911,0.9862838086968123388415973], + [0,0.2011940939974345223006283,0.3941513470775633698972074,0.5709721726085388475372267,0.7244177313601700474161861,0.8482065834104272162006483,0.9372733924007059043077589,0.9879925180204854284895657], + [ 0.0950125098376374401853193,0.2816035507792589132304605,0.4580167776572273863424194,0.6178762444026437484466718,0.7554044083550030338951012,0.8656312023878317438804679,0.9445750230732325760779884,0.9894009349916499325961542] + ], + + weights: [ + [1], + [0.8888888888888888888888889,0.5555555555555555555555556], + [0.6521451548625461426269361,0.3478548451374538573730639], + [0.5688888888888888888888889,0.4786286704993664680412915,0.2369268850561890875142640], + [0.4679139345726910473898703,0.3607615730481386075698335,0.1713244923791703450402961], + [0.4179591836734693877551020,0.3818300505051189449503698,0.2797053914892766679014678,0.1294849661688696932706114], + [0.3626837833783619829651504,0.3137066458778872873379622,0.2223810344533744705443560,0.1012285362903762591525314], + [0.3302393550012597631645251,0.3123470770400028400686304,0.2606106964029354623187429,0.1806481606948574040584720,0.0812743883615744119718922], + [0.2955242247147528701738930,0.2692667193099963550912269,0.2190863625159820439955349,0.1494513491505805931457763,0.0666713443086881375935688], + [0.2729250867779006307144835,0.2628045445102466621806889,0.2331937645919904799185237,0.1862902109277342514260976,0.1255803694649046246346943,0.0556685671161736664827537], + [0.2491470458134027850005624,0.2334925365383548087608499,0.2031674267230659217490645,0.1600783285433462263346525,0.1069393259953184309602547,0.0471753363865118271946160], + [0.2325515532308739101945895,0.2262831802628972384120902,0.2078160475368885023125232,0.1781459807619457382800467,0.1388735102197872384636018,0.0921214998377284479144218,0.0404840047653158795200216], + [0.2152638534631577901958764,0.2051984637212956039659241,0.1855383974779378137417166,0.1572031671581935345696019,0.1215185706879031846894148,0.0801580871597602098056333,0.0351194603317518630318329], + [0.2025782419255612728806202,0.1984314853271115764561183,0.1861610000155622110268006,0.1662692058169939335532009,0.1395706779261543144478048,0.1071592204671719350118695,0.0703660474881081247092674,0.0307532419961172683546284], + [0.1894506104550684962853967,0.1826034150449235888667637,0.1691565193950025381893121,0.1495959888165767320815017,0.1246289712555338720524763,0.0951585116824927848099251,0.0622535239386478928628438,0.0271524594117540948517806] + ] + + }, + + /** + * Account for high dpi rendering. + * http://www.html5rocks.com/en/tutorials/canvas/hidpi/ + */ + + devicePixelRatio: root.devicePixelRatio || 1, + + getBackingStoreRatio: function(ctx) { + return ctx.webkitBackingStorePixelRatio || + ctx.mozBackingStorePixelRatio || + ctx.msBackingStorePixelRatio || + ctx.oBackingStorePixelRatio || + ctx.backingStorePixelRatio || 1; + }, + + getRatio: function(ctx) { + return Two.Utils.devicePixelRatio / getBackingStoreRatio(ctx); + }, + + /** + * Properly defer play calling until after all objects + * have been updated with their newest styles. + */ + setPlaying: function(b) { + + this.playing = !!b; + return this; + + }, + + /** + * Return the computed matrix of a nested object. + * TODO: Optimize traversal. + */ + getComputedMatrix: function(object, matrix) { + + matrix = (matrix && matrix.identity()) || new Two.Matrix(); + var parent = object, matrices = []; + + while (parent && parent._matrix) { + matrices.push(parent._matrix); + parent = parent.parent; + } + + matrices.reverse(); + + _.each(matrices, function(m) { + + var e = m.elements; + matrix.multiply( + e[0], e[1], e[2], e[3], e[4], e[5], e[6], e[7], e[8], e[9]); + + }); + + return matrix; + + }, + + deltaTransformPoint: function(matrix, x, y) { + + var dx = x * matrix.a + y * matrix.c + 0; + var dy = x * matrix.b + y * matrix.d + 0; + + return new Two.Vector(dx, dy); + + }, + + /** + * https://gist.github.com/2052247 + */ + decomposeMatrix: function(matrix) { + + // calculate delta transform point + var px = Two.Utils.deltaTransformPoint(matrix, 0, 1); + var py = Two.Utils.deltaTransformPoint(matrix, 1, 0); + + // calculate skew + var skewX = ((180 / Math.PI) * Math.atan2(px.y, px.x) - 90); + var skewY = ((180 / Math.PI) * Math.atan2(py.y, py.x)); + + return { + translateX: matrix.e, + translateY: matrix.f, + scaleX: Math.sqrt(matrix.a * matrix.a + matrix.b * matrix.b), + scaleY: Math.sqrt(matrix.c * matrix.c + matrix.d * matrix.d), + skewX: skewX, + skewY: skewY, + rotation: skewX // rotation is the same as skew x + }; + + }, + + /** + * Walk through item properties and pick the ones of interest. + * Will try to resolve styles applied via CSS + * + * TODO: Reverse calculate `Two.Gradient`s for fill / stroke + * of any given path. + */ + applySvgAttributes: function(node, elem) { + + var attributes = {}, styles = {}, i, key, value, attr; + + // Not available in non browser environments + if (getComputedStyle) { + // Convert CSSStyleDeclaration to a normal object + var computedStyles = getComputedStyle(node); + i = computedStyles.length; + + while (i--) { + key = computedStyles[i]; + value = computedStyles[key]; + // Gecko returns undefined for unset properties + // Webkit returns the default value + if (value !== undefined) { + styles[key] = value; + } + } + } + + // Convert NodeMap to a normal object + i = node.attributes.length; + while (i--) { + attr = node.attributes[i]; + attributes[attr.nodeName] = attr.value; + } + + // Getting the correct opacity is a bit tricky, since SVG path elements don't + // support opacity as an attribute, but you can apply it via CSS. + // So we take the opacity and set (stroke/fill)-opacity to the same value. + if (!_.isUndefined(styles.opacity)) { + styles['stroke-opacity'] = styles.opacity; + styles['fill-opacity'] = styles.opacity; + } + + // Merge attributes and applied styles (attributes take precedence) + _.extend(styles, attributes); + + // Similarly visibility is influenced by the value of both display and visibility. + // Calculate a unified value here which defaults to `true`. + styles.visible = !(_.isUndefined(styles.display) && styles.display === 'none') + || (_.isUndefined(styles.visibility) && styles.visibility === 'hidden'); + + // Now iterate the whole thing + for (key in styles) { + value = styles[key]; + + switch (key) { + case 'transform': + // TODO: Check this out https://github.com/paperjs/paper.js/blob/master/src/svg/SVGImport.js#L313 + if (value === 'none') break; + var m = node.getCTM ? node.getCTM() : null; + + // Might happen when transform string is empty or not valid. + if (m === null) break; + + // // Option 1: edit the underlying matrix and don't force an auto calc. + // var m = node.getCTM(); + // elem._matrix.manual = true; + // elem._matrix.set(m.a, m.b, m.c, m.d, m.e, m.f); + + // Option 2: Decompose and infer Two.js related properties. + var transforms = Two.Utils.decomposeMatrix(node.getCTM()); + + elem.translation.set(transforms.translateX, transforms.translateY); + elem.rotation = transforms.rotation; + // Warning: Two.js elements only support uniform scalars... + elem.scale = transforms.scaleX; + + var x = parseFloat((styles.x + '').replace('px')); + var y = parseFloat((styles.y + '').replace('px')); + + // Override based on attributes. + if (x) { + elem.translation.x = x; + } + + if (y) { + elem.translation.y = y; + } + + break; + case 'visible': + elem.visible = value; + break; + case 'stroke-linecap': + elem.cap = value; + break; + case 'stroke-linejoin': + elem.join = value; + break; + case 'stroke-miterlimit': + elem.miter = value; + break; + case 'stroke-width': + elem.linewidth = parseFloat(value); + break; + case 'stroke-opacity': + case 'fill-opacity': + case 'opacity': + elem.opacity = parseFloat(value); + break; + case 'fill': + case 'stroke': + if (/url\(\#.*\)/i.test(value)) { + elem[key] = this.getById( + value.replace(/url\(\#(.*)\)/i, '$1')); + } else { + elem[key] = (value === 'none') ? 'transparent' : value; + } + break; + case 'id': + elem.id = value; + break; + case 'class': + elem.classList = value.split(' '); + break; + } + } + + return elem; + + }, + + /** + * Read any number of SVG node types and create Two equivalents of them. + */ + read: { + + svg: function() { + return Two.Utils.read.g.apply(this, arguments); + }, + + g: function(node) { + + var group = new Two.Group(); + + // Switched up order to inherit more specific styles + Two.Utils.applySvgAttributes.call(this, node, group); + + for (var i = 0, l = node.childNodes.length; i < l; i++) { + var n = node.childNodes[i]; + var tag = n.nodeName; + if (!tag) return; + + var tagName = tag.replace(/svg\:/ig, '').toLowerCase(); + + if (tagName in Two.Utils.read) { + var o = Two.Utils.read[tagName].call(group, n); + group.add(o); + } + } + + return group; + + }, + + polygon: function(node, open) { + + var points = node.getAttribute('points'); + + var verts = []; + points.replace(/(-?[\d\.?]+)[,|\s](-?[\d\.?]+)/g, function(match, p1, p2) { + verts.push(new Two.Anchor(parseFloat(p1), parseFloat(p2))); + }); + + var poly = new Two.Path(verts, !open).noStroke(); + poly.fill = 'black'; + + return Two.Utils.applySvgAttributes.call(this, node, poly); + + }, + + polyline: function(node) { + return Two.Utils.read.polygon.call(this, node, true); + }, + + path: function(node) { + + var path = node.getAttribute('d'); + + // Create a Two.Path from the paths. + + var coord = new Two.Anchor(); + var control, coords; + var closed = false, relative = false; + var commands = path.match(/[a-df-z][^a-df-z]*/ig); + var last = commands.length - 1; + + // Split up polybeziers + + _.each(commands.slice(0), function(command, i) { + + var type = command[0]; + var lower = type.toLowerCase(); + var items = command.slice(1).trim().split(/[\s,]+|(?=\s?[+\-])/); + var pre, post, result = [], bin; + + if (i <= 0) { + commands = []; + } + + switch (lower) { + case 'h': + case 'v': + if (items.length > 1) { + bin = 1; + } + break; + case 'm': + case 'l': + case 't': + if (items.length > 2) { + bin = 2; + } + break; + case 's': + case 'q': + if (items.length > 4) { + bin = 4; + } + break; + case 'c': + if (items.length > 6) { + bin = 6; + } + break; + case 'a': + // TODO: Handle Ellipses + break; + } + + if (bin) { + + for (var j = 0, l = items.length, times = 0; j < l; j+=bin) { + + var ct = type; + if (times > 0) { + + switch (type) { + case 'm': + ct = 'l'; + break; + case 'M': + ct = 'L'; + break; + } + + } + + result.push([ct].concat(items.slice(j, j + bin)).join(' ')); + times++; + + } + + commands = Array.prototype.concat.apply(commands, result); + + } else { + + commands.push(command); + + } + + }); + + // Create the vertices for our Two.Path + + var points = []; + _.each(commands, function(command, i) { + + var result, x, y; + var type = command[0]; + var lower = type.toLowerCase(); + + coords = command.slice(1).trim(); + coords = coords.replace(/(-?\d+(?:\.\d*)?)[eE]([+\-]?\d+)/g, function(match, n1, n2) { + return parseFloat(n1) * pow(10, n2); + }); + coords = coords.split(/[\s,]+|(?=\s?[+\-])/); + relative = type === lower; + + var x1, y1, x2, y2, x3, y3, x4, y4, reflection; + + switch (lower) { + + case 'z': + if (i >= last) { + closed = true; + } else { + x = coord.x; + y = coord.y; + result = new Two.Anchor( + x, y, + undefined, undefined, + undefined, undefined, + Two.Commands.close + ); + } + break; + + case 'm': + case 'l': + + x = parseFloat(coords[0]); + y = parseFloat(coords[1]); + + result = new Two.Anchor( + x, y, + undefined, undefined, + undefined, undefined, + lower === 'm' ? Two.Commands.move : Two.Commands.line + ); + + if (relative) { + result.addSelf(coord); + } + + // result.controls.left.copy(result); + // result.controls.right.copy(result); + + coord = result; + break; + + case 'h': + case 'v': + + var a = lower === 'h' ? 'x' : 'y'; + var b = a === 'x' ? 'y' : 'x'; + + result = new Two.Anchor( + undefined, undefined, + undefined, undefined, + undefined, undefined, + Two.Commands.line + ); + result[a] = parseFloat(coords[0]); + result[b] = coord[b]; + + if (relative) { + result[a] += coord[a]; + } + + // result.controls.left.copy(result); + // result.controls.right.copy(result); + + coord = result; + break; + + case 'c': + case 's': + + x1 = coord.x; + y1 = coord.y; + + if (!control) { + control = new Two.Vector();//.copy(coord); + } + + if (lower === 'c') { + + x2 = parseFloat(coords[0]); + y2 = parseFloat(coords[1]); + x3 = parseFloat(coords[2]); + y3 = parseFloat(coords[3]); + x4 = parseFloat(coords[4]); + y4 = parseFloat(coords[5]); + + } else { + + // Calculate reflection control point for proper x2, y2 + // inclusion. + + reflection = getReflection(coord, control, relative); + + x2 = reflection.x; + y2 = reflection.y; + x3 = parseFloat(coords[0]); + y3 = parseFloat(coords[1]); + x4 = parseFloat(coords[2]); + y4 = parseFloat(coords[3]); + + } + + if (relative) { + x2 += x1; + y2 += y1; + x3 += x1; + y3 += y1; + x4 += x1; + y4 += y1; + } + + if (!_.isObject(coord.controls)) { + Two.Anchor.AppendCurveProperties(coord); + } + + coord.controls.right.set(x2 - coord.x, y2 - coord.y); + result = new Two.Anchor( + x4, y4, + x3 - x4, y3 - y4, + undefined, undefined, + Two.Commands.curve + ); + + coord = result; + control = result.controls.left; + + break; + + case 't': + case 'q': + + x1 = coord.x; + y1 = coord.y; + + if (!control) { + control = new Two.Vector();//.copy(coord); + } + + if (control.isZero()) { + x2 = x1; + y2 = y1; + } else { + x2 = control.x; + y1 = control.y; + } + + if (lower === 'q') { + + x3 = parseFloat(coords[0]); + y3 = parseFloat(coords[1]); + x4 = parseFloat(coords[1]); + y4 = parseFloat(coords[2]); + + } else { + + reflection = getReflection(coord, control, relative); + + x3 = reflection.x; + y3 = reflection.y; + x4 = parseFloat(coords[0]); + y4 = parseFloat(coords[1]); + + } + + if (relative) { + x2 += x1; + y2 += y1; + x3 += x1; + y3 += y1; + x4 += x1; + y4 += y1; + } + + if (!_.isObject(coord.controls)) { + Two.Anchor.AppendCurveProperties(coord); + } + + coord.controls.right.set(x2 - coord.x, y2 - coord.y); + result = new Two.Anchor( + x4, y4, + x3 - x4, y3 - y4, + undefined, undefined, + Two.Commands.curve + ); + + coord = result; + control = result.controls.left; + + break; + + case 'a': + + // throw new Two.Utils.Error('not yet able to interpret Elliptical Arcs.'); + x1 = coord.x; + y1 = coord.y; + + var rx = parseFloat(coords[0]); + var ry = parseFloat(coords[1]); + var xAxisRotation = parseFloat(coords[2]) * Math.PI / 180; + var largeArcFlag = parseFloat(coords[3]); + var sweepFlag = parseFloat(coords[4]); + + x4 = parseFloat(coords[5]); + y4 = parseFloat(coords[6]); + + if (relative) { + x4 += x1; + y4 += y1; + } + + // http://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter + + // Calculate midpoint mx my + var mx = (x4 - x1) / 2; + var my = (y4 - y1) / 2; + + // Calculate x1' y1' F.6.5.1 + var _x = mx * Math.cos(xAxisRotation) + my * Math.sin(xAxisRotation); + var _y = - mx * Math.sin(xAxisRotation) + my * Math.cos(xAxisRotation); + + var rx2 = rx * rx; + var ry2 = ry * ry; + var _x2 = _x * _x; + var _y2 = _y * _y; + + // adjust radii + var l = _x2 / rx2 + _y2 / ry2; + if (l > 1) { + rx *= Math.sqrt(l); + ry *= Math.sqrt(l); + } + + var amp = Math.sqrt((rx2 * ry2 - rx2 * _y2 - ry2 * _x2) / (rx2 * _y2 + ry2 * _x2)); + + if (_.isNaN(amp)) { + amp = 0; + } else if (largeArcFlag != sweepFlag && amp > 0) { + amp *= -1; + } + + // Calculate cx' cy' F.6.5.2 + var _cx = amp * rx * _y / ry; + var _cy = - amp * ry * _x / rx; + + // Calculate cx cy F.6.5.3 + var cx = _cx * Math.cos(xAxisRotation) - _cy * Math.sin(xAxisRotation) + (x1 + x4) / 2; + var cy = _cx * Math.sin(xAxisRotation) + _cy * Math.cos(xAxisRotation) + (y1 + y4) / 2; + + // vector magnitude + var m = function(v) { return Math.sqrt(Math.pow(v[0], 2) + Math.pow(v[1], 2)); } + // ratio between two vectors + var r = function(u, v) { return (u[0] * v[0] + u[1] * v[1]) / (m(u) * m(v)) } + // angle between two vectors + var a = function(u, v) { return (u[0] * v[1] < u[1] * v[0] ? - 1 : 1) * Math.acos(r(u,v)); } + + // Calculate theta1 and delta theta F.6.5.4 + F.6.5.5 + var t1 = a([1, 0], [(_x - _cx) / rx, (_y - _cy) / ry]); + var u = [(_x - _cx) / rx, (_y - _cy) / ry]; + var v = [( - _x - _cx) / rx, ( - _y - _cy) / ry]; + var dt = a(u, v); + + if (r(u, v) <= -1) dt = Math.PI; + if (r(u, v) >= 1) dt = 0; + + // F.6.5.6 + if (largeArcFlag) { + dt = mod(dt, Math.PI * 2); + } + + if (sweepFlag && dt > 0) { + dt -= Math.PI * 2; + } + + var length = Two.Resolution; + + // Save a projection of our rotation and translation to apply + // to the set of points. + var projection = new Two.Matrix() + .translate(cx, cy) + .rotate(xAxisRotation); + + // Create a resulting array of Two.Anchor's to export to the + // the path. + result = _.map(_.range(length), function(i) { + var pct = 1 - (i / (length - 1)); + var theta = pct * dt + t1; + var x = rx * Math.cos(theta); + var y = ry * Math.sin(theta); + var projected = projection.multiply(x, y, 1); + return new Two.Anchor(projected.x, projected.y, false, false, false, false, Two.Commands.line);; + }); + + result.push(new Two.Anchor(x4, y4, false, false, false, false, Two.Commands.line)); + + coord = result[result.length - 1]; + control = coord.controls.left; + + break; + + } + + if (result) { + if (_.isArray(result)) { + points = points.concat(result); + } else { + points.push(result); + } + } + + }); + + if (points.length <= 1) { + return; + } + + var path = new Two.Path(points, closed, undefined, true).noStroke(); + path.fill = 'black'; + + var rect = path.getBoundingClientRect(true); + + // Center objects to stay consistent + // with the rest of the Two.js API. + rect.centroid = { + x: rect.left + rect.width / 2, + y: rect.top + rect.height / 2 + }; + + _.each(path.vertices, function(v) { + v.subSelf(rect.centroid); + }); + + path.translation.addSelf(rect.centroid); + + return Two.Utils.applySvgAttributes.call(this, node, path); + + }, + + circle: function(node) { + + var x = parseFloat(node.getAttribute('cx')); + var y = parseFloat(node.getAttribute('cy')); + var r = parseFloat(node.getAttribute('r')); + + var circle = new Two.Circle(x, y, r).noStroke(); + circle.fill = 'black'; + + return Two.Utils.applySvgAttributes.call(this, node, circle); + + }, + + ellipse: function(node) { + + var x = parseFloat(node.getAttribute('cx')); + var y = parseFloat(node.getAttribute('cy')); + var width = parseFloat(node.getAttribute('rx')); + var height = parseFloat(node.getAttribute('ry')); + + var ellipse = new Two.Ellipse(x, y, width, height).noStroke(); + ellipse.fill = 'black'; + + return Two.Utils.applySvgAttributes.call(this, node, ellipse); + + }, + + rect: function(node) { + + var x = parseFloat(node.getAttribute('x')) || 0; + var y = parseFloat(node.getAttribute('y')) || 0; + var width = parseFloat(node.getAttribute('width')); + var height = parseFloat(node.getAttribute('height')); + + var w2 = width / 2; + var h2 = height / 2; + + var rect = new Two.Rectangle(x + w2, y + h2, width, height) + .noStroke(); + rect.fill = 'black'; + + return Two.Utils.applySvgAttributes.call(this, node, rect); + + }, + + line: function(node) { + + var x1 = parseFloat(node.getAttribute('x1')); + var y1 = parseFloat(node.getAttribute('y1')); + var x2 = parseFloat(node.getAttribute('x2')); + var y2 = parseFloat(node.getAttribute('y2')); + + var line = new Two.Line(x1, y1, x2, y2).noFill(); + + return Two.Utils.applySvgAttributes.call(this, node, line); + + }, + + lineargradient: function(node) { + + var x1 = parseFloat(node.getAttribute('x1')); + var y1 = parseFloat(node.getAttribute('y1')); + var x2 = parseFloat(node.getAttribute('x2')); + var y2 = parseFloat(node.getAttribute('y2')); + + var ox = (x2 + x1) / 2; + var oy = (y2 + y1) / 2; + + var stops = []; + for (var i = 0; i < node.children.length; i++) { + + var child = node.children[i]; + + var offset = parseFloat(child.getAttribute('offset')); + var color = child.getAttribute('stop-color'); + var opacity = child.getAttribute('stop-opacity'); + var style = child.getAttribute('style'); + + if (_.isNull(color)) { + var matches = style ? style.match(/stop\-color\:\s?([\#a-fA-F0-9]*)/) : false; + color = matches && matches.length > 1 ? matches[1] : undefined; + } + + if (_.isNull(opacity)) { + var matches = style ? style.match(/stop\-opacity\:\s?([0-9\.\-]*)/) : false; + opacity = matches && matches.length > 1 ? parseFloat(matches[1]) : 1; + } + + stops.push(new Two.Gradient.Stop(offset, color, opacity)); + + } + + var gradient = new Two.LinearGradient(x1 - ox, y1 - oy, x2 - ox, + y2 - oy, stops); + + return Two.Utils.applySvgAttributes.call(this, node, gradient); + + }, + + radialgradient: function(node) { + + var cx = parseFloat(node.getAttribute('cx')) || 0; + var cy = parseFloat(node.getAttribute('cy')) || 0; + var r = parseFloat(node.getAttribute('r')); + + var fx = parseFloat(node.getAttribute('fx')); + var fy = parseFloat(node.getAttribute('fy')); + + if (_.isNaN(fx)) { + fx = cx; + } + + if (_.isNaN(fy)) { + fy = cy; + } + + var ox = Math.abs(cx + fx) / 2; + var oy = Math.abs(cy + fy) / 2; + + var stops = []; + for (var i = 0; i < node.children.length; i++) { + + var child = node.children[i]; + + var offset = parseFloat(child.getAttribute('offset')); + var color = child.getAttribute('stop-color'); + var opacity = child.getAttribute('stop-opacity'); + var style = child.getAttribute('style'); + + if (_.isNull(color)) { + var matches = style ? style.match(/stop\-color\:\s?([\#a-fA-F0-9]*)/) : false; + color = matches && matches.length > 1 ? matches[1] : undefined; + } + + if (_.isNull(opacity)) { + var matches = style ? style.match(/stop\-opacity\:\s?([0-9\.\-]*)/) : false; + opacity = matches && matches.length > 1 ? parseFloat(matches[1]) : 1; + } + + stops.push(new Two.Gradient.Stop(offset, color, opacity)); + + } + + var gradient = new Two.RadialGradient(cx - ox, cy - oy, r, + stops, fx - ox, fy - oy); + + return Two.Utils.applySvgAttributes.call(this, node, gradient); + + } + + }, + + /** + * Given 2 points (a, b) and corresponding control point for each + * return an array of points that represent points plotted along + * the curve. Number points determined by limit. + */ + subdivide: function(x1, y1, x2, y2, x3, y3, x4, y4, limit) { + + limit = limit || Two.Utils.Curve.RecursionLimit; + var amount = limit + 1; + + // TODO: Issue 73 + // Don't recurse if the end points are identical + if (x1 === x4 && y1 === y4) { + return [new Two.Anchor(x4, y4)]; + } + + return _.map(_.range(0, amount), function(i) { + + var t = i / amount; + var x = getPointOnCubicBezier(t, x1, x2, x3, x4); + var y = getPointOnCubicBezier(t, y1, y2, y3, y4); + + return new Two.Anchor(x, y); + + }); + + }, + + getPointOnCubicBezier: function(t, a, b, c, d) { + var k = 1 - t; + return (k * k * k * a) + (3 * k * k * t * b) + (3 * k * t * t * c) + + (t * t * t * d); + }, + + /** + * Given 2 points (a, b) and corresponding control point for each + * return a float that represents the length of the curve using + * Gauss-Legendre algorithm. Limit iterations of calculation by `limit`. + */ + getCurveLength: function(x1, y1, x2, y2, x3, y3, x4, y4, limit) { + + // TODO: Better / fuzzier equality check + // Linear calculation + if (x1 === x2 && y1 === y2 && x3 === x4 && y3 === y4) { + var dx = x4 - x1; + var dy = y4 - y1; + return sqrt(dx * dx + dy * dy); + } + + // Calculate the coefficients of a Bezier derivative. + var ax = 9 * (x2 - x3) + 3 * (x4 - x1), + bx = 6 * (x1 + x3) - 12 * x2, + cx = 3 * (x2 - x1), + + ay = 9 * (y2 - y3) + 3 * (y4 - y1), + by = 6 * (y1 + y3) - 12 * y2, + cy = 3 * (y2 - y1); + + var integrand = function(t) { + // Calculate quadratic equations of derivatives for x and y + var dx = (ax * t + bx) * t + cx, + dy = (ay * t + by) * t + cy; + return sqrt(dx * dx + dy * dy); + }; + + return integrate( + integrand, 0, 1, limit || Two.Utils.Curve.RecursionLimit + ); + + }, + + /** + * Integration for `getCurveLength` calculations. Referenced from + * Paper.js: https://github.com/paperjs/paper.js/blob/master/src/util/Numerical.js#L101 + */ + integrate: function(f, a, b, n) { + var x = Two.Utils.Curve.abscissas[n - 2], + w = Two.Utils.Curve.weights[n - 2], + A = 0.5 * (b - a), + B = A + a, + i = 0, + m = (n + 1) >> 1, + sum = n & 1 ? w[i++] * f(B) : 0; // Handle odd n + while (i < m) { + var Ax = A * x[i]; + sum += w[i++] * (f(B + Ax) + f(B - Ax)); + } + return A * sum; + }, + + /** + * Creates a set of points that have u, v values for anchor positions + */ + getCurveFromPoints: function(points, closed) { + + var l = points.length, last = l - 1; + + for (var i = 0; i < l; i++) { + + var point = points[i]; + + if (!_.isObject(point.controls)) { + Two.Anchor.AppendCurveProperties(point); + } + + var prev = closed ? mod(i - 1, l) : max(i - 1, 0); + var next = closed ? mod(i + 1, l) : min(i + 1, last); + + var a = points[prev]; + var b = point; + var c = points[next]; + getControlPoints(a, b, c); + + b._command = i === 0 ? Two.Commands.move : Two.Commands.curve; + + b.controls.left.x = _.isNumber(b.controls.left.x) ? b.controls.left.x : b.x; + b.controls.left.y = _.isNumber(b.controls.left.y) ? b.controls.left.y : b.y; + + b.controls.right.x = _.isNumber(b.controls.right.x) ? b.controls.right.x : b.x; + b.controls.right.y = _.isNumber(b.controls.right.y) ? b.controls.right.y : b.y; + + } + + }, + + /** + * Given three coordinates return the control points for the middle, b, + * vertex. + */ + getControlPoints: function(a, b, c) { + + var a1 = angleBetween(a, b); + var a2 = angleBetween(c, b); + + var d1 = distanceBetween(a, b); + var d2 = distanceBetween(c, b); + + var mid = (a1 + a2) / 2; + + // So we know which angle corresponds to which side. + + b.u = _.isObject(b.controls.left) ? b.controls.left : new Two.Vector(0, 0); + b.v = _.isObject(b.controls.right) ? b.controls.right : new Two.Vector(0, 0); + + // TODO: Issue 73 + if (d1 < 0.0001 || d2 < 0.0001) { + if (!b._relative) { + b.controls.left.copy(b); + b.controls.right.copy(b); + } + return b; + } + + d1 *= 0.33; // Why 0.33? + d2 *= 0.33; + + if (a2 < a1) { + mid += HALF_PI; + } else { + mid -= HALF_PI; + } + + b.controls.left.x = cos(mid) * d1; + b.controls.left.y = sin(mid) * d1; + + mid -= PI; + + b.controls.right.x = cos(mid) * d2; + b.controls.right.y = sin(mid) * d2; + + if (!b._relative) { + b.controls.left.x += b.x; + b.controls.left.y += b.y; + b.controls.right.x += b.x; + b.controls.right.y += b.y; + } + + return b; + + }, + + /** + * Get the reflection of a point "b" about point "a". Where "a" is in + * absolute space and "b" is relative to "a". + * + * http://www.w3.org/TR/SVG11/implnote.html#PathElementImplementationNotes + */ + getReflection: function(a, b, relative) { + + return new Two.Vector( + 2 * a.x - (b.x + a.x) - (relative ? a.x : 0), + 2 * a.y - (b.y + a.y) - (relative ? a.y : 0) + ); + + }, + + getAnchorsFromArcData: function(center, xAxisRotation, rx, ry, ts, td, ccw) { + + var matrix = new Two.Matrix() + .translate(center.x, center.y) + .rotate(xAxisRotation); + + var l = Two.Resolution; + + return _.map(_.range(l), function(i) { + + var pct = (i + 1) / l; + if (!!ccw) { + pct = 1 - pct; + } + + var theta = pct * td + ts; + var x = rx * Math.cos(theta); + var y = ry * Math.sin(theta); + + // x += center.x; + // y += center.y; + + var anchor = new Two.Anchor(x, y); + Two.Anchor.AppendCurveProperties(anchor); + anchor.command = Two.Commands.line; + + // TODO: Calculate control points here... + + return anchor; + + }); + + }, + + ratioBetween: function(A, B) { + + return (A.x * B.x + A.y * B.y) / (A.length() * B.length()); + + }, + + angleBetween: function(A, B) { + + var dx, dy; + + if (arguments.length >= 4) { + + dx = arguments[0] - arguments[2]; + dy = arguments[1] - arguments[3]; + + return atan2(dy, dx); + + } + + dx = A.x - B.x; + dy = A.y - B.y; + + return atan2(dy, dx); + + }, + + distanceBetweenSquared: function(p1, p2) { + + var dx = p1.x - p2.x; + var dy = p1.y - p2.y; + + return dx * dx + dy * dy; + + }, + + distanceBetween: function(p1, p2) { + + return sqrt(distanceBetweenSquared(p1, p2)); + + }, + + lerp: function(a, b, t) { + return t * (b - a) + a; + }, + + // A pretty fast toFixed(3) alternative + // See http://jsperf.com/parsefloat-tofixed-vs-math-round/18 + toFixed: function(v) { + return Math.floor(v * 1000) / 1000; + }, + + mod: function(v, l) { + + while (v < 0) { + v += l; + } + + return v % l; + + }, + + /** + * Array like collection that triggers inserted and removed events + * removed : pop / shift / splice + * inserted : push / unshift / splice (with > 2 arguments) + */ + Collection: function() { + + Array.call(this); + + if (arguments.length > 1) { + Array.prototype.push.apply(this, arguments); + } else if (arguments[0] && Array.isArray(arguments[0])) { + Array.prototype.push.apply(this, arguments[0]); + } + + }, + + // Custom Error Throwing for Two.js + + Error: function(message) { + this.name = 'two.js'; + this.message = message; + }, + + Events: { + + on: function(name, callback) { + + this._events || (this._events = {}); + var list = this._events[name] || (this._events[name] = []); + + list.push(callback); + + return this; + + }, + + off: function(name, callback) { + + if (!this._events) { + return this; + } + if (!name && !callback) { + this._events = {}; + return this; + } + + var names = name ? [name] : _.keys(this._events); + for (var i = 0, l = names.length; i < l; i++) { + + var name = names[i]; + var list = this._events[name]; + + if (!!list) { + var events = []; + if (callback) { + for (var j = 0, k = list.length; j < k; j++) { + var ev = list[j]; + ev = ev.callback ? ev.callback : ev; + if (callback && callback !== ev) { + events.push(ev); + } + } + } + this._events[name] = events; + } + } + + return this; + }, + + trigger: function(name) { + if (!this._events) return this; + var args = slice.call(arguments, 1); + var events = this._events[name]; + if (events) trigger(this, events, args); + return this; + }, + + listen: function (obj, name, callback) { + + var bound = this; + + if (obj) { + var ev = function () { + callback.apply(bound, arguments); + }; + + // add references about the object that assigned this listener + ev.obj = obj; + ev.name = name; + ev.callback = callback; + + obj.on(name, ev); + } + + return this; + + }, + + ignore: function (obj, name, callback) { + + obj.off(name, callback); + + return this; + + } + + } + + }) + + }); + + Two.Utils.Events.bind = Two.Utils.Events.on; + Two.Utils.Events.unbind = Two.Utils.Events.off; + + var trigger = function(obj, events, args) { + var method; + switch (args.length) { + case 0: + method = function(i) { + events[i].call(obj, args[0]); + }; + break; + case 1: + method = function(i) { + events[i].call(obj, args[0], args[1]); + }; + break; + case 2: + method = function(i) { + events[i].call(obj, args[0], args[1], args[2]); + }; + break; + case 3: + method = function(i) { + events[i].call(obj, args[0], args[1], args[2], args[3]); + }; + break; + default: + method = function(i) { + events[i].apply(obj, args); + }; + } + for (var i = 0; i < events.length; i++) { + method(i); + } + }; + + Two.Utils.Error.prototype = new Error(); + Two.Utils.Error.prototype.constructor = Two.Utils.Error; + + Two.Utils.Collection.prototype = new Array(); + Two.Utils.Collection.prototype.constructor = Two.Utils.Collection; + + _.extend(Two.Utils.Collection.prototype, Two.Utils.Events, { + + pop: function() { + var popped = Array.prototype.pop.apply(this, arguments); + this.trigger(Two.Events.remove, [popped]); + return popped; + }, + + shift: function() { + var shifted = Array.prototype.shift.apply(this, arguments); + this.trigger(Two.Events.remove, [shifted]); + return shifted; + }, + + push: function() { + var pushed = Array.prototype.push.apply(this, arguments); + this.trigger(Two.Events.insert, arguments); + return pushed; + }, + + unshift: function() { + var unshifted = Array.prototype.unshift.apply(this, arguments); + this.trigger(Two.Events.insert, arguments); + return unshifted; + }, + + splice: function() { + var spliced = Array.prototype.splice.apply(this, arguments); + var inserted; + + this.trigger(Two.Events.remove, spliced); + + if (arguments.length > 2) { + inserted = this.slice(arguments[0], arguments[0] + arguments.length - 2); + this.trigger(Two.Events.insert, inserted); + this.trigger(Two.Events.order); + } + return spliced; + }, + + sort: function() { + Array.prototype.sort.apply(this, arguments); + this.trigger(Two.Events.order); + return this; + }, + + reverse: function() { + Array.prototype.reverse.apply(this, arguments); + this.trigger(Two.Events.order); + return this; + } + + }); + + // Localize utils + + var distanceBetween = Two.Utils.distanceBetween, + getAnchorsFromArcData = Two.Utils.getAnchorsFromArcData, + distanceBetweenSquared = Two.Utils.distanceBetweenSquared, + ratioBetween = Two.Utils.ratioBetween, + angleBetween = Two.Utils.angleBetween, + getControlPoints = Two.Utils.getControlPoints, + getCurveFromPoints = Two.Utils.getCurveFromPoints, + solveSegmentIntersection = Two.Utils.solveSegmentIntersection, + decoupleShapes = Two.Utils.decoupleShapes, + mod = Two.Utils.mod, + getBackingStoreRatio = Two.Utils.getBackingStoreRatio, + getPointOnCubicBezier = Two.Utils.getPointOnCubicBezier, + getCurveLength = Two.Utils.getCurveLength, + integrate = Two.Utils.integrate, + getReflection = Two.Utils.getReflection; + + _.extend(Two.prototype, Two.Utils.Events, { + + appendTo: function(elem) { + + elem.appendChild(this.renderer.domElement); + return this; + + }, + + play: function() { + + Two.Utils.setPlaying.call(this, true); + return this.trigger(Two.Events.play); + + }, + + pause: function() { + + this.playing = false; + return this.trigger(Two.Events.pause); + + }, + + /** + * Update positions and calculations in one pass before rendering. + */ + update: function() { + + var animated = !!this._lastFrame; + var now = perf.now(); + + this.frameCount++; + + if (animated) { + this.timeDelta = parseFloat((now - this._lastFrame).toFixed(3)); + } + this._lastFrame = now; + + var width = this.width; + var height = this.height; + var renderer = this.renderer; + + // Update width / height for the renderer + if (width !== renderer.width || height !== renderer.height) { + renderer.setSize(width, height, this.ratio); + } + + this.trigger(Two.Events.update, this.frameCount, this.timeDelta); + + return this.render(); + + }, + + /** + * Render all drawable - visible objects of the scene. + */ + render: function() { + + this.renderer.render(); + return this.trigger(Two.Events.render, this.frameCount); + + }, + + /** + * Convenience Methods + */ + + add: function(o) { + + var objects = o; + if (!(objects instanceof Array)) { + objects = _.toArray(arguments); + } + + this.scene.add(objects); + return this; + + }, + + remove: function(o) { + + var objects = o; + if (!(objects instanceof Array)) { + objects = _.toArray(arguments); + } + + this.scene.remove(objects); + + return this; + + }, + + clear: function() { + + this.scene.remove(_.toArray(this.scene.children)); + return this; + + }, + + makeLine: function(x1, y1, x2, y2) { + + var line = new Two.Line(x1, y1, x2, y2); + this.scene.add(line); + + return line; + + }, + + makeRectangle: function(x, y, width, height) { + + var rect = new Two.Rectangle(x, y, width, height); + this.scene.add(rect); + + return rect; + + }, + + makeRoundedRectangle: function(x, y, width, height, sides) { + + var rect = new Two.RoundedRectangle(x, y, width, height, sides); + this.scene.add(rect); + + return rect; + + }, + + makeCircle: function(ox, oy, r) { + + var circle = new Two.Circle(ox, oy, r); + this.scene.add(circle); + + return circle; + + }, + + makeEllipse: function(ox, oy, rx, ry) { + + var ellipse = new Two.Ellipse(ox, oy, rx, ry); + this.scene.add(ellipse); + + return ellipse; + + }, + + makeStar: function(ox, oy, or, ir, sides) { + + var star = new Two.Star(ox, oy, or, ir, sides); + this.scene.add(star); + + return star; + + }, + + makeCurve: function(p) { + + var l = arguments.length, points = p; + if (!_.isArray(p)) { + points = []; + for (var i = 0; i < l; i+=2) { + var x = arguments[i]; + if (!_.isNumber(x)) { + break; + } + var y = arguments[i + 1]; + points.push(new Two.Anchor(x, y)); + } + } + + var last = arguments[l - 1]; + var curve = new Two.Path(points, !(_.isBoolean(last) ? last : undefined), true); + var rect = curve.getBoundingClientRect(); + curve.center().translation + .set(rect.left + rect.width / 2, rect.top + rect.height / 2); + + this.scene.add(curve); + + return curve; + + }, + + makePolygon: function(ox, oy, r, sides) { + + var poly = new Two.Polygon(ox, oy, r, sides); + this.scene.add(poly); + + return poly; + + }, + + /* + * Make an Arc Segment + */ + + makeArcSegment: function(ox, oy, ir, or, sa, ea, res) { + var arcSegment = new Two.ArcSegment(ox, oy, ir, or, sa, ea, res); + this.scene.add(arcSegment); + return arcSegment; + }, + + /** + * Convenience method to make and draw a Two.Path. + */ + makePath: function(p) { + + var l = arguments.length, points = p; + if (!_.isArray(p)) { + points = []; + for (var i = 0; i < l; i+=2) { + var x = arguments[i]; + if (!_.isNumber(x)) { + break; + } + var y = arguments[i + 1]; + points.push(new Two.Anchor(x, y)); + } + } + + var last = arguments[l - 1]; + var path = new Two.Path(points, !(_.isBoolean(last) ? last : undefined)); + var rect = path.getBoundingClientRect(); + path.center().translation + .set(rect.left + rect.width / 2, rect.top + rect.height / 2); + + this.scene.add(path); + + return path; + + }, + + /** + * Convenience method to make and add a Two.Text. + */ + makeText: function(message, x, y, styles) { + var text = new Two.Text(message, x, y, styles); + this.add(text); + return text; + }, + + /** + * Convenience method to make and add a Two.LinearGradient. + */ + makeLinearGradient: function(x1, y1, x2, y2 /* stops */) { + + var stops = slice.call(arguments, 4); + var gradient = new Two.LinearGradient(x1, y1, x2, y2, stops); + + this.add(gradient); + + return gradient; + + }, + + /** + * Convenience method to make and add a Two.RadialGradient. + */ + makeRadialGradient: function(x1, y1, r /* stops */) { + + var stops = slice.call(arguments, 3); + var gradient = new Two.RadialGradient(x1, y1, r, stops); + + this.add(gradient); + + return gradient; + + }, + + makeSprite: function(path, x, y, cols, rows, frameRate, autostart) { + + var sprite = new Two.Sprite(path, x, y, cols, rows, frameRate); + if (!!autostart) { + sprite.play(); + } + this.add(sprite); + + return sprite; + + }, + + makeImageSequence: function(paths, x, y, frameRate, autostart) { + + var imageSequence = new Two.ImageSequence(paths, x, y, frameRate); + if (!!autostart) { + imageSequence.play(); + } + this.add(imageSequence); + + return imageSequence; + + }, + + makeTexture: function(path, callback) { + + var texture = new Two.Texture(path, callback); + return texture; + + }, + + makeGroup: function(o) { + + var objects = o; + if (!(objects instanceof Array)) { + objects = _.toArray(arguments); + } + + var group = new Two.Group(); + this.scene.add(group); + group.add(objects); + + return group; + + }, + + /** + * Interpret an SVG Node and add it to this instance's scene. The + * distinction should be made that this doesn't `import` svg's, it solely + * interprets them into something compatible for Two.js — this is slightly + * different than a direct transcription. + * + * @param {Object} svgNode - The node to be parsed + * @param {Boolean} shallow - Don't create a top-most group but + * append all contents directly + */ + interpret: function(svgNode, shallow) { + + var tag = svgNode.tagName.toLowerCase(); + + if (!(tag in Two.Utils.read)) { + return null; + } + + var node = Two.Utils.read[tag].call(this, svgNode); + + if (shallow && node instanceof Two.Group) { + this.add(node.children); + } else { + this.add(node); + } + + return node; + + }, + + /** + * Load an SVG file / text and interpret. + */ + load: function(text, callback) { + + var nodes = [], elem, i; + + if (/.*\.svg/ig.test(text)) { + + Two.Utils.xhr(text, _.bind(function(data) { + + dom.temp.innerHTML = data; + for (i = 0; i < dom.temp.children.length; i++) { + elem = dom.temp.children[i]; + nodes.push(this.interpret(elem)); + } + + callback(nodes.length <= 1 ? nodes[0] : nodes, + dom.temp.children.length <= 1 ? dom.temp.children[0] : dom.temp.children); + + }, this)); + + return this; + + } + + dom.temp.innerHTML = text; + for (i = 0; i < dom.temp.children.length; i++) { + elem = dom.temp.children[i]; + nodes.push(this.interpret(elem)); + } + + callback(nodes.length <= 1 ? nodes[0] : nodes, + dom.temp.children.length <= 1 ? dom.temp.children[0] : dom.temp.children); + + return this; + + } + + }); + + function fitToWindow() { + + var wr = document.body.getBoundingClientRect(); + + var width = this.width = wr.width; + var height = this.height = wr.height; + + this.renderer.setSize(width, height, this.ratio); + this.trigger(Two.Events.resize, width, height); + + } + + // Request Animation Frame + + var raf = dom.getRequestAnimationFrame(); + + function loop() { + + raf(loop); + + for (var i = 0; i < Two.Instances.length; i++) { + var t = Two.Instances[i]; + if (t.playing) { + t.update(); + } + } + + } + + if (typeof define === 'function' && define.amd) { + define('two', [], function() { + return Two; + }); + } else if (typeof module != 'undefined' && module.exports) { + module.exports = Two; + } + + return Two; + +})((typeof global !== 'undefined' ? global : this).Two); + +(function(Two) { + + var _ = Two.Utils; + + var Registry = Two.Registry = function() { + + this.map = {}; + + }; + + _.extend(Registry, { + + }); + + _.extend(Registry.prototype, { + + add: function(id, obj) { + this.map[id] = obj; + return this; + }, + + remove: function(id) { + delete this.map[id]; + return this; + }, + + get: function(id) { + return this.map[id]; + }, + + contains: function(id) { + return id in this.map; + } + + }); + +})((typeof global !== 'undefined' ? global : this).Two); + +(function(Two) { + + var _ = Two.Utils; + + var Vector = Two.Vector = function(x, y) { + + this.x = x || 0; + this.y = y || 0; + + }; + + _.extend(Vector, { + + zero: new Two.Vector() + + }); + + _.extend(Vector.prototype, Two.Utils.Events, { + + set: function(x, y) { + this.x = x; + this.y = y; + return this; + }, + + copy: function(v) { + this.x = v.x; + this.y = v.y; + return this; + }, + + clear: function() { + this.x = 0; + this.y = 0; + return this; + }, + + clone: function() { + return new Vector(this.x, this.y); + }, + + add: function(v1, v2) { + this.x = v1.x + v2.x; + this.y = v1.y + v2.y; + return this; + }, + + addSelf: function(v) { + this.x += v.x; + this.y += v.y; + return this; + }, + + sub: function(v1, v2) { + this.x = v1.x - v2.x; + this.y = v1.y - v2.y; + return this; + }, + + subSelf: function(v) { + this.x -= v.x; + this.y -= v.y; + return this; + }, + + multiplySelf: function(v) { + this.x *= v.x; + this.y *= v.y; + return this; + }, + + multiplyScalar: function(s) { + this.x *= s; + this.y *= s; + return this; + }, + + divideScalar: function(s) { + if (s) { + this.x /= s; + this.y /= s; + } else { + this.set(0, 0); + } + return this; + }, + + negate: function() { + return this.multiplyScalar(-1); + }, + + dot: function(v) { + return this.x * v.x + this.y * v.y; + }, + + lengthSquared: function() { + return this.x * this.x + this.y * this.y; + }, + + length: function() { + return Math.sqrt(this.lengthSquared()); + }, + + normalize: function() { + return this.divideScalar(this.length()); + }, + + distanceTo: function(v) { + return Math.sqrt(this.distanceToSquared(v)); + }, + + distanceToSquared: function(v) { + var dx = this.x - v.x, + dy = this.y - v.y; + return dx * dx + dy * dy; + }, + + setLength: function(l) { + return this.normalize().multiplyScalar(l); + }, + + equals: function(v, eps) { + eps = (typeof eps === 'undefined') ? 0.0001 : eps; + return (this.distanceTo(v) < eps); + }, + + lerp: function(v, t) { + var x = (v.x - this.x) * t + this.x; + var y = (v.y - this.y) * t + this.y; + return this.set(x, y); + }, + + isZero: function(eps) { + eps = (typeof eps === 'undefined') ? 0.0001 : eps; + return (this.length() < eps); + }, + + toString: function() { + return this.x + ', ' + this.y; + }, + + toObject: function() { + return { x: this.x, y: this.y }; + }, + + rotate: function (radians) { + var cos = Math.cos(radians); + var sin = Math.sin(radians); + this.x = this.x * cos - this.y * sin; + this.y = this.x * sin + this.y * cos; + return this; + } + + }); + + var BoundProto = { + + set: function(x, y) { + this._x = x; + this._y = y; + return this.trigger(Two.Events.change); + }, + + copy: function(v) { + this._x = v.x; + this._y = v.y; + return this.trigger(Two.Events.change); + }, + + clear: function() { + this._x = 0; + this._y = 0; + return this.trigger(Two.Events.change); + }, + + clone: function() { + return new Vector(this._x, this._y); + }, + + add: function(v1, v2) { + this._x = v1.x + v2.x; + this._y = v1.y + v2.y; + return this.trigger(Two.Events.change); + }, + + addSelf: function(v) { + this._x += v.x; + this._y += v.y; + return this.trigger(Two.Events.change); + }, + + sub: function(v1, v2) { + this._x = v1.x - v2.x; + this._y = v1.y - v2.y; + return this.trigger(Two.Events.change); + }, + + subSelf: function(v) { + this._x -= v.x; + this._y -= v.y; + return this.trigger(Two.Events.change); + }, + + multiplySelf: function(v) { + this._x *= v.x; + this._y *= v.y; + return this.trigger(Two.Events.change); + }, + + multiplyScalar: function(s) { + this._x *= s; + this._y *= s; + return this.trigger(Two.Events.change); + }, + + divideScalar: function(s) { + if (s) { + this._x /= s; + this._y /= s; + return this.trigger(Two.Events.change); + } + return this.clear(); + }, + + negate: function() { + return this.multiplyScalar(-1); + }, + + dot: function(v) { + return this._x * v.x + this._y * v.y; + }, + + lengthSquared: function() { + return this._x * this._x + this._y * this._y; + }, + + length: function() { + return Math.sqrt(this.lengthSquared()); + }, + + normalize: function() { + return this.divideScalar(this.length()); + }, + + distanceTo: function(v) { + return Math.sqrt(this.distanceToSquared(v)); + }, + + distanceToSquared: function(v) { + var dx = this._x - v.x, + dy = this._y - v.y; + return dx * dx + dy * dy; + }, + + setLength: function(l) { + return this.normalize().multiplyScalar(l); + }, + + equals: function(v, eps) { + eps = (typeof eps === 'undefined') ? 0.0001 : eps; + return (this.distanceTo(v) < eps); + }, + + lerp: function(v, t) { + var x = (v.x - this._x) * t + this._x; + var y = (v.y - this._y) * t + this._y; + return this.set(x, y); + }, + + isZero: function(eps) { + eps = (typeof eps === 'undefined') ? 0.0001 : eps; + return (this.length() < eps); + }, + + toString: function() { + return this._x + ', ' + this._y; + }, + + toObject: function() { + return { x: this._x, y: this._y }; + }, + + rotate: function (radians) { + var cos = Math.cos(radians); + var sin = Math.sin(radians); + this._x = this._x * cos - this._y * sin; + this._y = this._x * sin + this._y * cos; + return this; + } + + }; + + var xgs = { + enumerable: true, + get: function() { + return this._x; + }, + set: function(v) { + this._x = v; + this.trigger(Two.Events.change, 'x'); + } + }; + + var ygs = { + enumerable: true, + get: function() { + return this._y; + }, + set: function(v) { + this._y = v; + this.trigger(Two.Events.change, 'y'); + } + }; + + /** + * Override Backbone bind / on in order to add properly broadcasting. + * This allows Two.Vector to not broadcast events unless event listeners + * are explicity bound to it. + */ + + Two.Vector.prototype.bind = Two.Vector.prototype.on = function() { + + if (!this._bound) { + this._x = this.x; + this._y = this.y; + Object.defineProperty(this, 'x', xgs); + Object.defineProperty(this, 'y', ygs); + _.extend(this, BoundProto); + this._bound = true; // Reserved for event initialization check + } + + Two.Utils.Events.bind.apply(this, arguments); + + return this; + + }; + +})((typeof global !== 'undefined' ? global : this).Two); + +(function(Two) { + + // Localized variables + var commands = Two.Commands; + var _ = Two.Utils; + + /** + * An object that holds 3 `Two.Vector`s, the anchor point and its + * corresponding handles: `left` and `right`. + */ + var Anchor = Two.Anchor = function(x, y, ux, uy, vx, vy, command) { + + Two.Vector.call(this, x, y); + + this._broadcast = _.bind(function() { + this.trigger(Two.Events.change); + }, this); + + this._command = command || commands.move; + this._relative = true; + + if (!command) { + return this; + } + + Anchor.AppendCurveProperties(this); + + if (_.isNumber(ux)) { + this.controls.left.x = ux; + } + if (_.isNumber(uy)) { + this.controls.left.y = uy; + } + if (_.isNumber(vx)) { + this.controls.right.x = vx; + } + if (_.isNumber(vy)) { + this.controls.right.y = vy; + } + + }; + + _.extend(Anchor, { + + AppendCurveProperties: function(anchor) { + anchor.controls = { + left: new Two.Vector(0, 0), + right: new Two.Vector(0, 0) + }; + } + + }); + + var AnchorProto = { + + listen: function() { + + if (!_.isObject(this.controls)) { + Anchor.AppendCurveProperties(this); + } + + this.controls.left.bind(Two.Events.change, this._broadcast); + this.controls.right.bind(Two.Events.change, this._broadcast); + + return this; + + }, + + ignore: function() { + + this.controls.left.unbind(Two.Events.change, this._broadcast); + this.controls.right.unbind(Two.Events.change, this._broadcast); + + return this; + + }, + + clone: function() { + + var controls = this.controls; + + var clone = new Two.Anchor( + this.x, + this.y, + controls && controls.left.x, + controls && controls.left.y, + controls && controls.right.x, + controls && controls.right.y, + this.command + ); + clone.relative = this._relative; + return clone; + + }, + + toObject: function() { + var o = { + x: this.x, + y: this.y + }; + if (this._command) { + o.command = this._command; + } + if (this._relative) { + o.relative = this._relative; + } + if (this.controls) { + o.controls = { + left: this.controls.left.toObject(), + right: this.controls.right.toObject() + }; + } + return o; + }, + + toString: function() { + if (!this.controls) { + return [this._x, this._y].join(', '); + } + return [this._x, this._y, this.controls.left.x, this.controls.left.y, + this.controls.right.x, this.controls.right.y].join(', '); + } + + }; + + Object.defineProperty(Anchor.prototype, 'command', { + + enumerable: true, + + get: function() { + return this._command; + }, + + set: function(c) { + this._command = c; + if (this._command === commands.curve && !_.isObject(this.controls)) { + Anchor.AppendCurveProperties(this); + } + return this.trigger(Two.Events.change); + } + + }); + + Object.defineProperty(Anchor.prototype, 'relative', { + + enumerable: true, + + get: function() { + return this._relative; + }, + + set: function(b) { + if (this._relative == b) { + return this; + } + this._relative = !!b; + return this.trigger(Two.Events.change); + } + + }); + + _.extend(Anchor.prototype, Two.Vector.prototype, AnchorProto); + + // Make it possible to bind and still have the Anchor specific + // inheritance from Two.Vector + Two.Anchor.prototype.bind = Two.Anchor.prototype.on = function() { + Two.Vector.prototype.bind.apply(this, arguments); + _.extend(this, AnchorProto); + }; + + Two.Anchor.prototype.unbind = Two.Anchor.prototype.off = function() { + Two.Vector.prototype.unbind.apply(this, arguments); + _.extend(this, AnchorProto); + }; + +})((typeof global !== 'undefined' ? global : this).Two); + +(function(Two) { + + /** + * Constants + */ + var cos = Math.cos, sin = Math.sin, tan = Math.tan; + var _ = Two.Utils; + + /** + * Two.Matrix contains an array of elements that represent + * the two dimensional 3 x 3 matrix as illustrated below: + * + * ===== + * a b c + * d e f + * g h i // this row is not really used in 2d transformations + * ===== + * + * String order is for transform strings: a, d, b, e, c, f + * + * @class + */ + var Matrix = Two.Matrix = function(a, b, c, d, e, f) { + + this.elements = new Two.Array(9); + + var elements = a; + if (!_.isArray(elements)) { + elements = _.toArray(arguments); + } + + // initialize the elements with default values. + + this.identity().set(elements); + + }; + + _.extend(Matrix, { + + Identity: [ + 1, 0, 0, + 0, 1, 0, + 0, 0, 1 + ], + + /** + * Multiply two matrix 3x3 arrays + */ + Multiply: function(A, B, C) { + + if (B.length <= 3) { // Multiply Vector + + var x, y, z, e = A; + + var a = B[0] || 0, + b = B[1] || 0, + c = B[2] || 0; + + // Go down rows first + // a, d, g, b, e, h, c, f, i + + x = e[0] * a + e[1] * b + e[2] * c; + y = e[3] * a + e[4] * b + e[5] * c; + z = e[6] * a + e[7] * b + e[8] * c; + + return { x: x, y: y, z: z }; + + } + + var A0 = A[0], A1 = A[1], A2 = A[2]; + var A3 = A[3], A4 = A[4], A5 = A[5]; + var A6 = A[6], A7 = A[7], A8 = A[8]; + + var B0 = B[0], B1 = B[1], B2 = B[2]; + var B3 = B[3], B4 = B[4], B5 = B[5]; + var B6 = B[6], B7 = B[7], B8 = B[8]; + + C = C || new Two.Array(9); + + C[0] = A0 * B0 + A1 * B3 + A2 * B6; + C[1] = A0 * B1 + A1 * B4 + A2 * B7; + C[2] = A0 * B2 + A1 * B5 + A2 * B8; + C[3] = A3 * B0 + A4 * B3 + A5 * B6; + C[4] = A3 * B1 + A4 * B4 + A5 * B7; + C[5] = A3 * B2 + A4 * B5 + A5 * B8; + C[6] = A6 * B0 + A7 * B3 + A8 * B6; + C[7] = A6 * B1 + A7 * B4 + A8 * B7; + C[8] = A6 * B2 + A7 * B5 + A8 * B8; + + return C; + + } + + }); + + _.extend(Matrix.prototype, Two.Utils.Events, { + + /** + * Takes an array of elements or the arguments list itself to + * set and update the current matrix's elements. Only updates + * specified values. + */ + set: function(a) { + + var elements = a; + if (!_.isArray(elements)) { + elements = _.toArray(arguments); + } + + _.extend(this.elements, elements); + + return this.trigger(Two.Events.change); + + }, + + /** + * Turn matrix to identity, like resetting. + */ + identity: function() { + + this.set(Matrix.Identity); + + return this; + + }, + + /** + * Multiply scalar or multiply by another matrix. + */ + multiply: function(a, b, c, d, e, f, g, h, i) { + + var elements = arguments, l = elements.length; + + // Multiply scalar + + if (l <= 1) { + + _.each(this.elements, function(v, i) { + this.elements[i] = v * a; + }, this); + + return this.trigger(Two.Events.change); + + } + + if (l <= 3) { // Multiply Vector + + var x, y, z; + a = a || 0; + b = b || 0; + c = c || 0; + e = this.elements; + + // Go down rows first + // a, d, g, b, e, h, c, f, i + + x = e[0] * a + e[1] * b + e[2] * c; + y = e[3] * a + e[4] * b + e[5] * c; + z = e[6] * a + e[7] * b + e[8] * c; + + return { x: x, y: y, z: z }; + + } + + // Multiple matrix + + var A = this.elements; + var B = elements; + + var A0 = A[0], A1 = A[1], A2 = A[2]; + var A3 = A[3], A4 = A[4], A5 = A[5]; + var A6 = A[6], A7 = A[7], A8 = A[8]; + + var B0 = B[0], B1 = B[1], B2 = B[2]; + var B3 = B[3], B4 = B[4], B5 = B[5]; + var B6 = B[6], B7 = B[7], B8 = B[8]; + + this.elements[0] = A0 * B0 + A1 * B3 + A2 * B6; + this.elements[1] = A0 * B1 + A1 * B4 + A2 * B7; + this.elements[2] = A0 * B2 + A1 * B5 + A2 * B8; + + this.elements[3] = A3 * B0 + A4 * B3 + A5 * B6; + this.elements[4] = A3 * B1 + A4 * B4 + A5 * B7; + this.elements[5] = A3 * B2 + A4 * B5 + A5 * B8; + + this.elements[6] = A6 * B0 + A7 * B3 + A8 * B6; + this.elements[7] = A6 * B1 + A7 * B4 + A8 * B7; + this.elements[8] = A6 * B2 + A7 * B5 + A8 * B8; + + return this.trigger(Two.Events.change); + + }, + + inverse: function(out) { + + var a = this.elements; + out = out || new Two.Matrix(); + + var a00 = a[0], a01 = a[1], a02 = a[2]; + var a10 = a[3], a11 = a[4], a12 = a[5]; + var a20 = a[6], a21 = a[7], a22 = a[8]; + + var b01 = a22 * a11 - a12 * a21; + var b11 = -a22 * a10 + a12 * a20; + var b21 = a21 * a10 - a11 * a20; + + // Calculate the determinant + var det = a00 * b01 + a01 * b11 + a02 * b21; + + if (!det) { + return null; + } + + det = 1.0 / det; + + out.elements[0] = b01 * det; + out.elements[1] = (-a22 * a01 + a02 * a21) * det; + out.elements[2] = (a12 * a01 - a02 * a11) * det; + out.elements[3] = b11 * det; + out.elements[4] = (a22 * a00 - a02 * a20) * det; + out.elements[5] = (-a12 * a00 + a02 * a10) * det; + out.elements[6] = b21 * det; + out.elements[7] = (-a21 * a00 + a01 * a20) * det; + out.elements[8] = (a11 * a00 - a01 * a10) * det; + + return out; + + }, + + /** + * Set a scalar onto the matrix. + */ + scale: function(sx, sy) { + + var l = arguments.length; + if (l <= 1) { + sy = sx; + } + + return this.multiply(sx, 0, 0, 0, sy, 0, 0, 0, 1); + + }, + + /** + * Rotate the matrix. + */ + rotate: function(radians) { + + var c = cos(radians); + var s = sin(radians); + + return this.multiply(c, -s, 0, s, c, 0, 0, 0, 1); + + }, + + /** + * Translate the matrix. + */ + translate: function(x, y) { + + return this.multiply(1, 0, x, 0, 1, y, 0, 0, 1); + + }, + + /* + * Skew the matrix by an angle in the x axis direction. + */ + skewX: function(radians) { + + var a = tan(radians); + + return this.multiply(1, a, 0, 0, 1, 0, 0, 0, 1); + + }, + + /* + * Skew the matrix by an angle in the y axis direction. + */ + skewY: function(radians) { + + var a = tan(radians); + + return this.multiply(1, 0, 0, a, 1, 0, 0, 0, 1); + + }, + + /** + * Create a transform string to be used with rendering apis. + */ + toString: function(fullMatrix) { + var temp = []; + + this.toArray(fullMatrix, temp); + + return temp.join(' '); + + }, + + /** + * Create a transform array to be used with rendering apis. + */ + toArray: function(fullMatrix, output) { + + var elements = this.elements; + var hasOutput = !!output; + + var a = parseFloat(elements[0].toFixed(3)); + var b = parseFloat(elements[1].toFixed(3)); + var c = parseFloat(elements[2].toFixed(3)); + var d = parseFloat(elements[3].toFixed(3)); + var e = parseFloat(elements[4].toFixed(3)); + var f = parseFloat(elements[5].toFixed(3)); + + if (!!fullMatrix) { + + var g = parseFloat(elements[6].toFixed(3)); + var h = parseFloat(elements[7].toFixed(3)); + var i = parseFloat(elements[8].toFixed(3)); + + if (hasOutput) { + output[0] = a; + output[1] = d; + output[2] = g; + output[3] = b; + output[4] = e; + output[5] = h; + output[6] = c; + output[7] = f; + output[8] = i; + return; + } + + return [ + a, d, g, b, e, h, c, f, i + ]; + } + + if (hasOutput) { + output[0] = a; + output[1] = d; + output[2] = b; + output[3] = e; + output[4] = c; + output[5] = f; + return; + } + + return [ + a, d, b, e, c, f // Specific format see LN:19 + ]; + + }, + + /** + * Clone the current matrix. + */ + clone: function() { + var a, b, c, d, e, f, g, h, i; + + a = this.elements[0]; + b = this.elements[1]; + c = this.elements[2]; + d = this.elements[3]; + e = this.elements[4]; + f = this.elements[5]; + g = this.elements[6]; + h = this.elements[7]; + i = this.elements[8]; + + return new Two.Matrix(a, b, c, d, e, f, g, h, i); + + } + + }); + +})((typeof global !== 'undefined' ? global : this).Two); + +(function(Two) { + + // Localize variables + var mod = Two.Utils.mod, toFixed = Two.Utils.toFixed; + var _ = Two.Utils; + + var svg = { + + version: 1.1, + + ns: 'http://www.w3.org/2000/svg', + xlink: 'http://www.w3.org/1999/xlink', + + alignments: { + left: 'start', + center: 'middle', + right: 'end' + }, + + /** + * Create an svg namespaced element. + */ + createElement: function(name, attrs) { + var tag = name; + var elem = document.createElementNS(svg.ns, tag); + if (tag === 'svg') { + attrs = _.defaults(attrs || {}, { + version: svg.version + }); + } + if (!_.isEmpty(attrs)) { + svg.setAttributes(elem, attrs); + } + return elem; + }, + + /** + * Add attributes from an svg element. + */ + setAttributes: function(elem, attrs) { + var keys = Object.keys(attrs); + for (var i = 0; i < keys.length; i++) { + if (/href/.test(keys[i])) { + elem.setAttributeNS(svg.xlink, keys[i], attrs[keys[i]]); + } else { + elem.setAttribute(keys[i], attrs[keys[i]]); + } + } + return this; + }, + + /** + * Remove attributes from an svg element. + */ + removeAttributes: function(elem, attrs) { + for (var key in attrs) { + elem.removeAttribute(key); + } + return this; + }, + + /** + * Turn a set of vertices into a string for the d property of a path + * element. It is imperative that the string collation is as fast as + * possible, because this call will be happening multiple times a + * second. + */ + toString: function(points, closed) { + + var l = points.length, + last = l - 1, + d, // The elusive last Two.Commands.move point + ret = ''; + + for (var i = 0; i < l; i++) { + var b = points[i]; + var command; + var prev = closed ? mod(i - 1, l) : Math.max(i - 1, 0); + var next = closed ? mod(i + 1, l) : Math.min(i + 1, last); + + var a = points[prev]; + var c = points[next]; + + var vx, vy, ux, uy, ar, bl, br, cl; + + // Access x and y directly, + // bypassing the getter + var x = toFixed(b._x); + var y = toFixed(b._y); + + switch (b._command) { + + case Two.Commands.close: + command = Two.Commands.close; + break; + + case Two.Commands.curve: + + ar = (a.controls && a.controls.right) || Two.Vector.zero; + bl = (b.controls && b.controls.left) || Two.Vector.zero; + + if (a._relative) { + vx = toFixed((ar.x + a.x)); + vy = toFixed((ar.y + a.y)); + } else { + vx = toFixed(ar.x); + vy = toFixed(ar.y); + } + + if (b._relative) { + ux = toFixed((bl.x + b.x)); + uy = toFixed((bl.y + b.y)); + } else { + ux = toFixed(bl.x); + uy = toFixed(bl.y); + } + + command = ((i === 0) ? Two.Commands.move : Two.Commands.curve) + + ' ' + vx + ' ' + vy + ' ' + ux + ' ' + uy + ' ' + x + ' ' + y; + break; + + case Two.Commands.move: + d = b; + command = Two.Commands.move + ' ' + x + ' ' + y; + break; + + default: + command = b._command + ' ' + x + ' ' + y; + + } + + // Add a final point and close it off + + if (i >= last && closed) { + + if (b._command === Two.Commands.curve) { + + // Make sure we close to the most previous Two.Commands.move + c = d; + + br = (b.controls && b.controls.right) || b; + cl = (c.controls && c.controls.left) || c; + + if (b._relative) { + vx = toFixed((br.x + b.x)); + vy = toFixed((br.y + b.y)); + } else { + vx = toFixed(br.x); + vy = toFixed(br.y); + } + + if (c._relative) { + ux = toFixed((cl.x + c.x)); + uy = toFixed((cl.y + c.y)); + } else { + ux = toFixed(cl.x); + uy = toFixed(cl.y); + } + + x = toFixed(c.x); + y = toFixed(c.y); + + command += + ' C ' + vx + ' ' + vy + ' ' + ux + ' ' + uy + ' ' + x + ' ' + y; + } + + command += ' Z'; + + } + + ret += command + ' '; + + } + + return ret; + + }, + + getClip: function(shape) { + + var clip = shape._renderer.clip; + + if (!clip) { + + var root = shape; + + while (root.parent) { + root = root.parent; + } + + clip = shape._renderer.clip = svg.createElement('clipPath'); + root.defs.appendChild(clip); + + } + + return clip; + + }, + + group: { + + // TODO: Can speed up. + // TODO: How does this effect a f + appendChild: function(object) { + + var elem = object._renderer.elem; + + if (!elem) { + return; + } + + var tag = elem.nodeName; + + if (!tag || /(radial|linear)gradient/i.test(tag) || object._clip) { + return; + } + + this.elem.appendChild(elem); + + }, + + removeChild: function(object) { + + var elem = object._renderer.elem; + + if (!elem || elem.parentNode != this.elem) { + return; + } + + var tag = elem.nodeName; + + if (!tag) { + return; + } + + // Defer subtractions while clipping. + if (object._clip) { + return; + } + + this.elem.removeChild(elem); + + }, + + orderChild: function(object) { + this.elem.appendChild(object._renderer.elem); + }, + + renderChild: function(child) { + svg[child._renderer.type].render.call(child, this); + }, + + render: function(domElement) { + + this._update(); + + // Shortcut for hidden objects. + // Doesn't reset the flags, so changes are stored and + // applied once the object is visible again + if (this._opacity === 0 && !this._flagOpacity) { + return this; + } + + if (!this._renderer.elem) { + this._renderer.elem = svg.createElement('g', { + id: this.id + }); + domElement.appendChild(this._renderer.elem); + } + + // _Update styles for the <g> + var flagMatrix = this._matrix.manual || this._flagMatrix; + var context = { + domElement: domElement, + elem: this._renderer.elem + }; + + if (flagMatrix) { + this._renderer.elem.setAttribute('transform', 'matrix(' + this._matrix.toString() + ')'); + } + + for (var i = 0; i < this.children.length; i++) { + var child = this.children[i]; + svg[child._renderer.type].render.call(child, domElement); + } + + if (this._flagOpacity) { + this._renderer.elem.setAttribute('opacity', this._opacity); + } + + if (this._flagAdditions) { + this.additions.forEach(svg.group.appendChild, context); + } + + if (this._flagSubtractions) { + this.subtractions.forEach(svg.group.removeChild, context); + } + + if (this._flagOrder) { + this.children.forEach(svg.group.orderChild, context); + } + + /** + * Commented two-way functionality of clips / masks with groups and + * polygons. Uncomment when this bug is fixed: + * https://code.google.com/p/chromium/issues/detail?id=370951 + */ + + // if (this._flagClip) { + + // clip = svg.getClip(this); + // elem = this._renderer.elem; + + // if (this._clip) { + // elem.removeAttribute('id'); + // clip.setAttribute('id', this.id); + // clip.appendChild(elem); + // } else { + // clip.removeAttribute('id'); + // elem.setAttribute('id', this.id); + // this.parent._renderer.elem.appendChild(elem); // TODO: should be insertBefore + // } + + // } + + if (this._flagMask) { + if (this._mask) { + this._renderer.elem.setAttribute('clip-path', 'url(#' + this._mask.id + ')'); + } else { + this._renderer.elem.removeAttribute('clip-path'); + } + } + + return this.flagReset(); + + } + + }, + + path: { + + render: function(domElement) { + + this._update(); + + // Shortcut for hidden objects. + // Doesn't reset the flags, so changes are stored and + // applied once the object is visible again + if (this._opacity === 0 && !this._flagOpacity) { + return this; + } + + // Collect any attribute that needs to be changed here + var changed = {}; + + var flagMatrix = this._matrix.manual || this._flagMatrix; + + if (flagMatrix) { + changed.transform = 'matrix(' + this._matrix.toString() + ')'; + } + + if (this._flagVertices) { + var vertices = svg.toString(this._vertices, this._closed); + changed.d = vertices; + } + + if (this._fill && this._fill._renderer) { + this._fill._update(); + svg[this._fill._renderer.type].render.call(this._fill, domElement, true); + } + + if (this._flagFill) { + changed.fill = this._fill && this._fill.id + ? 'url(#' + this._fill.id + ')' : this._fill; + } + + if (this._stroke && this._stroke._renderer) { + this._stroke._update(); + svg[this._stroke._renderer.type].render.call(this._stroke, domElement, true); + } + + if (this._flagStroke) { + changed.stroke = this._stroke && this._stroke.id + ? 'url(#' + this._stroke.id + ')' : this._stroke; + } + + if (this._flagLinewidth) { + changed['stroke-width'] = this._linewidth; + } + + if (this._flagOpacity) { + changed['stroke-opacity'] = this._opacity; + changed['fill-opacity'] = this._opacity; + } + + if (this._flagVisible) { + changed.visibility = this._visible ? 'visible' : 'hidden'; + } + + if (this._flagCap) { + changed['stroke-linecap'] = this._cap; + } + + if (this._flagJoin) { + changed['stroke-linejoin'] = this._join; + } + + if (this._flagMiter) { + changed['stroke-miterlimit'] = this._miter; + } + + // If there is no attached DOM element yet, + // create it with all necessary attributes. + if (!this._renderer.elem) { + + changed.id = this.id; + this._renderer.elem = svg.createElement('path', changed); + domElement.appendChild(this._renderer.elem); + + // Otherwise apply all pending attributes + } else { + svg.setAttributes(this._renderer.elem, changed); + } + + if (this._flagClip) { + + var clip = svg.getClip(this); + var elem = this._renderer.elem; + + if (this._clip) { + elem.removeAttribute('id'); + clip.setAttribute('id', this.id); + clip.appendChild(elem); + } else { + clip.removeAttribute('id'); + elem.setAttribute('id', this.id); + this.parent._renderer.elem.appendChild(elem); // TODO: should be insertBefore + } + + } + + /** + * Commented two-way functionality of clips / masks with groups and + * polygons. Uncomment when this bug is fixed: + * https://code.google.com/p/chromium/issues/detail?id=370951 + */ + + // if (this._flagMask) { + // if (this._mask) { + // elem.setAttribute('clip-path', 'url(#' + this._mask.id + ')'); + // } else { + // elem.removeAttribute('clip-path'); + // } + // } + + return this.flagReset(); + + } + + }, + + text: { + + render: function(domElement) { + + this._update(); + + var changed = {}; + + var flagMatrix = this._matrix.manual || this._flagMatrix; + + if (flagMatrix) { + changed.transform = 'matrix(' + this._matrix.toString() + ')'; + } + + if (this._flagFamily) { + changed['font-family'] = this._family; + } + if (this._flagSize) { + changed['font-size'] = this._size; + } + if (this._flagLeading) { + changed['line-height'] = this._leading; + } + if (this._flagAlignment) { + changed['text-anchor'] = svg.alignments[this._alignment] || this._alignment; + } + if (this._flagBaseline) { + changed['alignment-baseline'] = changed['dominant-baseline'] = this._baseline; + } + if (this._flagStyle) { + changed['font-style'] = this._style; + } + if (this._flagWeight) { + changed['font-weight'] = this._weight; + } + if (this._flagDecoration) { + changed['text-decoration'] = this._decoration; + } + if (this._fill && this._fill._renderer) { + this._fill._update(); + svg[this._fill._renderer.type].render.call(this._fill, domElement, true); + } + if (this._flagFill) { + changed.fill = this._fill && this._fill.id + ? 'url(#' + this._fill.id + ')' : this._fill; + } + if (this._stroke && this._stroke._renderer) { + this._stroke._update(); + svg[this._stroke._renderer.type].render.call(this._stroke, domElement, true); + } + if (this._flagStroke) { + changed.stroke = this._stroke && this._stroke.id + ? 'url(#' + this._stroke.id + ')' : this._stroke; + } + if (this._flagLinewidth) { + changed['stroke-width'] = this._linewidth; + } + if (this._flagOpacity) { + changed.opacity = this._opacity; + } + if (this._flagVisible) { + changed.visibility = this._visible ? 'visible' : 'hidden'; + } + + if (!this._renderer.elem) { + + changed.id = this.id; + + this._renderer.elem = svg.createElement('text', changed); + domElement.defs.appendChild(this._renderer.elem); + + } else { + + svg.setAttributes(this._renderer.elem, changed); + + } + + if (this._flagClip) { + + var clip = svg.getClip(this); + var elem = this._renderer.elem; + + if (this._clip) { + elem.removeAttribute('id'); + clip.setAttribute('id', this.id); + clip.appendChild(elem); + } else { + clip.removeAttribute('id'); + elem.setAttribute('id', this.id); + this.parent._renderer.elem.appendChild(elem); // TODO: should be insertBefore + } + + } + + if (this._flagValue) { + this._renderer.elem.textContent = this._value; + } + + return this.flagReset(); + + } + + }, + + 'linear-gradient': { + + render: function(domElement, silent) { + + if (!silent) { + this._update(); + } + + var changed = {}; + + if (this._flagEndPoints) { + changed.x1 = this.left._x; + changed.y1 = this.left._y; + changed.x2 = this.right._x; + changed.y2 = this.right._y; + } + + if (this._flagSpread) { + changed.spreadMethod = this._spread; + } + + // If there is no attached DOM element yet, + // create it with all necessary attributes. + if (!this._renderer.elem) { + + changed.id = this.id; + changed.gradientUnits = 'userSpaceOnUse'; + this._renderer.elem = svg.createElement('linearGradient', changed); + domElement.defs.appendChild(this._renderer.elem); + + // Otherwise apply all pending attributes + } else { + + svg.setAttributes(this._renderer.elem, changed); + + } + + if (this._flagStops) { + + var lengthChanged = this._renderer.elem.childNodes.length + !== this.stops.length; + + if (lengthChanged) { + this._renderer.elem.childNodes.length = 0; + } + + for (var i = 0; i < this.stops.length; i++) { + + var stop = this.stops[i]; + var attrs = {}; + + if (stop._flagOffset) { + attrs.offset = 100 * stop._offset + '%'; + } + if (stop._flagColor) { + attrs['stop-color'] = stop._color; + } + if (stop._flagOpacity) { + attrs['stop-opacity'] = stop._opacity; + } + + if (!stop._renderer.elem) { + stop._renderer.elem = svg.createElement('stop', attrs); + } else { + svg.setAttributes(stop._renderer.elem, attrs); + } + + if (lengthChanged) { + this._renderer.elem.appendChild(stop._renderer.elem); + } + stop.flagReset(); + + } + + } + + return this.flagReset(); + + } + + }, + + 'radial-gradient': { + + render: function(domElement, silent) { + + if (!silent) { + this._update(); + } + + var changed = {}; + + if (this._flagCenter) { + changed.cx = this.center._x; + changed.cy = this.center._y; + } + if (this._flagFocal) { + changed.fx = this.focal._x; + changed.fy = this.focal._y; + } + + if (this._flagRadius) { + changed.r = this._radius; + } + + if (this._flagSpread) { + changed.spreadMethod = this._spread; + } + + // If there is no attached DOM element yet, + // create it with all necessary attributes. + if (!this._renderer.elem) { + + changed.id = this.id; + changed.gradientUnits = 'userSpaceOnUse'; + this._renderer.elem = svg.createElement('radialGradient', changed); + domElement.defs.appendChild(this._renderer.elem); + + // Otherwise apply all pending attributes + } else { + + svg.setAttributes(this._renderer.elem, changed); + + } + + if (this._flagStops) { + + var lengthChanged = this._renderer.elem.childNodes.length + !== this.stops.length; + + if (lengthChanged) { + this._renderer.elem.childNodes.length = 0; + } + + for (var i = 0; i < this.stops.length; i++) { + + var stop = this.stops[i]; + var attrs = {}; + + if (stop._flagOffset) { + attrs.offset = 100 * stop._offset + '%'; + } + if (stop._flagColor) { + attrs['stop-color'] = stop._color; + } + if (stop._flagOpacity) { + attrs['stop-opacity'] = stop._opacity; + } + + if (!stop._renderer.elem) { + stop._renderer.elem = svg.createElement('stop', attrs); + } else { + svg.setAttributes(stop._renderer.elem, attrs); + } + + if (lengthChanged) { + this._renderer.elem.appendChild(stop._renderer.elem); + } + stop.flagReset(); + + } + + } + + return this.flagReset(); + + } + + }, + + texture: { + + render: function(domElement, silent) { + + if (!silent) { + this._update(); + } + + var changed = {}; + var styles = { x: 0, y: 0 }; + var image = this.image; + + if (this._flagLoaded && this.loaded) { + + switch (image.nodeName.toLowerCase()) { + + case 'canvas': + styles.href = styles['xlink:href'] = image.toDataURL('image/png'); + break; + case 'img': + case 'image': + styles.href = styles['xlink:href'] = this.src; + break; + + } + + } + + if (this._flagOffset || this._flagLoaded || this._flagScale) { + + changed.x = this._offset.x; + changed.y = this._offset.y; + + if (image) { + + changed.x -= image.width / 2; + changed.y -= image.height / 2; + + if (this._scale instanceof Two.Vector) { + changed.x *= this._scale.x; + changed.y *= this._scale.y; + } else { + changed.x *= this._scale; + changed.y *= this._scale; + } + } + + if (changed.x > 0) { + changed.x *= - 1; + } + if (changed.y > 0) { + changed.y *= - 1; + } + + } + + if (this._flagScale || this._flagLoaded || this._flagRepeat) { + + changed.width = 0; + changed.height = 0; + + if (image) { + + styles.width = changed.width = image.width; + styles.height = changed.height = image.height; + + // TODO: Hack / Bandaid + switch (this._repeat) { + case 'no-repeat': + changed.width += 1; + changed.height += 1; + break; + } + + if (this._scale instanceof Two.Vector) { + changed.width *= this._scale.x; + changed.height *= this._scale.y; + } else { + changed.width *= this._scale; + changed.height *= this._scale; + } + } + + } + + if (this._flagScale || this._flagLoaded) { + if (!this._renderer.image) { + this._renderer.image = svg.createElement('image', styles); + } else if (!_.isEmpty(styles)) { + svg.setAttributes(this._renderer.image, styles); + } + } + + if (!this._renderer.elem) { + + changed.id = this.id; + changed.patternUnits = 'userSpaceOnUse'; + this._renderer.elem = svg.createElement('pattern', changed); + domElement.defs.appendChild(this._renderer.elem); + + } else if (!_.isEmpty(changed)) { + + svg.setAttributes(this._renderer.elem, changed); + + } + + if (this._renderer.elem && this._renderer.image && !this._renderer.appended) { + this._renderer.elem.appendChild(this._renderer.image); + this._renderer.appended = true; + } + + return this.flagReset(); + + } + + } + + }; + + /** + * @class + */ + var Renderer = Two[Two.Types.svg] = function(params) { + + this.domElement = params.domElement || svg.createElement('svg'); + + this.scene = new Two.Group(); + this.scene.parent = this; + + this.defs = svg.createElement('defs'); + this.domElement.appendChild(this.defs); + this.domElement.defs = this.defs; + this.domElement.style.overflow = 'hidden'; + + }; + + _.extend(Renderer, { + + Utils: svg + + }); + + _.extend(Renderer.prototype, Two.Utils.Events, { + + setSize: function(width, height) { + + this.width = width; + this.height = height; + + svg.setAttributes(this.domElement, { + width: width, + height: height + }); + + return this; + + }, + + render: function() { + + svg.group.render.call(this.scene, this.domElement); + + return this; + + } + + }); + +})((typeof global !== 'undefined' ? global : this).Two); + +(function(Two) { + + /** + * Constants + */ + var mod = Two.Utils.mod, toFixed = Two.Utils.toFixed; + var getRatio = Two.Utils.getRatio; + var _ = Two.Utils; + + // Returns true if this is a non-transforming matrix + var isDefaultMatrix = function (m) { + return (m[0] == 1 && m[3] == 0 && m[1] == 0 && m[4] == 1 && m[2] == 0 && m[5] == 0); + }; + + var canvas = { + + isHidden: /(none|transparent)/i, + + alignments: { + left: 'start', + middle: 'center', + right: 'end' + }, + + shim: function(elem) { + elem.tagName = 'canvas'; + elem.nodeType = 1; + return elem; + }, + + group: { + + renderChild: function(child) { + canvas[child._renderer.type].render.call(child, this.ctx, true, this.clip); + }, + + render: function(ctx) { + + // TODO: Add a check here to only invoke _update if need be. + this._update(); + + var matrix = this._matrix.elements; + var parent = this.parent; + this._renderer.opacity = this._opacity * (parent && parent._renderer ? parent._renderer.opacity : 1); + + var defaultMatrix = isDefaultMatrix(matrix); + + var mask = this._mask; + // var clip = this._clip; + + if (!this._renderer.context) { + this._renderer.context = {}; + } + + this._renderer.context.ctx = ctx; + // this._renderer.context.clip = clip; + + if (!defaultMatrix) { + ctx.save(); + ctx.transform(matrix[0], matrix[3], matrix[1], matrix[4], matrix[2], matrix[5]); + } + + if (mask) { + canvas[mask._renderer.type].render.call(mask, ctx, true); + } + + if (this.opacity > 0 && this.scale !== 0) { + for (var i = 0; i < this.children.length; i++) { + var child = this.children[i]; + canvas[child._renderer.type].render.call(child, ctx); + } + } + + if (!defaultMatrix) { + ctx.restore(); + } + + /** + * Commented two-way functionality of clips / masks with groups and + * polygons. Uncomment when this bug is fixed: + * https://code.google.com/p/chromium/issues/detail?id=370951 + */ + + // if (clip) { + // ctx.clip(); + // } + + return this.flagReset(); + + } + + }, + + path: { + + render: function(ctx, forced, parentClipped) { + + var matrix, stroke, linewidth, fill, opacity, visible, cap, join, miter, + closed, commands, length, last, next, prev, a, b, c, d, ux, uy, vx, vy, + ar, bl, br, cl, x, y, mask, clip, defaultMatrix, isOffset; + + // TODO: Add a check here to only invoke _update if need be. + this._update(); + + matrix = this._matrix.elements; + stroke = this._stroke; + linewidth = this._linewidth; + fill = this._fill; + opacity = this._opacity * this.parent._renderer.opacity; + visible = this._visible; + cap = this._cap; + join = this._join; + miter = this._miter; + closed = this._closed; + commands = this._vertices; // Commands + length = commands.length; + last = length - 1; + defaultMatrix = isDefaultMatrix(matrix); + + // mask = this._mask; + clip = this._clip; + + if (!forced && (!visible || clip)) { + return this; + } + + // Transform + if (!defaultMatrix) { + ctx.save(); + ctx.transform(matrix[0], matrix[3], matrix[1], matrix[4], matrix[2], matrix[5]); + } + + /** + * Commented two-way functionality of clips / masks with groups and + * polygons. Uncomment when this bug is fixed: + * https://code.google.com/p/chromium/issues/detail?id=370951 + */ + + // if (mask) { + // canvas[mask._renderer.type].render.call(mask, ctx, true); + // } + + // Styles + if (fill) { + if (_.isString(fill)) { + ctx.fillStyle = fill; + } else { + canvas[fill._renderer.type].render.call(fill, ctx); + ctx.fillStyle = fill._renderer.effect; + } + } + if (stroke) { + if (_.isString(stroke)) { + ctx.strokeStyle = stroke; + } else { + canvas[stroke._renderer.type].render.call(stroke, ctx); + ctx.strokeStyle = stroke._renderer.effect; + } + } + if (linewidth) { + ctx.lineWidth = linewidth; + } + if (miter) { + ctx.miterLimit = miter; + } + if (join) { + ctx.lineJoin = join; + } + if (cap) { + ctx.lineCap = cap; + } + if (_.isNumber(opacity)) { + ctx.globalAlpha = opacity; + } + + ctx.beginPath(); + + for (var i = 0; i < commands.length; i++) { + + b = commands[i]; + + x = toFixed(b._x); + y = toFixed(b._y); + + switch (b._command) { + + case Two.Commands.close: + ctx.closePath(); + break; + + case Two.Commands.curve: + + prev = closed ? mod(i - 1, length) : Math.max(i - 1, 0); + next = closed ? mod(i + 1, length) : Math.min(i + 1, last); + + a = commands[prev]; + c = commands[next]; + ar = (a.controls && a.controls.right) || Two.Vector.zero; + bl = (b.controls && b.controls.left) || Two.Vector.zero; + + if (a._relative) { + vx = (ar.x + toFixed(a._x)); + vy = (ar.y + toFixed(a._y)); + } else { + vx = toFixed(ar.x); + vy = toFixed(ar.y); + } + + if (b._relative) { + ux = (bl.x + toFixed(b._x)); + uy = (bl.y + toFixed(b._y)); + } else { + ux = toFixed(bl.x); + uy = toFixed(bl.y); + } + + ctx.bezierCurveTo(vx, vy, ux, uy, x, y); + + if (i >= last && closed) { + + c = d; + + br = (b.controls && b.controls.right) || Two.Vector.zero; + cl = (c.controls && c.controls.left) || Two.Vector.zero; + + if (b._relative) { + vx = (br.x + toFixed(b._x)); + vy = (br.y + toFixed(b._y)); + } else { + vx = toFixed(br.x); + vy = toFixed(br.y); + } + + if (c._relative) { + ux = (cl.x + toFixed(c._x)); + uy = (cl.y + toFixed(c._y)); + } else { + ux = toFixed(cl.x); + uy = toFixed(cl.y); + } + + x = toFixed(c._x); + y = toFixed(c._y); + + ctx.bezierCurveTo(vx, vy, ux, uy, x, y); + + } + + break; + + case Two.Commands.line: + ctx.lineTo(x, y); + break; + + case Two.Commands.move: + d = b; + ctx.moveTo(x, y); + break; + + } + } + + // Loose ends + + if (closed) { + ctx.closePath(); + } + + if (!clip && !parentClipped) { + if (!canvas.isHidden.test(fill)) { + isOffset = fill._renderer && fill._renderer.offset + if (isOffset) { + ctx.save(); + ctx.translate( + - fill._renderer.offset.x, - fill._renderer.offset.y); + ctx.scale(fill._renderer.scale.x, fill._renderer.scale.y); + } + ctx.fill(); + if (isOffset) { + ctx.restore(); + } + } + if (!canvas.isHidden.test(stroke)) { + isOffset = stroke._renderer && stroke._renderer.offset; + if (isOffset) { + ctx.save(); + ctx.translate( + - stroke._renderer.offset.x, - stroke._renderer.offset.y); + ctx.scale(stroke._renderer.scale.x, stroke._renderer.scale.y); + ctx.lineWidth = linewidth / stroke._renderer.scale.x; + } + ctx.stroke(); + if (isOffset) { + ctx.restore(); + } + } + } + + if (!defaultMatrix) { + ctx.restore(); + } + + if (clip && !parentClipped) { + ctx.clip(); + } + + return this.flagReset(); + + } + + }, + + text: { + + render: function(ctx, forced, parentClipped) { + + // TODO: Add a check here to only invoke _update if need be. + this._update(); + + var matrix = this._matrix.elements; + var stroke = this._stroke; + var linewidth = this._linewidth; + var fill = this._fill; + var opacity = this._opacity * this.parent._renderer.opacity; + var visible = this._visible; + var defaultMatrix = isDefaultMatrix(matrix); + var isOffset = fill._renderer && fill._renderer.offset + && stroke._renderer && stroke._renderer.offset; + + var a, b, c, d, e, sx, sy; + + // mask = this._mask; + var clip = this._clip; + + if (!forced && (!visible || clip)) { + return this; + } + + // Transform + if (!defaultMatrix) { + ctx.save(); + ctx.transform(matrix[0], matrix[3], matrix[1], matrix[4], matrix[2], matrix[5]); + } + + /** + * Commented two-way functionality of clips / masks with groups and + * polygons. Uncomment when this bug is fixed: + * https://code.google.com/p/chromium/issues/detail?id=370951 + */ + + // if (mask) { + // canvas[mask._renderer.type].render.call(mask, ctx, true); + // } + + if (!isOffset) { + ctx.font = [this._style, this._weight, this._size + 'px/' + + this._leading + 'px', this._family].join(' '); + } + + ctx.textAlign = canvas.alignments[this._alignment] || this._alignment; + ctx.textBaseline = this._baseline; + + // Styles + if (fill) { + if (_.isString(fill)) { + ctx.fillStyle = fill; + } else { + canvas[fill._renderer.type].render.call(fill, ctx); + ctx.fillStyle = fill._renderer.effect; + } + } + if (stroke) { + if (_.isString(stroke)) { + ctx.strokeStyle = stroke; + } else { + canvas[stroke._renderer.type].render.call(stroke, ctx); + ctx.strokeStyle = stroke._renderer.effect; + } + } + if (linewidth) { + ctx.lineWidth = linewidth; + } + if (_.isNumber(opacity)) { + ctx.globalAlpha = opacity; + } + + if (!clip && !parentClipped) { + + if (!canvas.isHidden.test(fill)) { + + if (fill._renderer && fill._renderer.offset) { + + sx = toFixed(fill._renderer.scale.x); + sy = toFixed(fill._renderer.scale.y); + + ctx.save(); + ctx.translate( - toFixed(fill._renderer.offset.x), + - toFixed(fill._renderer.offset.y)); + ctx.scale(sx, sy); + + a = this._size / fill._renderer.scale.y; + b = this._leading / fill._renderer.scale.y; + ctx.font = [this._style, this._weight, toFixed(a) + 'px/', + toFixed(b) + 'px', this._family].join(' '); + + c = fill._renderer.offset.x / fill._renderer.scale.x; + d = fill._renderer.offset.y / fill._renderer.scale.y; + + ctx.fillText(this.value, toFixed(c), toFixed(d)); + ctx.restore(); + + } else { + ctx.fillText(this.value, 0, 0); + } + + } + + if (!canvas.isHidden.test(stroke)) { + + if (stroke._renderer && stroke._renderer.offset) { + + sx = toFixed(stroke._renderer.scale.x); + sy = toFixed(stroke._renderer.scale.y); + + ctx.save(); + ctx.translate(- toFixed(stroke._renderer.offset.x), + - toFixed(stroke._renderer.offset.y)); + ctx.scale(sx, sy); + + a = this._size / stroke._renderer.scale.y; + b = this._leading / stroke._renderer.scale.y; + ctx.font = [this._style, this._weight, toFixed(a) + 'px/', + toFixed(b) + 'px', this._family].join(' '); + + c = stroke._renderer.offset.x / stroke._renderer.scale.x; + d = stroke._renderer.offset.y / stroke._renderer.scale.y; + e = linewidth / stroke._renderer.scale.x; + + ctx.lineWidth = toFixed(e); + ctx.strokeText(this.value, toFixed(c), toFixed(d)); + ctx.restore(); + + } else { + ctx.strokeText(this.value, 0, 0); + } + } + } + + if (!defaultMatrix) { + ctx.restore(); + } + + // TODO: Test for text + if (clip && !parentClipped) { + ctx.clip(); + } + + return this.flagReset(); + + } + + }, + + 'linear-gradient': { + + render: function(ctx) { + + this._update(); + + if (!this._renderer.effect || this._flagEndPoints || this._flagStops) { + + this._renderer.effect = ctx.createLinearGradient( + this.left._x, this.left._y, + this.right._x, this.right._y + ); + + for (var i = 0; i < this.stops.length; i++) { + var stop = this.stops[i]; + this._renderer.effect.addColorStop(stop._offset, stop._color); + } + + } + + return this.flagReset(); + + } + + }, + + 'radial-gradient': { + + render: function(ctx) { + + this._update(); + + if (!this._renderer.effect || this._flagCenter || this._flagFocal + || this._flagRadius || this._flagStops) { + + this._renderer.effect = ctx.createRadialGradient( + this.center._x, this.center._y, 0, + this.focal._x, this.focal._y, this._radius + ); + + for (var i = 0; i < this.stops.length; i++) { + var stop = this.stops[i]; + this._renderer.effect.addColorStop(stop._offset, stop._color); + } + + } + + return this.flagReset(); + + } + + }, + + texture: { + + render: function(ctx) { + + this._update(); + + var image = this.image; + var repeat; + + if (!this._renderer.effect || ((this._flagLoaded || this._flagImage || this._flagVideo || this._flagRepeat) && this.loaded)) { + this._renderer.effect = ctx.createPattern(this.image, this._repeat); + } + + if (this._flagOffset || this._flagLoaded || this._flagScale) { + + if (!(this._renderer.offset instanceof Two.Vector)) { + this._renderer.offset = new Two.Vector(); + } + + this._renderer.offset.x = - this._offset.x; + this._renderer.offset.y = - this._offset.y; + + if (image) { + + this._renderer.offset.x += image.width / 2; + this._renderer.offset.y += image.height / 2; + + if (this._scale instanceof Two.Vector) { + this._renderer.offset.x *= this._scale.x; + this._renderer.offset.y *= this._scale.y; + } else { + this._renderer.offset.x *= this._scale; + this._renderer.offset.y *= this._scale; + } + } + + } + + if (this._flagScale || this._flagLoaded) { + + if (!(this._renderer.scale instanceof Two.Vector)) { + this._renderer.scale = new Two.Vector(); + } + + if (this._scale instanceof Two.Vector) { + this._renderer.scale.copy(this._scale); + } else { + this._renderer.scale.set(this._scale, this._scale); + } + + } + + return this.flagReset(); + + } + + } + + }; + + var Renderer = Two[Two.Types.canvas] = function(params) { + // Smoothing property. Defaults to true + // Set it to false when working with pixel art. + // false can lead to better performance, since it would use a cheaper interpolation algorithm. + // It might not make a big difference on GPU backed canvases. + var smoothing = (params.smoothing !== false); + this.domElement = params.domElement || document.createElement('canvas'); + this.ctx = this.domElement.getContext('2d'); + this.overdraw = params.overdraw || false; + + if (!_.isUndefined(this.ctx.imageSmoothingEnabled)) { + this.ctx.imageSmoothingEnabled = smoothing; + } + + // Everything drawn on the canvas needs to be added to the scene. + this.scene = new Two.Group(); + this.scene.parent = this; + }; + + + _.extend(Renderer, { + + Utils: canvas + + }); + + _.extend(Renderer.prototype, Two.Utils.Events, { + + setSize: function(width, height, ratio) { + + this.width = width; + this.height = height; + + this.ratio = _.isUndefined(ratio) ? getRatio(this.ctx) : ratio; + + this.domElement.width = width * this.ratio; + this.domElement.height = height * this.ratio; + + if (this.domElement.style) { + _.extend(this.domElement.style, { + width: width + 'px', + height: height + 'px' + }); + } + + return this; + + }, + + render: function() { + + var isOne = this.ratio === 1; + + if (!isOne) { + this.ctx.save(); + this.ctx.scale(this.ratio, this.ratio); + } + + if (!this.overdraw) { + this.ctx.clearRect(0, 0, this.width, this.height); + } + + canvas.group.render.call(this.scene, this.ctx); + + if (!isOne) { + this.ctx.restore(); + } + + return this; + + } + + }); + + function resetTransform(ctx) { + ctx.setTransform(1, 0, 0, 1, 0, 0); + } + +})((typeof global !== 'undefined' ? global : this).Two); + +(function(Two) { + + /** + * Constants + */ + + var root = Two.root, + multiplyMatrix = Two.Matrix.Multiply, + mod = Two.Utils.mod, + identity = [1, 0, 0, 0, 1, 0, 0, 0, 1], + transformation = new Two.Array(9), + getRatio = Two.Utils.getRatio, + getComputedMatrix = Two.Utils.getComputedMatrix, + toFixed = Two.Utils.toFixed, + _ = Two.Utils; + + var webgl = { + + isHidden: /(none|transparent)/i, + + canvas: (root.document ? root.document.createElement('canvas') : { getContext: _.identity }), + + alignments: { + left: 'start', + middle: 'center', + right: 'end' + }, + + matrix: new Two.Matrix(), + + uv: new Two.Array([ + 0, 0, + 1, 0, + 0, 1, + 0, 1, + 1, 0, + 1, 1 + ]), + + group: { + + removeChild: function(child, gl) { + if (child.children) { + for (var i = 0; i < child.children.length; i++) { + webgl.group.removeChild(child.children[i], gl); + } + return; + } + // Deallocate texture to free up gl memory. + gl.deleteTexture(child._renderer.texture); + delete child._renderer.texture; + }, + + renderChild: function(child) { + webgl[child._renderer.type].render.call(child, this.gl, this.program); + }, + + render: function(gl, program) { + + this._update(); + + var parent = this.parent; + var flagParentMatrix = (parent._matrix && parent._matrix.manual) || parent._flagMatrix; + var flagMatrix = this._matrix.manual || this._flagMatrix; + + if (flagParentMatrix || flagMatrix) { + + if (!this._renderer.matrix) { + this._renderer.matrix = new Two.Array(9); + } + + // Reduce amount of object / array creation / deletion + this._matrix.toArray(true, transformation); + + multiplyMatrix(transformation, parent._renderer.matrix, this._renderer.matrix); + this._renderer.scale = this._scale * parent._renderer.scale; + + if (flagParentMatrix) { + this._flagMatrix = true; + } + + } + + if (this._mask) { + + gl.enable(gl.STENCIL_TEST); + gl.stencilFunc(gl.ALWAYS, 1, 1); + + gl.colorMask(false, false, false, true); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); + + webgl[this._mask._renderer.type].render.call(this._mask, gl, program, this); + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.NOTEQUAL, 0, 1); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); + + } + + this._flagOpacity = parent._flagOpacity || this._flagOpacity; + + this._renderer.opacity = this._opacity + * (parent && parent._renderer ? parent._renderer.opacity : 1); + + if (this._flagSubtractions) { + for (var i = 0; i < this.subtractions.length; i++) { + webgl.group.removeChild(this.subtractions[i], gl); + } + } + + this.children.forEach(webgl.group.renderChild, { + gl: gl, + program: program + }); + + if (this._mask) { + + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR); + + webgl[this._mask._renderer.type].render.call(this._mask, gl, program, this); + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.NOTEQUAL, 0, 1); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); + + gl.disable(gl.STENCIL_TEST); + + } + + return this.flagReset(); + + } + + }, + + path: { + + updateCanvas: function(elem) { + + var next, prev, a, c, ux, uy, vx, vy, ar, bl, br, cl, x, y; + var isOffset; + + var commands = elem._vertices; + var canvas = this.canvas; + var ctx = this.ctx; + + // Styles + var scale = elem._renderer.scale; + var stroke = elem._stroke; + var linewidth = elem._linewidth; + var fill = elem._fill; + var opacity = elem._renderer.opacity || elem._opacity; + var cap = elem._cap; + var join = elem._join; + var miter = elem._miter; + var closed = elem._closed; + var length = commands.length; + var last = length - 1; + + canvas.width = Math.max(Math.ceil(elem._renderer.rect.width * scale), 1); + canvas.height = Math.max(Math.ceil(elem._renderer.rect.height * scale), 1); + + var centroid = elem._renderer.rect.centroid; + var cx = centroid.x; + var cy = centroid.y; + + ctx.clearRect(0, 0, canvas.width, canvas.height); + + if (fill) { + if (_.isString(fill)) { + ctx.fillStyle = fill; + } else { + webgl[fill._renderer.type].render.call(fill, ctx, elem); + ctx.fillStyle = fill._renderer.effect; + } + } + if (stroke) { + if (_.isString(stroke)) { + ctx.strokeStyle = stroke; + } else { + webgl[stroke._renderer.type].render.call(stroke, ctx, elem); + ctx.strokeStyle = stroke._renderer.effect; + } + } + if (linewidth) { + ctx.lineWidth = linewidth; + } + if (miter) { + ctx.miterLimit = miter; + } + if (join) { + ctx.lineJoin = join; + } + if (cap) { + ctx.lineCap = cap; + } + if (_.isNumber(opacity)) { + ctx.globalAlpha = opacity; + } + + var d; + ctx.save(); + ctx.scale(scale, scale); + ctx.translate(cx, cy); + + ctx.beginPath(); + for (var i = 0; i < commands.length; i++) { + + b = commands[i]; + + x = toFixed(b._x); + y = toFixed(b._y); + + switch (b._command) { + + case Two.Commands.close: + ctx.closePath(); + break; + + case Two.Commands.curve: + + prev = closed ? mod(i - 1, length) : Math.max(i - 1, 0); + next = closed ? mod(i + 1, length) : Math.min(i + 1, last); + + a = commands[prev]; + c = commands[next]; + ar = (a.controls && a.controls.right) || Two.Vector.zero; + bl = (b.controls && b.controls.left) || Two.Vector.zero; + + if (a._relative) { + vx = toFixed((ar.x + a._x)); + vy = toFixed((ar.y + a._y)); + } else { + vx = toFixed(ar.x); + vy = toFixed(ar.y); + } + + if (b._relative) { + ux = toFixed((bl.x + b._x)); + uy = toFixed((bl.y + b._y)); + } else { + ux = toFixed(bl.x); + uy = toFixed(bl.y); + } + + ctx.bezierCurveTo(vx, vy, ux, uy, x, y); + + if (i >= last && closed) { + + c = d; + + br = (b.controls && b.controls.right) || Two.Vector.zero; + cl = (c.controls && c.controls.left) || Two.Vector.zero; + + if (b._relative) { + vx = toFixed((br.x + b._x)); + vy = toFixed((br.y + b._y)); + } else { + vx = toFixed(br.x); + vy = toFixed(br.y); + } + + if (c._relative) { + ux = toFixed((cl.x + c._x)); + uy = toFixed((cl.y + c._y)); + } else { + ux = toFixed(cl.x); + uy = toFixed(cl.y); + } + + x = toFixed(c._x); + y = toFixed(c._y); + + ctx.bezierCurveTo(vx, vy, ux, uy, x, y); + + } + + break; + + case Two.Commands.line: + ctx.lineTo(x, y); + break; + + case Two.Commands.move: + d = b; + ctx.moveTo(x, y); + break; + + } + + } + + // Loose ends + + if (closed) { + ctx.closePath(); + } + + if (!webgl.isHidden.test(fill)) { + isOffset = fill._renderer && fill._renderer.offset + if (isOffset) { + ctx.save(); + ctx.translate( + - fill._renderer.offset.x, - fill._renderer.offset.y); + ctx.scale(fill._renderer.scale.x, fill._renderer.scale.y); + } + ctx.fill(); + if (isOffset) { + ctx.restore(); + } + } + + if (!webgl.isHidden.test(stroke)) { + isOffset = stroke._renderer && stroke._renderer.offset; + if (isOffset) { + ctx.save(); + ctx.translate( + - stroke._renderer.offset.x, - stroke._renderer.offset.y); + ctx.scale(stroke._renderer.scale.x, stroke._renderer.scale.y); + ctx.lineWidth = linewidth / stroke._renderer.scale.x; + } + ctx.stroke(); + if (isOffset) { + ctx.restore(); + } + } + + ctx.restore(); + + }, + + /** + * Returns the rect of a set of verts. Typically takes vertices that are + * "centered" around 0 and returns them to be anchored upper-left. + */ + getBoundingClientRect: function(vertices, border, rect) { + + var left = Infinity, right = -Infinity, + top = Infinity, bottom = -Infinity, + width, height; + + vertices.forEach(function(v) { + + var x = v.x, y = v.y, controls = v.controls; + var a, b, c, d, cl, cr; + + top = Math.min(y, top); + left = Math.min(x, left); + right = Math.max(x, right); + bottom = Math.max(y, bottom); + + if (!v.controls) { + return; + } + + cl = controls.left; + cr = controls.right; + + if (!cl || !cr) { + return; + } + + a = v._relative ? cl.x + x : cl.x; + b = v._relative ? cl.y + y : cl.y; + c = v._relative ? cr.x + x : cr.x; + d = v._relative ? cr.y + y : cr.y; + + if (!a || !b || !c || !d) { + return; + } + + top = Math.min(b, d, top); + left = Math.min(a, c, left); + right = Math.max(a, c, right); + bottom = Math.max(b, d, bottom); + + }); + + // Expand borders + + if (_.isNumber(border)) { + top -= border; + left -= border; + right += border; + bottom += border; + } + + width = right - left; + height = bottom - top; + + rect.top = top; + rect.left = left; + rect.right = right; + rect.bottom = bottom; + rect.width = width; + rect.height = height; + + if (!rect.centroid) { + rect.centroid = {}; + } + + rect.centroid.x = - left; + rect.centroid.y = - top; + + }, + + render: function(gl, program, forcedParent) { + + if (!this._visible || !this._opacity) { + return this; + } + + this._update(); + + // Calculate what changed + + var parent = this.parent; + var flagParentMatrix = parent._matrix.manual || parent._flagMatrix; + var flagMatrix = this._matrix.manual || this._flagMatrix; + var flagTexture = this._flagVertices || this._flagFill + || (this._fill instanceof Two.LinearGradient && (this._fill._flagSpread || this._fill._flagStops || this._fill._flagEndPoints)) + || (this._fill instanceof Two.RadialGradient && (this._fill._flagSpread || this._fill._flagStops || this._fill._flagRadius || this._fill._flagCenter || this._fill._flagFocal)) + || (this._fill instanceof Two.Texture && (this._fill._flagLoaded && this._fill.loaded || this._fill._flagOffset || this._fill._flagScale)) + || (this._stroke instanceof Two.LinearGradient && (this._stroke._flagSpread || this._stroke._flagStops || this._stroke._flagEndPoints)) + || (this._stroke instanceof Two.RadialGradient && (this._stroke._flagSpread || this._stroke._flagStops || this._stroke._flagRadius || this._stroke._flagCenter || this._stroke._flagFocal)) + || (this._stroke instanceof Two.Texture && (this._stroke._flagLoaded && this._stroke.loaded || this._stroke._flagOffset || this._fill._flagScale)) + || this._flagStroke || this._flagLinewidth || this._flagOpacity + || parent._flagOpacity || this._flagVisible || this._flagCap + || this._flagJoin || this._flagMiter || this._flagScale + || !this._renderer.texture; + + if (flagParentMatrix || flagMatrix) { + + if (!this._renderer.matrix) { + this._renderer.matrix = new Two.Array(9); + } + + // Reduce amount of object / array creation / deletion + + this._matrix.toArray(true, transformation); + + multiplyMatrix(transformation, parent._renderer.matrix, this._renderer.matrix); + this._renderer.scale = this._scale * parent._renderer.scale; + + } + + if (flagTexture) { + + if (!this._renderer.rect) { + this._renderer.rect = {}; + } + + if (!this._renderer.triangles) { + this._renderer.triangles = new Two.Array(12); + } + + this._renderer.opacity = this._opacity * parent._renderer.opacity; + + webgl.path.getBoundingClientRect(this._vertices, this._linewidth, this._renderer.rect); + webgl.getTriangles(this._renderer.rect, this._renderer.triangles); + + webgl.updateBuffer.call(webgl, gl, this, program); + webgl.updateTexture.call(webgl, gl, this); + + } + + // if (this._mask) { + // webgl[this._mask._renderer.type].render.call(mask, gl, program, this); + // } + + if (this._clip && !forcedParent) { + return; + } + + // Draw Texture + + gl.bindBuffer(gl.ARRAY_BUFFER, this._renderer.textureCoordsBuffer); + + gl.vertexAttribPointer(program.textureCoords, 2, gl.FLOAT, false, 0, 0); + + gl.bindTexture(gl.TEXTURE_2D, this._renderer.texture); + + + // Draw Rect + + gl.uniformMatrix3fv(program.matrix, false, this._renderer.matrix); + + gl.bindBuffer(gl.ARRAY_BUFFER, this._renderer.buffer); + + gl.vertexAttribPointer(program.position, 2, gl.FLOAT, false, 0, 0); + + gl.drawArrays(gl.TRIANGLES, 0, 6); + + return this.flagReset(); + + } + + }, + + text: { + + updateCanvas: function(elem) { + + var canvas = this.canvas; + var ctx = this.ctx; + + // Styles + var scale = elem._renderer.scale; + var stroke = elem._stroke; + var linewidth = elem._linewidth * scale; + var fill = elem._fill; + var opacity = elem._renderer.opacity || elem._opacity; + + canvas.width = Math.max(Math.ceil(elem._renderer.rect.width * scale), 1); + canvas.height = Math.max(Math.ceil(elem._renderer.rect.height * scale), 1); + + var centroid = elem._renderer.rect.centroid; + var cx = centroid.x; + var cy = centroid.y; + + var a, b, c, d, e, sx, sy; + var isOffset = fill._renderer && fill._renderer.offset + && stroke._renderer && stroke._renderer.offset; + + ctx.clearRect(0, 0, canvas.width, canvas.height); + + if (!isOffset) { + ctx.font = [elem._style, elem._weight, elem._size + 'px/' + + elem._leading + 'px', elem._family].join(' '); + } + + ctx.textAlign = 'center'; + ctx.textBaseline = 'middle'; + + // Styles + if (fill) { + if (_.isString(fill)) { + ctx.fillStyle = fill; + } else { + webgl[fill._renderer.type].render.call(fill, ctx, elem); + ctx.fillStyle = fill._renderer.effect; + } + } + if (stroke) { + if (_.isString(stroke)) { + ctx.strokeStyle = stroke; + } else { + webgl[stroke._renderer.type].render.call(stroke, ctx, elem); + ctx.strokeStyle = stroke._renderer.effect; + } + } + if (linewidth) { + ctx.lineWidth = linewidth; + } + if (_.isNumber(opacity)) { + ctx.globalAlpha = opacity; + } + + ctx.save(); + ctx.scale(scale, scale); + ctx.translate(cx, cy); + + if (!webgl.isHidden.test(fill)) { + + if (fill._renderer && fill._renderer.offset) { + + sx = toFixed(fill._renderer.scale.x); + sy = toFixed(fill._renderer.scale.y); + + ctx.save(); + ctx.translate( - toFixed(fill._renderer.offset.x), + - toFixed(fill._renderer.offset.y)); + ctx.scale(sx, sy); + + a = elem._size / fill._renderer.scale.y; + b = elem._leading / fill._renderer.scale.y; + ctx.font = [elem._style, elem._weight, toFixed(a) + 'px/', + toFixed(b) + 'px', elem._family].join(' '); + + c = fill._renderer.offset.x / fill._renderer.scale.x; + d = fill._renderer.offset.y / fill._renderer.scale.y; + + ctx.fillText(elem.value, toFixed(c), toFixed(d)); + ctx.restore(); + + } else { + ctx.fillText(elem.value, 0, 0); + } + + } + + if (!webgl.isHidden.test(stroke)) { + + if (stroke._renderer && stroke._renderer.offset) { + + sx = toFixed(stroke._renderer.scale.x); + sy = toFixed(stroke._renderer.scale.y); + + ctx.save(); + ctx.translate(- toFixed(stroke._renderer.offset.x), + - toFixed(stroke._renderer.offset.y)); + ctx.scale(sx, sy); + + a = elem._size / stroke._renderer.scale.y; + b = elem._leading / stroke._renderer.scale.y; + ctx.font = [elem._style, elem._weight, toFixed(a) + 'px/', + toFixed(b) + 'px', elem._family].join(' '); + + c = stroke._renderer.offset.x / stroke._renderer.scale.x; + d = stroke._renderer.offset.y / stroke._renderer.scale.y; + e = linewidth / stroke._renderer.scale.x; + + ctx.lineWidth = toFixed(e); + ctx.strokeText(elem.value, toFixed(c), toFixed(d)); + ctx.restore(); + + } else { + ctx.strokeText(elem.value, 0, 0); + } + + } + + ctx.restore(); + + }, + + getBoundingClientRect: function(elem, rect) { + + var ctx = webgl.ctx; + + ctx.font = [elem._style, elem._weight, elem._size + 'px/' + + elem._leading + 'px', elem._family].join(' '); + + ctx.textAlign = 'center'; + ctx.textBaseline = elem._baseline; + + // TODO: Estimate this better + var width = ctx.measureText(elem._value).width; + var height = Math.max(elem._size || elem._leading); + + if (this._linewidth && !webgl.isHidden.test(this._stroke)) { + // width += this._linewidth; // TODO: Not sure if the `measure` calcs this. + height += this._linewidth; + } + + var w = width / 2; + var h = height / 2; + + switch (webgl.alignments[elem._alignment] || elem._alignment) { + + case webgl.alignments.left: + rect.left = 0; + rect.right = width; + break; + case webgl.alignments.right: + rect.left = - width; + rect.right = 0; + break; + default: + rect.left = - w; + rect.right = w; + } + + // TODO: Gradients aren't inherited... + switch (elem._baseline) { + case 'bottom': + rect.top = - height; + rect.bottom = 0; + break; + case 'top': + rect.top = 0; + rect.bottom = height; + break; + default: + rect.top = - h; + rect.bottom = h; + } + + rect.width = width; + rect.height = height; + + if (!rect.centroid) { + rect.centroid = {}; + } + + // TODO: + rect.centroid.x = w; + rect.centroid.y = h; + + }, + + render: function(gl, program, forcedParent) { + + if (!this._visible || !this._opacity) { + return this; + } + + this._update(); + + // Calculate what changed + + var parent = this.parent; + var flagParentMatrix = parent._matrix.manual || parent._flagMatrix; + var flagMatrix = this._matrix.manual || this._flagMatrix; + var flagTexture = this._flagVertices || this._flagFill + || (this._fill instanceof Two.LinearGradient && (this._fill._flagSpread || this._fill._flagStops || this._fill._flagEndPoints)) + || (this._fill instanceof Two.RadialGradient && (this._fill._flagSpread || this._fill._flagStops || this._fill._flagRadius || this._fill._flagCenter || this._fill._flagFocal)) + || (this._fill instanceof Two.Texture && (this._fill._flagLoaded && this._fill.loaded)) + || (this._stroke instanceof Two.LinearGradient && (this._stroke._flagSpread || this._stroke._flagStops || this._stroke._flagEndPoints)) + || (this._stroke instanceof Two.RadialGradient && (this._stroke._flagSpread || this._stroke._flagStops || this._stroke._flagRadius || this._stroke._flagCenter || this._stroke._flagFocal)) + || (this._texture instanceof Two.Texture && (this._texture._flagLoaded && this._texture.loaded)) + || this._flagStroke || this._flagLinewidth || this._flagOpacity + || parent._flagOpacity || this._flagVisible || this._flagScale + || this._flagValue || this._flagFamily || this._flagSize + || this._flagLeading || this._flagAlignment || this._flagBaseline + || this._flagStyle || this._flagWeight || this._flagDecoration + || !this._renderer.texture; + + if (flagParentMatrix || flagMatrix) { + + if (!this._renderer.matrix) { + this._renderer.matrix = new Two.Array(9); + } + + // Reduce amount of object / array creation / deletion + + this._matrix.toArray(true, transformation); + + multiplyMatrix(transformation, parent._renderer.matrix, this._renderer.matrix); + this._renderer.scale = this._scale * parent._renderer.scale; + + } + + if (flagTexture) { + + if (!this._renderer.rect) { + this._renderer.rect = {}; + } + + if (!this._renderer.triangles) { + this._renderer.triangles = new Two.Array(12); + } + + this._renderer.opacity = this._opacity * parent._renderer.opacity; + + webgl.text.getBoundingClientRect(this, this._renderer.rect); + webgl.getTriangles(this._renderer.rect, this._renderer.triangles); + + webgl.updateBuffer.call(webgl, gl, this, program); + webgl.updateTexture.call(webgl, gl, this); + + } + + // if (this._mask) { + // webgl[this._mask._renderer.type].render.call(mask, gl, program, this); + // } + + if (this._clip && !forcedParent) { + return; + } + + // Draw Texture + + gl.bindBuffer(gl.ARRAY_BUFFER, this._renderer.textureCoordsBuffer); + + gl.vertexAttribPointer(program.textureCoords, 2, gl.FLOAT, false, 0, 0); + + gl.bindTexture(gl.TEXTURE_2D, this._renderer.texture); + + + // Draw Rect + + gl.uniformMatrix3fv(program.matrix, false, this._renderer.matrix); + + gl.bindBuffer(gl.ARRAY_BUFFER, this._renderer.buffer); + + gl.vertexAttribPointer(program.position, 2, gl.FLOAT, false, 0, 0); + + gl.drawArrays(gl.TRIANGLES, 0, 6); + + return this.flagReset(); + + } + + }, + + 'linear-gradient': { + + render: function(ctx, elem) { + + if (!ctx.canvas.getContext('2d')) { + return; + } + + this._update(); + + if (!this._renderer.effect || this._flagEndPoints || this._flagStops) { + + this._renderer.effect = ctx.createLinearGradient( + this.left._x, this.left._y, + this.right._x, this.right._y + ); + + for (var i = 0; i < this.stops.length; i++) { + var stop = this.stops[i]; + this._renderer.effect.addColorStop(stop._offset, stop._color); + } + + } + + return this.flagReset(); + + } + + }, + + 'radial-gradient': { + + render: function(ctx, elem) { + + if (!ctx.canvas.getContext('2d')) { + return; + } + + this._update(); + + if (!this._renderer.effect || this._flagCenter || this._flagFocal + || this._flagRadius || this._flagStops) { + + this._renderer.effect = ctx.createRadialGradient( + this.center._x, this.center._y, 0, + this.focal._x, this.focal._y, this._radius + ); + + for (var i = 0; i < this.stops.length; i++) { + var stop = this.stops[i]; + this._renderer.effect.addColorStop(stop._offset, stop._color); + } + + } + + return this.flagReset(); + + } + + }, + + texture: { + + render: function(ctx, elem) { + + if (!ctx.canvas.getContext('2d')) { + return; + } + + this._update(); + + var image = this.image; + var repeat; + + if (!this._renderer.effect || ((this._flagLoaded || this._flagRepeat) && this.loaded)) { + this._renderer.effect = ctx.createPattern(image, this._repeat); + } + + if (this._flagOffset || this._flagLoaded || this._flagScale) { + + if (!(this._renderer.offset instanceof Two.Vector)) { + this._renderer.offset = new Two.Vector(); + } + + this._renderer.offset.x = this._offset.x; + this._renderer.offset.y = this._offset.y; + + if (image) { + + this._renderer.offset.x -= image.width / 2; + this._renderer.offset.y += image.height / 2; + + if (this._scale instanceof Two.Vector) { + this._renderer.offset.x *= this._scale.x; + this._renderer.offset.y *= this._scale.y; + } else { + this._renderer.offset.x *= this._scale; + this._renderer.offset.y *= this._scale; + } + } + + } + + if (this._flagScale || this._flagLoaded) { + + if (!(this._renderer.scale instanceof Two.Vector)) { + this._renderer.scale = new Two.Vector(); + } + + if (this._scale instanceof Two.Vector) { + this._renderer.scale.copy(this._scale); + } else { + this._renderer.scale.set(this._scale, this._scale); + } + + } + + return this.flagReset(); + + } + + }, + + getTriangles: function(rect, triangles) { + + var top = rect.top, + left = rect.left, + right = rect.right, + bottom = rect.bottom; + + // First Triangle + + triangles[0] = left; + triangles[1] = top; + + triangles[2] = right; + triangles[3] = top; + + triangles[4] = left; + triangles[5] = bottom; + + // Second Triangle + + triangles[6] = left; + triangles[7] = bottom; + + triangles[8] = right; + triangles[9] = top; + + triangles[10] = right; + triangles[11] = bottom; + + }, + + updateTexture: function(gl, elem) { + + this[elem._renderer.type].updateCanvas.call(webgl, elem); + + if (elem._renderer.texture) { + gl.deleteTexture(elem._renderer.texture); + } + + gl.bindBuffer(gl.ARRAY_BUFFER, elem._renderer.textureCoordsBuffer); + + // TODO: Is this necessary every time or can we do once? + // TODO: Create a registry for textures + elem._renderer.texture = gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, elem._renderer.texture); + + // Set the parameters so we can render any size image. + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); + // gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); + // gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); + // gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); + + if (this.canvas.width <= 0 || this.canvas.height <= 0) { + return; + } + + // Upload the image into the texture. + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, this.canvas); + + }, + + updateBuffer: function(gl, elem, program) { + + if (_.isObject(elem._renderer.buffer)) { + gl.deleteBuffer(elem._renderer.buffer); + } + + elem._renderer.buffer = gl.createBuffer(); + + gl.bindBuffer(gl.ARRAY_BUFFER, elem._renderer.buffer); + gl.enableVertexAttribArray(program.position); + + gl.bufferData(gl.ARRAY_BUFFER, elem._renderer.triangles, gl.STATIC_DRAW); + + if (_.isObject(elem._renderer.textureCoordsBuffer)) { + gl.deleteBuffer(elem._renderer.textureCoordsBuffer); + } + + elem._renderer.textureCoordsBuffer = gl.createBuffer(); + + gl.bindBuffer(gl.ARRAY_BUFFER, elem._renderer.textureCoordsBuffer); + gl.enableVertexAttribArray(program.textureCoords); + + gl.bufferData(gl.ARRAY_BUFFER, this.uv, gl.STATIC_DRAW); + + }, + + program: { + + create: function(gl, shaders) { + var program, linked, error; + program = gl.createProgram(); + _.each(shaders, function(s) { + gl.attachShader(program, s); + }); + + gl.linkProgram(program); + linked = gl.getProgramParameter(program, gl.LINK_STATUS); + if (!linked) { + error = gl.getProgramInfoLog(program); + gl.deleteProgram(program); + throw new Two.Utils.Error('unable to link program: ' + error); + } + + return program; + + } + + }, + + shaders: { + + create: function(gl, source, type) { + var shader, compiled, error; + shader = gl.createShader(gl[type]); + gl.shaderSource(shader, source); + gl.compileShader(shader); + + compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS); + if (!compiled) { + error = gl.getShaderInfoLog(shader); + gl.deleteShader(shader); + throw new Two.Utils.Error('unable to compile shader ' + shader + ': ' + error); + } + + return shader; + + }, + + types: { + vertex: 'VERTEX_SHADER', + fragment: 'FRAGMENT_SHADER' + }, + + vertex: [ + 'attribute vec2 a_position;', + 'attribute vec2 a_textureCoords;', + '', + 'uniform mat3 u_matrix;', + 'uniform vec2 u_resolution;', + '', + 'varying vec2 v_textureCoords;', + '', + 'void main() {', + ' vec2 projected = (u_matrix * vec3(a_position, 1.0)).xy;', + ' vec2 normal = projected / u_resolution;', + ' vec2 clipspace = (normal * 2.0) - 1.0;', + '', + ' gl_Position = vec4(clipspace * vec2(1.0, -1.0), 0.0, 1.0);', + ' v_textureCoords = a_textureCoords;', + '}' + ].join('\n'), + + fragment: [ + 'precision mediump float;', + '', + 'uniform sampler2D u_image;', + 'varying vec2 v_textureCoords;', + '', + 'void main() {', + ' gl_FragColor = texture2D(u_image, v_textureCoords);', + '}' + ].join('\n') + + }, + + TextureRegistry: new Two.Registry() + + }; + + webgl.ctx = webgl.canvas.getContext('2d'); + + var Renderer = Two[Two.Types.webgl] = function(options) { + + var params, gl, vs, fs; + this.domElement = options.domElement || document.createElement('canvas'); + + // Everything drawn on the canvas needs to come from the stage. + this.scene = new Two.Group(); + this.scene.parent = this; + + this._renderer = { + matrix: new Two.Array(identity), + scale: 1, + opacity: 1 + }; + this._flagMatrix = true; + + // http://games.greggman.com/game/webgl-and-alpha/ + // http://www.khronos.org/registry/webgl/specs/latest/#5.2 + params = _.defaults(options || {}, { + antialias: false, + alpha: true, + premultipliedAlpha: true, + stencil: true, + preserveDrawingBuffer: true, + overdraw: false + }); + + this.overdraw = params.overdraw; + + gl = this.ctx = this.domElement.getContext('webgl', params) || + this.domElement.getContext('experimental-webgl', params); + + if (!this.ctx) { + throw new Two.Utils.Error( + 'unable to create a webgl context. Try using another renderer.'); + } + + // Compile Base Shaders to draw in pixel space. + vs = webgl.shaders.create( + gl, webgl.shaders.vertex, webgl.shaders.types.vertex); + fs = webgl.shaders.create( + gl, webgl.shaders.fragment, webgl.shaders.types.fragment); + + this.program = webgl.program.create(gl, [vs, fs]); + gl.useProgram(this.program); + + // Create and bind the drawing buffer + + // look up where the vertex data needs to go. + this.program.position = gl.getAttribLocation(this.program, 'a_position'); + this.program.matrix = gl.getUniformLocation(this.program, 'u_matrix'); + this.program.textureCoords = gl.getAttribLocation(this.program, 'a_textureCoords'); + + // Copied from Three.js WebGLRenderer + gl.disable(gl.DEPTH_TEST); + + // Setup some initial statements of the gl context + gl.enable(gl.BLEND); + + // https://code.google.com/p/chromium/issues/detail?id=316393 + // gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, gl.TRUE); + + gl.blendEquationSeparate(gl.FUNC_ADD, gl.FUNC_ADD); + gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, + gl.ONE, gl.ONE_MINUS_SRC_ALPHA ); + + }; + + _.extend(Renderer, { + + Utils: webgl + + }); + + _.extend(Renderer.prototype, Two.Utils.Events, { + + setSize: function(width, height, ratio) { + + this.width = width; + this.height = height; + + this.ratio = _.isUndefined(ratio) ? getRatio(this.ctx) : ratio; + + this.domElement.width = width * this.ratio; + this.domElement.height = height * this.ratio; + + _.extend(this.domElement.style, { + width: width + 'px', + height: height + 'px' + }); + + width *= this.ratio; + height *= this.ratio; + + // Set for this.stage parent scaling to account for HDPI + this._renderer.matrix[0] = this._renderer.matrix[4] = this._renderer.scale = this.ratio; + + this._flagMatrix = true; + + this.ctx.viewport(0, 0, width, height); + + var resolutionLocation = this.ctx.getUniformLocation( + this.program, 'u_resolution'); + this.ctx.uniform2f(resolutionLocation, width, height); + + return this; + + }, + + render: function() { + + var gl = this.ctx; + + if (!this.overdraw) { + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + } + + webgl.group.render.call(this.scene, gl, this.program); + this._flagMatrix = false; + + return this; + + } + + }); + +})((typeof global !== 'undefined' ? global : this).Two); + +(function(Two) { + + var _ = Two.Utils; + + var Shape = Two.Shape = function() { + + // Private object for renderer specific variables. + this._renderer = {}; + this._renderer.flagMatrix = _.bind(Shape.FlagMatrix, this); + this.isShape = true; + + this.id = Two.Identifier + Two.uniqueId(); + this.classList = []; + + // Define matrix properties which all inherited + // objects of Shape have. + + this._matrix = new Two.Matrix(); + + this.translation = new Two.Vector(); + this.rotation = 0; + this.scale = 1; + + }; + + _.extend(Shape, { + + FlagMatrix: function() { + this._flagMatrix = true; + }, + + MakeObservable: function(object) { + + Object.defineProperty(object, 'translation', { + enumerable: true, + get: function() { + return this._translation; + }, + set: function(v) { + if (this._translation) { + this._translation.unbind(Two.Events.change, this._renderer.flagMatrix); + } + this._translation = v; + this._translation.bind(Two.Events.change, this._renderer.flagMatrix); + Shape.FlagMatrix.call(this); + } + }); + + Object.defineProperty(object, 'rotation', { + enumerable: true, + get: function() { + return this._rotation; + }, + set: function(v) { + this._rotation = v; + this._flagMatrix = true; + } + }); + + Object.defineProperty(object, 'scale', { + enumerable: true, + get: function() { + return this._scale; + }, + set: function(v) { + + if (this._scale instanceof Two.Vector) { + this._scale.unbind(Two.Events.change, this._renderer.flagMatrix); + } + + this._scale = v; + + if (this._scale instanceof Two.Vector) { + this._scale.bind(Two.Events.change, this._renderer.flagMatrix); + } + + this._flagMatrix = true; + this._flagScale = true; + + } + }); + + } + + }); + + _.extend(Shape.prototype, Two.Utils.Events, { + + // Flags + + _flagMatrix: true, + _flagScale: false, + + // _flagMask: false, + // _flagClip: false, + + // Underlying Properties + + _rotation: 0, + _scale: 1, + _translation: null, + + // _mask: null, + // _clip: false, + + addTo: function(group) { + group.add(this); + return this; + }, + + clone: function() { + var clone = new Shape(); + clone.translation.copy(this.translation); + clone.rotation = this.rotation; + clone.scale = this.scale; + _.each(Shape.Properties, function(k) { + clone[k] = this[k]; + }, this); + return clone._update(); + }, + + /** + * To be called before render that calculates and collates all information + * to be as up-to-date as possible for the render. Called once a frame. + */ + _update: function(deep) { + + if (!this._matrix.manual && this._flagMatrix) { + + this._matrix + .identity() + .translate(this.translation.x, this.translation.y); + + if (this._scale instanceof Two.Vector) { + this._matrix.scale(this._scale.x, this._scale.y); + } else { + this._matrix.scale(this._scale); + } + + this._matrix.rotate(this.rotation); + + } + + if (deep) { + // Bubble up to parents mainly for `getBoundingClientRect` method. + if (this.parent && this.parent._update) { + this.parent._update(); + } + } + + return this; + + }, + + flagReset: function() { + + this._flagMatrix = this._flagScale = false; + + return this; + + } + + }); + + Shape.MakeObservable(Shape.prototype); + +})((typeof global !== 'undefined' ? global : this).Two); + +(function(Two) { + + /** + * Constants + */ + + var min = Math.min, max = Math.max, round = Math.round, + getComputedMatrix = Two.Utils.getComputedMatrix; + + var commands = {}; + var _ = Two.Utils; + + _.each(Two.Commands, function(v, k) { + commands[k] = new RegExp(v); + }); + + var Path = Two.Path = function(vertices, closed, curved, manual) { + + Two.Shape.call(this); + + this._renderer.type = 'path'; + this._renderer.flagVertices = _.bind(Path.FlagVertices, this); + this._renderer.bindVertices = _.bind(Path.BindVertices, this); + this._renderer.unbindVertices = _.bind(Path.UnbindVertices, this); + + this._renderer.flagFill = _.bind(Path.FlagFill, this); + this._renderer.flagStroke = _.bind(Path.FlagStroke, this); + + this._closed = !!closed; + this._curved = !!curved; + + this.beginning = 0; + this.ending = 1; + + // Style properties + + this.fill = '#fff'; + this.stroke = '#000'; + this.linewidth = 1.0; + this.opacity = 1.0; + this.visible = true; + + this.cap = 'butt'; // Default of Adobe Illustrator + this.join = 'miter'; // Default of Adobe Illustrator + this.miter = 4; // Default of Adobe Illustrator + + this._vertices = []; + this.vertices = vertices; + + // Determines whether or not two.js should calculate curves, lines, and + // commands automatically for you or to let the developer manipulate them + // for themselves. + this.automatic = !manual; + + }; + + _.extend(Path, { + + Properties: [ + 'fill', + 'stroke', + 'linewidth', + 'opacity', + 'visible', + 'cap', + 'join', + 'miter', + + 'closed', + 'curved', + 'automatic', + 'beginning', + 'ending' + ], + + FlagVertices: function() { + this._flagVertices = true; + this._flagLength = true; + }, + + BindVertices: function(items) { + + // This function is called a lot + // when importing a large SVG + var i = items.length; + while (i--) { + items[i].bind(Two.Events.change, this._renderer.flagVertices); + } + + this._renderer.flagVertices(); + + }, + + UnbindVertices: function(items) { + + var i = items.length; + while (i--) { + items[i].unbind(Two.Events.change, this._renderer.flagVertices); + } + + this._renderer.flagVertices(); + + }, + + FlagFill: function() { + this._flagFill = true; + }, + + FlagStroke: function() { + this._flagStroke = true; + }, + + MakeObservable: function(object) { + + Two.Shape.MakeObservable(object); + + // Only the 6 defined properties are flagged like this. The subsequent + // properties behave differently and need to be hand written. + _.each(Path.Properties.slice(2, 8), Two.Utils.defineProperty, object); + + Object.defineProperty(object, 'fill', { + enumerable: true, + get: function() { + return this._fill; + }, + set: function(f) { + + if (this._fill instanceof Two.Gradient + || this._fill instanceof Two.LinearGradient + || this._fill instanceof Two.RadialGradient + || this._fill instanceof Two.Texture) { + this._fill.unbind(Two.Events.change, this._renderer.flagFill); + } + + this._fill = f; + this._flagFill = true; + + if (this._fill instanceof Two.Gradient + || this._fill instanceof Two.LinearGradient + || this._fill instanceof Two.RadialGradient + || this._fill instanceof Two.Texture) { + this._fill.bind(Two.Events.change, this._renderer.flagFill); + } + + } + }); + + Object.defineProperty(object, 'stroke', { + enumerable: true, + get: function() { + return this._stroke; + }, + set: function(f) { + + if (this._stroke instanceof Two.Gradient + || this._stroke instanceof Two.LinearGradient + || this._stroke instanceof Two.RadialGradient + || this._stroke instanceof Two.Texture) { + this._stroke.unbind(Two.Events.change, this._renderer.flagStroke); + } + + this._stroke = f; + this._flagStroke = true; + + if (this._stroke instanceof Two.Gradient + || this._stroke instanceof Two.LinearGradient + || this._stroke instanceof Two.RadialGradient + || this._stroke instanceof Two.Texture) { + this._stroke.bind(Two.Events.change, this._renderer.flagStroke); + } + + } + }); + + Object.defineProperty(object, 'length', { + get: function() { + if (this._flagLength) { + this._updateLength(); + } + return this._length; + } + }); + + Object.defineProperty(object, 'closed', { + enumerable: true, + get: function() { + return this._closed; + }, + set: function(v) { + this._closed = !!v; + this._flagVertices = true; + } + }); + + Object.defineProperty(object, 'curved', { + enumerable: true, + get: function() { + return this._curved; + }, + set: function(v) { + this._curved = !!v; + this._flagVertices = true; + } + }); + + Object.defineProperty(object, 'automatic', { + enumerable: true, + get: function() { + return this._automatic; + }, + set: function(v) { + if (v === this._automatic) { + return; + } + this._automatic = !!v; + var method = this._automatic ? 'ignore' : 'listen'; + _.each(this.vertices, function(v) { + v[method](); + }); + } + }); + + Object.defineProperty(object, 'beginning', { + enumerable: true, + get: function() { + return this._beginning; + }, + set: function(v) { + this._beginning = v; + this._flagVertices = true; + } + }); + + Object.defineProperty(object, 'ending', { + enumerable: true, + get: function() { + return this._ending; + }, + set: function(v) { + this._ending = v; + this._flagVertices = true; + } + }); + + Object.defineProperty(object, 'vertices', { + + enumerable: true, + + get: function() { + return this._collection; + }, + + set: function(vertices) { + + var updateVertices = this._renderer.flagVertices; + var bindVertices = this._renderer.bindVertices; + var unbindVertices = this._renderer.unbindVertices; + + // Remove previous listeners + if (this._collection) { + this._collection + .unbind(Two.Events.insert, bindVertices) + .unbind(Two.Events.remove, unbindVertices); + } + + // Create new Collection with copy of vertices + this._collection = new Two.Utils.Collection((vertices || []).slice(0)); + + // Listen for Collection changes and bind / unbind + this._collection + .bind(Two.Events.insert, bindVertices) + .bind(Two.Events.remove, unbindVertices); + + // Bind Initial Vertices + bindVertices(this._collection); + + } + + }); + + Object.defineProperty(object, 'clip', { + enumerable: true, + get: function() { + return this._clip; + }, + set: function(v) { + this._clip = v; + this._flagClip = true; + } + }); + + } + + }); + + _.extend(Path.prototype, Two.Shape.prototype, { + + // Flags + // http://en.wikipedia.org/wiki/Flag + + _flagVertices: true, + _flagLength: true, + + _flagFill: true, + _flagStroke: true, + _flagLinewidth: true, + _flagOpacity: true, + _flagVisible: true, + + _flagCap: true, + _flagJoin: true, + _flagMiter: true, + + _flagClip: false, + + // Underlying Properties + + _length: 0, + + _fill: '#fff', + _stroke: '#000', + _linewidth: 1.0, + _opacity: 1.0, + _visible: true, + + _cap: 'round', + _join: 'round', + _miter: 4, + + _closed: true, + _curved: false, + _automatic: true, + _beginning: 0, + _ending: 1.0, + + _clip: false, + + clone: function(parent) { + + parent = parent || this.parent; + + var points = _.map(this.vertices, function(v) { + return v.clone(); + }); + + var clone = new Path(points, this.closed, this.curved, !this.automatic); + + _.each(Two.Path.Properties, function(k) { + clone[k] = this[k]; + }, this); + + clone.translation.copy(this.translation); + clone.rotation = this.rotation; + clone.scale = this.scale; + + if (parent) { + parent.add(clone); + } + + return clone; + + }, + + toObject: function() { + + var result = { + vertices: _.map(this.vertices, function(v) { + return v.toObject(); + }) + }; + + _.each(Two.Shape.Properties, function(k) { + result[k] = this[k]; + }, this); + + result.translation = this.translation.toObject; + result.rotation = this.rotation; + result.scale = this.scale; + + return result; + + }, + + noFill: function() { + this.fill = 'transparent'; + return this; + }, + + noStroke: function() { + this.stroke = 'transparent'; + return this; + }, + + /** + * Orient the vertices of the shape to the upper lefthand + * corner of the path. + */ + corner: function() { + + var rect = this.getBoundingClientRect(true); + + rect.centroid = { + x: rect.left + rect.width / 2, + y: rect.top + rect.height / 2 + }; + + _.each(this.vertices, function(v) { + v.addSelf(rect.centroid); + }); + + return this; + + }, + + /** + * Orient the vertices of the shape to the center of the + * path. + */ + center: function() { + + var rect = this.getBoundingClientRect(true); + + rect.centroid = { + x: rect.left + rect.width / 2, + y: rect.top + rect.height / 2 + }; + + _.each(this.vertices, function(v) { + v.subSelf(rect.centroid); + }); + + // this.translation.addSelf(rect.centroid); + + return this; + + }, + + /** + * Remove self from the scene / parent. + */ + remove: function() { + + if (!this.parent) { + return this; + } + + this.parent.remove(this); + + return this; + + }, + + /** + * Return an object with top, left, right, bottom, width, and height + * parameters of the group. + */ + getBoundingClientRect: function(shallow) { + var matrix, border, l, x, y, i, v; + + var left = Infinity, right = -Infinity, + top = Infinity, bottom = -Infinity; + + // TODO: Update this to not __always__ update. Just when it needs to. + this._update(true); + + matrix = !!shallow ? this._matrix : getComputedMatrix(this); + + border = this.linewidth / 2; + l = this._vertices.length; + + if (l <= 0) { + v = matrix.multiply(0, 0, 1); + return { + top: v.y, + left: v.x, + right: v.x, + bottom: v.y, + width: 0, + height: 0 + }; + } + + for (i = 0; i < l; i++) { + v = this._vertices[i]; + + x = v.x; + y = v.y; + + v = matrix.multiply(x, y, 1); + top = min(v.y - border, top); + left = min(v.x - border, left); + right = max(v.x + border, right); + bottom = max(v.y + border, bottom); + } + + return { + top: top, + left: left, + right: right, + bottom: bottom, + width: right - left, + height: bottom - top + }; + + }, + + /** + * Given a float `t` from 0 to 1, return a point or assign a passed `obj`'s + * coordinates to that percentage on this Two.Path's curve. + */ + getPointAt: function(t, obj) { + var ia, ib; + var x, x1, x2, x3, x4, y, y1, y2, y3, y4, left, right; + var target = this.length * Math.min(Math.max(t, 0), 1); + var length = this.vertices.length; + var last = length - 1; + + var a = null; + var b = null; + + for (var i = 0, l = this._lengths.length, sum = 0; i < l; i++) { + + if (sum + this._lengths[i] >= target) { + + if (this._closed) { + ia = Two.Utils.mod(i, length); + ib = Two.Utils.mod(i - 1, length); + if (i === 0) { + ia = ib; + ib = i; + } + } else { + ia = i; + ib = Math.min(Math.max(i - 1, 0), last); + } + + a = this.vertices[ia]; + b = this.vertices[ib]; + target -= sum; + if (this._lengths[i] !== 0) { + t = target / this._lengths[i]; + } + + break; + + } + + sum += this._lengths[i]; + + } + + // console.log(sum, a.command, b.command); + + if (_.isNull(a) || _.isNull(b)) { + return null; + } + + right = b.controls && b.controls.right; + left = a.controls && a.controls.left; + + x1 = b.x; + y1 = b.y; + x2 = (right || b).x; + y2 = (right || b).y; + x3 = (left || a).x; + y3 = (left || a).y; + x4 = a.x; + y4 = a.y; + + if (right && b._relative) { + x2 += b.x; + y2 += b.y; + } + + if (left && a._relative) { + x3 += a.x; + y3 += a.y; + } + + x = Two.Utils.getPointOnCubicBezier(t, x1, x2, x3, x4); + y = Two.Utils.getPointOnCubicBezier(t, y1, y2, y3, y4); + + if (_.isObject(obj)) { + obj.x = x; + obj.y = y; + return obj; + } + + return new Two.Vector(x, y); + + }, + + /** + * Based on closed / curved and sorting of vertices plot where all points + * should be and where the respective handles should be too. + */ + plot: function() { + + if (this.curved) { + Two.Utils.getCurveFromPoints(this._vertices, this.closed); + return this; + } + + for (var i = 0; i < this._vertices.length; i++) { + this._vertices[i]._command = i === 0 ? Two.Commands.move : Two.Commands.line; + } + + return this; + + }, + + subdivide: function(limit) { + //TODO: DRYness (function below) + this._update(); + + var last = this.vertices.length - 1; + var b = this.vertices[last]; + var closed = this._closed || this.vertices[last]._command === Two.Commands.close; + var points = []; + _.each(this.vertices, function(a, i) { + + if (i <= 0 && !closed) { + b = a; + return; + } + + if (a.command === Two.Commands.move) { + points.push(new Two.Anchor(b.x, b.y)); + if (i > 0) { + points[points.length - 1].command = Two.Commands.line; + } + b = a; + return; + } + + var verts = getSubdivisions(a, b, limit); + points = points.concat(verts); + + // Assign commands to all the verts + _.each(verts, function(v, i) { + if (i <= 0 && b.command === Two.Commands.move) { + v.command = Two.Commands.move; + } else { + v.command = Two.Commands.line; + } + }); + + if (i >= last) { + + // TODO: Add check if the two vectors in question are the same values. + if (this._closed && this._automatic) { + + b = a; + + verts = getSubdivisions(a, b, limit); + points = points.concat(verts); + + // Assign commands to all the verts + _.each(verts, function(v, i) { + if (i <= 0 && b.command === Two.Commands.move) { + v.command = Two.Commands.move; + } else { + v.command = Two.Commands.line; + } + }); + + } else if (closed) { + points.push(new Two.Anchor(a.x, a.y)); + } + + points[points.length - 1].command = closed ? Two.Commands.close : Two.Commands.line; + + } + + b = a; + + }, this); + + this._automatic = false; + this._curved = false; + this.vertices = points; + + return this; + + }, + + _updateLength: function(limit) { + //TODO: DRYness (function above) + this._update(); + + var length = this.vertices.length; + var last = length - 1; + var b = this.vertices[last]; + var closed = this._closed || this.vertices[last]._command === Two.Commands.close; + var sum = 0; + + if (_.isUndefined(this._lengths)) { + this._lengths = []; + } + + _.each(this.vertices, function(a, i) { + + if ((i <= 0 && !closed) || a.command === Two.Commands.move) { + b = a; + this._lengths[i] = 0; + return; + } + + this._lengths[i] = getCurveLength(a, b, limit); + sum += this._lengths[i]; + + if (i >= last && closed) { + + b = this.vertices[(i + 1) % length]; + + this._lengths[i + 1] = getCurveLength(a, b, limit); + sum += this._lengths[i + 1]; + + } + + b = a; + + }, this); + + this._length = sum; + + return this; + + }, + + _update: function() { + + if (this._flagVertices) { + + var l = this.vertices.length; + var last = l - 1, v; + + // TODO: Should clamp this so that `ia` and `ib` + // cannot select non-verts. + var ia = round((this._beginning) * last); + var ib = round((this._ending) * last); + + this._vertices.length = 0; + + for (var i = ia; i < ib + 1; i++) { + v = this.vertices[i]; + this._vertices.push(v); + } + + if (this._automatic) { + this.plot(); + } + + } + + Two.Shape.prototype._update.apply(this, arguments); + + return this; + + }, + + flagReset: function() { + + this._flagVertices = this._flagFill = this._flagStroke = + this._flagLinewidth = this._flagOpacity = this._flagVisible = + this._flagCap = this._flagJoin = this._flagMiter = + this._flagClip = false; + + Two.Shape.prototype.flagReset.call(this); + + return this; + + } + + }); + + Path.MakeObservable(Path.prototype); + + /** + * Utility functions + */ + + function getCurveLength(a, b, limit) { + // TODO: DRYness + var x1, x2, x3, x4, y1, y2, y3, y4; + + var right = b.controls && b.controls.right; + var left = a.controls && a.controls.left; + + x1 = b.x; + y1 = b.y; + x2 = (right || b).x; + y2 = (right || b).y; + x3 = (left || a).x; + y3 = (left || a).y; + x4 = a.x; + y4 = a.y; + + if (right && b._relative) { + x2 += b.x; + y2 += b.y; + } + + if (left && a._relative) { + x3 += a.x; + y3 += a.y; + } + + return Two.Utils.getCurveLength(x1, y1, x2, y2, x3, y3, x4, y4, limit); + + } + + function getSubdivisions(a, b, limit) { + // TODO: DRYness + var x1, x2, x3, x4, y1, y2, y3, y4; + + var right = b.controls && b.controls.right; + var left = a.controls && a.controls.left; + + x1 = b.x; + y1 = b.y; + x2 = (right || b).x; + y2 = (right || b).y; + x3 = (left || a).x; + y3 = (left || a).y; + x4 = a.x; + y4 = a.y; + + if (right && b._relative) { + x2 += b.x; + y2 += b.y; + } + + if (left && a._relative) { + x3 += a.x; + y3 += a.y; + } + + return Two.Utils.subdivide(x1, y1, x2, y2, x3, y3, x4, y4, limit); + + } + +})((typeof global !== 'undefined' ? global : this).Two); + +(function(Two) { + + var Path = Two.Path; + var _ = Two.Utils; + + var Line = Two.Line = function(x1, y1, x2, y2) { + + var width = x2 - x1; + var height = y2 - y1; + + var w2 = width / 2; + var h2 = height / 2; + + Path.call(this, [ + new Two.Anchor(- w2, - h2), + new Two.Anchor(w2, h2) + ]); + + this.translation.set(x1 + w2, y1 + h2); + + }; + + _.extend(Line.prototype, Path.prototype); + + Path.MakeObservable(Line.prototype); + +})((typeof global !== 'undefined' ? global : this).Two); + +(function(Two) { + + var Path = Two.Path; + var _ = Two.Utils; + + var Rectangle = Two.Rectangle = function(x, y, width, height) { + + Path.call(this, [ + new Two.Anchor(), + new Two.Anchor(), + new Two.Anchor(), + new Two.Anchor() + ], true); + + this.width = width; + this.height = height; + this._update(); + + this.translation.set(x, y); + + }; + + _.extend(Rectangle, { + + Properties: ['width', 'height'], + + MakeObservable: function(obj) { + Path.MakeObservable(obj); + _.each(Rectangle.Properties, Two.Utils.defineProperty, obj); + } + + }); + + _.extend(Rectangle.prototype, Path.prototype, { + + _width: 0, + _height: 0, + + _flagWidth: 0, + _flagHeight: 0, + + _update: function() { + + if (this._flagWidth || this._flagHeight) { + + var xr = this._width / 2; + var yr = this._height / 2; + + this.vertices[0].set(-xr, -yr); + this.vertices[1].set(xr, -yr); + this.vertices[2].set(xr, yr); + this.vertices[3].set(-xr, yr); + + } + + Path.prototype._update.call(this); + + return this; + + }, + + flagReset: function() { + + this._flagWidth = this._flagHeight = false; + Path.prototype.flagReset.call(this); + + return this; + + } + + }); + + Rectangle.MakeObservable(Rectangle.prototype); + +})((typeof global !== 'undefined' ? global : this).Two); + +(function(Two) { + + var Path = Two.Path, TWO_PI = Math.PI * 2, cos = Math.cos, sin = Math.sin; + var _ = Two.Utils; + + var Ellipse = Two.Ellipse = function(ox, oy, rx, ry) { + + if (!_.isNumber(ry)) { + ry = rx; + } + + var amount = Two.Resolution; + + var points = _.map(_.range(amount), function(i) { + return new Two.Anchor(); + }, this); + + Path.call(this, points, true, true); + + this.width = rx * 2; + this.height = ry * 2; + + this._update(); + this.translation.set(ox, oy); + + }; + + _.extend(Ellipse, { + + Properties: ['width', 'height'], + + MakeObservable: function(obj) { + + Path.MakeObservable(obj); + _.each(Ellipse.Properties, Two.Utils.defineProperty, obj); + + } + + }); + + _.extend(Ellipse.prototype, Path.prototype, { + + _width: 0, + _height: 0, + + _flagWidth: false, + _flagHeight: false, + + _update: function() { + + if (this._flagWidth || this._flagHeight) { + for (var i = 0, l = this.vertices.length; i < l; i++) { + var pct = i / l; + var theta = pct * TWO_PI; + var x = this._width * cos(theta) / 2; + var y = this._height * sin(theta) / 2; + this.vertices[i].set(x, y); + } + } + + Path.prototype._update.call(this); + return this; + + }, + + flagReset: function() { + + this._flagWidth = this._flagHeight = false; + + Path.prototype.flagReset.call(this); + return this; + + } + + }); + + Ellipse.MakeObservable(Ellipse.prototype); + +})((typeof global !== 'undefined' ? global : this).Two); + +(function(Two) { + + var Path = Two.Path, TWO_PI = Math.PI * 2, cos = Math.cos, sin = Math.sin; + var _ = Two.Utils; + + var Circle = Two.Circle = function(ox, oy, r) { + + var amount = Two.Resolution; + + var points = _.map(_.range(amount), function(i) { + return new Two.Anchor(); + }, this); + + Path.call(this, points, true, true); + + this.radius = r; + + this._update(); + this.translation.set(ox, oy); + + }; + + _.extend(Circle, { + + Properties: ['radius'], + + MakeObservable: function(obj) { + + Path.MakeObservable(obj); + _.each(Circle.Properties, Two.Utils.defineProperty, obj); + + } + + }); + + _.extend(Circle.prototype, Path.prototype, { + + _radius: 0, + _flagRadius: false, + + _update: function() { + + if (this._flagRadius) { + for (var i = 0, l = this.vertices.length; i < l; i++) { + var pct = i / l; + var theta = pct * TWO_PI; + var x = this._radius * cos(theta); + var y = this._radius * sin(theta); + this.vertices[i].set(x, y); + } + } + + Path.prototype._update.call(this); + return this; + + }, + + flagReset: function() { + + this._flagRadius = false; + + Path.prototype.flagReset.call(this); + return this; + + } + + }); + + Circle.MakeObservable(Circle.prototype); + +})((typeof global !== 'undefined' ? global : this).Two); + +(function(Two) { + + var Path = Two.Path, TWO_PI = Math.PI * 2, cos = Math.cos, sin = Math.sin; + var _ = Two.Utils; + + var Polygon = Two.Polygon = function(ox, oy, r, sides) { + + sides = Math.max(sides || 0, 3); + + var points = _.map(_.range(sides), function(i) { + return new Two.Anchor(); + }); + + Path.call(this, points, true); + + this.width = r * 2; + this.height = r * 2; + this.sides = sides; + + this._update(); + this.translation.set(ox, oy); + + }; + + _.extend(Polygon, { + + Properties: ['width', 'height', 'sides'], + + MakeObservable: function(obj) { + + Path.MakeObservable(obj); + _.each(Polygon.Properties, Two.Utils.defineProperty, obj); + + } + + }); + + _.extend(Polygon.prototype, Path.prototype, { + + _width: 0, + _height: 0, + _sides: 0, + + _flagWidth: false, + _flagHeight: false, + _flagSides: false, + + _update: function() { + + if (this._flagWidth || this._flagHeight || this._flagSides) { + + var sides = this._sides; + var amount = this.vertices.length; + + if (amount > sides) { + this.vertices.splice(sides - 1, amount - sides); + } + + for (var i = 0; i < sides; i++) { + + var pct = (i + 0.5) / sides; + var theta = TWO_PI * pct + Math.PI / 2; + var x = this._width * cos(theta); + var y = this._height * sin(theta); + + if (i >= amount) { + this.vertices.push(new Two.Anchor(x, y)); + } else { + this.vertices[i].set(x, y); + } + + } + + } + + Path.prototype._update.call(this); + return this; + + }, + + flagReset: function() { + + this._flagWidth = this._flagHeight = this._flagSides = false; + Path.prototype.flagReset.call(this); + + return this; + + } + + }); + + Polygon.MakeObservable(Polygon.prototype); + +})((typeof global !== 'undefined' ? global : this).Two); + +(function(Two) { + + var Path = Two.Path, PI = Math.PI, TWO_PI = Math.PI * 2, HALF_PI = Math.PI / 2, + cos = Math.cos, sin = Math.sin, abs = Math.abs, _ = Two.Utils; + + var ArcSegment = Two.ArcSegment = function(ox, oy, ir, or, sa, ea, res) { + + var points = _.map(_.range(res || (Two.Resolution * 3)), function() { + return new Two.Anchor(); + }); + + Path.call(this, points, false, false, true); + + this.innerRadius = ir; + this.outerRadius = or; + + this.startAngle = sa; + this.endAngle = ea; + + this._update(); + this.translation.set(ox, oy); + + } + + _.extend(ArcSegment, { + + Properties: ['startAngle', 'endAngle', 'innerRadius', 'outerRadius'], + + MakeObservable: function(obj) { + + Path.MakeObservable(obj); + _.each(ArcSegment.Properties, Two.Utils.defineProperty, obj); + + } + + }); + + _.extend(ArcSegment.prototype, Path.prototype, { + + _flagStartAngle: false, + _flagEndAngle: false, + _flagInnerRadius: false, + _flagOuterRadius: false, + + _startAngle: 0, + _endAngle: TWO_PI, + _innerRadius: 0, + _outerRadius: 0, + + _update: function() { + + if (this._flagStartAngle || this._flagEndAngle || this._flagInnerRadius + || this._flagOuterRadius) { + + var sa = this._startAngle; + var ea = this._endAngle; + + var ir = this._innerRadius; + var or = this._outerRadius; + + var connected = mod(sa, TWO_PI) === mod(ea, TWO_PI); + var punctured = ir > 0; + + var vertices = this.vertices; + var length = (punctured ? vertices.length / 2 : vertices.length); + var command, id = 0; + + if (connected) { + length--; + } else if (!punctured) { + length -= 2; + } + + /** + * Outer Circle + */ + for (var i = 0, last = length - 1; i < length; i++) { + + var pct = i / last; + var v = vertices[id]; + var theta = pct * (ea - sa) + sa; + var step = (ea - sa) / length; + + var x = or * Math.cos(theta); + var y = or * Math.sin(theta); + + switch (i) { + case 0: + command = Two.Commands.move; + break; + default: + command = Two.Commands.curve; + } + + v.command = command; + v.x = x; + v.y = y; + v.controls.left.clear(); + v.controls.right.clear(); + + if (v.command === Two.Commands.curve) { + var amp = or * step / Math.PI; + v.controls.left.x = amp * Math.cos(theta - HALF_PI); + v.controls.left.y = amp * Math.sin(theta - HALF_PI); + v.controls.right.x = amp * Math.cos(theta + HALF_PI); + v.controls.right.y = amp * Math.sin(theta + HALF_PI); + if (i === 1) { + v.controls.left.multiplyScalar(2); + } + if (i === last) { + v.controls.right.multiplyScalar(2); + } + } + + id++; + + } + + if (punctured) { + + if (connected) { + vertices[id].command = Two.Commands.close; + id++; + } else { + length--; + last = length - 1; + } + + /** + * Inner Circle + */ + for (i = 0; i < length; i++) { + + pct = i / last; + v = vertices[id]; + theta = (1 - pct) * (ea - sa) + sa; + step = (ea - sa) / length; + + x = ir * Math.cos(theta); + y = ir * Math.sin(theta); + command = Two.Commands.curve; + if (i <= 0) { + command = connected ? Two.Commands.move : Two.Commands.line; + } + + v.command = command; + v.x = x; + v.y = y; + v.controls.left.clear(); + v.controls.right.clear(); + + if (v.command === Two.Commands.curve) { + amp = ir * step / Math.PI; + v.controls.left.x = amp * Math.cos(theta + HALF_PI); + v.controls.left.y = amp * Math.sin(theta + HALF_PI); + v.controls.right.x = amp * Math.cos(theta - HALF_PI); + v.controls.right.y = amp * Math.sin(theta - HALF_PI); + if (i === 1) { + v.controls.left.multiplyScalar(2); + } + if (i === last) { + v.controls.right.multiplyScalar(2); + } + } + + id++; + + } + + } else if (!connected) { + + vertices[id].command = Two.Commands.line; + vertices[id].x = 0; + vertices[id].y = 0; + id++; + + } + + /** + * Final Point + */ + vertices[id].command = Two.Commands.close; + + } + + Path.prototype._update.call(this); + + return this; + + }, + + flagReset: function() { + + Path.prototype.flagReset.call(this); + + this._flagStartAngle = this._flagEndAngle + = this._flagInnerRadius = this._flagOuterRadius = false; + + return this; + + } + + }); + + ArcSegment.MakeObservable(ArcSegment.prototype); + + function mod(v, l) { + while (v < 0) { + v += l; + } + return v % l; + } + +})((typeof global !== 'undefined' ? global : this).Two); + +(function(Two) { + + var Path = Two.Path, TWO_PI = Math.PI * 2, cos = Math.cos, sin = Math.sin; + var _ = Two.Utils; + + var Star = Two.Star = function(ox, oy, or, ir, sides) { + + if (!_.isNumber(ir)) { + ir = or / 2; + } + + if (!_.isNumber(sides) || sides <= 0) { + sides = 5; + } + + var length = sides * 2; + + var points = _.map(_.range(length), function(i) { + return new Two.Anchor(); + }); + + Path.call(this, points, true); + + this.innerRadius = ir; + this.outerRadius = or; + this.sides = sides; + + this._update(); + this.translation.set(ox, oy); + + }; + + _.extend(Star, { + + Properties: ['innerRadius', 'outerRadius', 'sides'], + + MakeObservable: function(obj) { + + Path.MakeObservable(obj); + _.each(Star.Properties, Two.Utils.defineProperty, obj); + + } + + }); + + _.extend(Star.prototype, Path.prototype, { + + _innerRadius: 0, + _outerRadius: 0, + _sides: 0, + + _flagInnerRadius: false, + _flagOuterRadius: false, + _flagSides: false, + + _update: function() { + + if (this._flagInnerRadius || this._flagOuterRadius || this._flagSides) { + + var sides = this._sides * 2; + var amount = this.vertices.length; + + if (amount > sides) { + this.vertices.splice(sides - 1, amount - sides); + } + + for (var i = 0; i < sides; i++) { + + var pct = (i + 0.5) / sides; + var theta = TWO_PI * pct; + var r = (i % 2 ? this._innerRadius : this._outerRadius); + var x = r * cos(theta); + var y = r * sin(theta); + + if (i >= amount) { + this.vertices.push(new Two.Anchor(x, y)); + } else { + this.vertices[i].set(x, y); + } + + } + + } + + Path.prototype._update.call(this); + + return this; + + }, + + flagReset: function() { + + this._flagInnerRadius = this._flagOuterRadius = this._flagSides = false; + Path.prototype.flagReset.call(this); + + return this; + + } + + }); + + Star.MakeObservable(Star.prototype); + +})((typeof global !== 'undefined' ? global : this).Two); + +(function(Two) { + + var Path = Two.Path; + var _ = Two.Utils; + + var RoundedRectangle = Two.RoundedRectangle = function(ox, oy, width, height, radius) { + + if (!_.isNumber(radius)) { + radius = Math.floor(Math.min(width, height) / 12); + } + + var amount = 10; + + var points = _.map(_.range(amount), function(i) { + return new Two.Anchor(0, 0, 0, 0, 0, 0, + i === 0 ? Two.Commands.move : Two.Commands.curve); + }); + + points[points.length - 1].command = Two.Commands.close; + + Path.call(this, points, false, false, true); + + this.width = width; + this.height = height; + this.radius = radius; + + this._update(); + this.translation.set(ox, oy); + + }; + + _.extend(RoundedRectangle, { + + Properties: ['width', 'height', 'radius'], + + MakeObservable: function(obj) { + + Path.MakeObservable(obj); + _.each(RoundedRectangle.Properties, Two.Utils.defineProperty, obj); + + } + + }); + + _.extend(RoundedRectangle.prototype, Path.prototype, { + + _width: 0, + _height: 0, + _radius: 0, + + _flagWidth: false, + _flagHeight: false, + _flagRadius: false, + + _update: function() { + + if (this._flagWidth || this._flagHeight || this._flagRadius) { + + var width = this._width; + var height = this._height; + var radius = Math.min(Math.max(this._radius, 0), + Math.min(width, height)); + + var v; + var w = width / 2; + var h = height / 2; + + v = this.vertices[0]; + v.x = - (w - radius); + v.y = - h; + + // Upper Right Corner + + v = this.vertices[1]; + v.x = (w - radius); + v.y = - h; + v.controls.left.clear(); + v.controls.right.x = radius; + v.controls.right.y = 0; + + v = this.vertices[2]; + v.x = w; + v.y = - (h - radius); + v.controls.right.clear(); + v.controls.left.clear(); + + // Bottom Right Corner + + v = this.vertices[3]; + v.x = w; + v.y = (h - radius); + v.controls.left.clear(); + v.controls.right.x = 0; + v.controls.right.y = radius; + + v = this.vertices[4]; + v.x = (w - radius); + v.y = h; + v.controls.right.clear(); + v.controls.left.clear(); + + // Bottom Left Corner + + v = this.vertices[5]; + v.x = - (w - radius); + v.y = h; + v.controls.left.clear(); + v.controls.right.x = - radius; + v.controls.right.y = 0; + + v = this.vertices[6]; + v.x = - w; + v.y = (h - radius); + v.controls.left.clear(); + v.controls.right.clear(); + + // Upper Left Corner + + v = this.vertices[7]; + v.x = - w; + v.y = - (h - radius); + v.controls.left.clear(); + v.controls.right.x = 0; + v.controls.right.y = - radius; + + v = this.vertices[8]; + v.x = - (w - radius); + v.y = - h; + v.controls.left.clear(); + v.controls.right.clear(); + + v = this.vertices[9]; + v.copy(this.vertices[8]); + + } + + Path.prototype._update.call(this); + + return this; + + }, + + flagReset: function() { + + this._flagWidth = this._flagHeight = this._flagRadius = false; + Path.prototype.flagReset.call(this); + + return this; + + } + + }); + + RoundedRectangle.MakeObservable(RoundedRectangle.prototype); + +})((typeof global !== 'undefined' ? global : this).Two); + +(function(Two) { + + var root = Two.root; + var getComputedMatrix = Two.Utils.getComputedMatrix; + var _ = Two.Utils; + + var canvas = (root.document ? root.document.createElement('canvas') : { getContext: _.identity }); + var ctx = canvas.getContext('2d'); + + var Text = Two.Text = function(message, x, y, styles) { + + Two.Shape.call(this); + + this._renderer.type = 'text'; + this._renderer.flagFill = _.bind(Text.FlagFill, this); + this._renderer.flagStroke = _.bind(Text.FlagStroke, this); + + this.value = message; + + if (_.isNumber(x)) { + this.translation.x = x; + } + if (_.isNumber(y)) { + this.translation.y = y; + } + + if (!_.isObject(styles)) { + return this; + } + + _.each(Two.Text.Properties, function(property) { + + if (property in styles) { + this[property] = styles[property]; + } + + }, this); + + }; + + _.extend(Two.Text, { + + Properties: [ + 'value', 'family', 'size', 'leading', 'alignment', 'linewidth', 'style', + 'weight', 'decoration', 'baseline', 'opacity', 'visible', 'fill', 'stroke' + ], + + FlagFill: function() { + this._flagFill = true; + }, + + FlagStroke: function() { + this._flagStroke = true; + }, + + MakeObservable: function(object) { + + Two.Shape.MakeObservable(object); + + _.each(Two.Text.Properties.slice(0, 12), Two.Utils.defineProperty, object); + + Object.defineProperty(object, 'fill', { + enumerable: true, + get: function() { + return this._fill; + }, + set: function(f) { + + if (this._fill instanceof Two.Gradient + || this._fill instanceof Two.LinearGradient + || this._fill instanceof Two.RadialGradient + || this._fill instanceof Two.Texture) { + this._fill.unbind(Two.Events.change, this._renderer.flagFill); + } + + this._fill = f; + this._flagFill = true; + + if (this._fill instanceof Two.Gradient + || this._fill instanceof Two.LinearGradient + || this._fill instanceof Two.RadialGradient + || this._fill instanceof Two.Texture) { + this._fill.bind(Two.Events.change, this._renderer.flagFill); + } + + } + }); + + Object.defineProperty(object, 'stroke', { + enumerable: true, + get: function() { + return this._stroke; + }, + set: function(f) { + + if (this._stroke instanceof Two.Gradient + || this._stroke instanceof Two.LinearGradient + || this._stroke instanceof Two.RadialGradient + || this._stroke instanceof Two.Texture) { + this._stroke.unbind(Two.Events.change, this._renderer.flagStroke); + } + + this._stroke = f; + this._flagStroke = true; + + if (this._stroke instanceof Two.Gradient + || this._stroke instanceof Two.LinearGradient + || this._stroke instanceof Two.RadialGradient + || this._stroke instanceof Two.Texture) { + this._stroke.bind(Two.Events.change, this._renderer.flagStroke); + } + + } + }); + + Object.defineProperty(object, 'clip', { + enumerable: true, + get: function() { + return this._clip; + }, + set: function(v) { + this._clip = v; + this._flagClip = true; + } + }); + + } + + }); + + _.extend(Two.Text.prototype, Two.Shape.prototype, { + + // Flags + // http://en.wikipedia.org/wiki/Flag + + _flagValue: true, + _flagFamily: true, + _flagSize: true, + _flagLeading: true, + _flagAlignment: true, + _flagBaseline: true, + _flagStyle: true, + _flagWeight: true, + _flagDecoration: true, + + _flagFill: true, + _flagStroke: true, + _flagLinewidth: true, + _flagOpacity: true, + _flagVisible: true, + + _flagClip: false, + + // Underlying Properties + + _value: '', + _family: 'sans-serif', + _size: 13, + _leading: 17, + _alignment: 'center', + _baseline: 'middle', + _style: 'normal', + _weight: 500, + _decoration: 'none', + + _fill: '#000', + _stroke: 'transparent', + _linewidth: 1, + _opacity: 1, + _visible: true, + + _clip: false, + + remove: function() { + + if (!this.parent) { + return this; + } + + this.parent.remove(this); + + return this; + + }, + + clone: function(parent) { + + var parent = parent || this.parent; + + var clone = new Two.Text(this.value); + clone.translation.copy(this.translation); + clone.rotation = this.rotation; + clone.scale = this.scale; + + _.each(Two.Text.Properties, function(property) { + clone[property] = this[property]; + }, this); + + if (parent) { + parent.add(clone); + } + + return clone; + + }, + + toObject: function() { + + var result = { + translation: this.translation.toObject(), + rotation: this.rotation, + scale: this.scale + }; + + _.each(Two.Text.Properties, function(property) { + result[property] = this[property]; + }, this); + + return result; + + }, + + noStroke: function() { + this.stroke = 'transparent'; + return this; + }, + + noFill: function() { + this.fill = 'transparent'; + return this; + }, + + /** + * A shim to not break `getBoundingClientRect` calls. TODO: Implement a + * way to calculate proper bounding boxes of `Two.Text`. + */ + getBoundingClientRect: function(shallow) { + + var matrix, border, l, x, y, i, v; + + var left = Infinity, right = -Infinity, + top = Infinity, bottom = -Infinity; + + // TODO: Update this to not __always__ update. Just when it needs to. + this._update(true); + + matrix = !!shallow ? this._matrix : getComputedMatrix(this); + + v = matrix.multiply(0, 0, 1); + + return { + top: v.x, + left: v.y, + right: v.x, + bottom: v.y, + width: 0, + height: 0 + }; + + }, + + flagReset: function() { + + this._flagValue = this._flagFamily = this._flagSize = + this._flagLeading = this._flagAlignment = this._flagFill = + this._flagStroke = this._flagLinewidth = this._flagOpaicty = + this._flagVisible = this._flagClip = this._flagDecoration = + this._flagBaseline = false; + + Two.Shape.prototype.flagReset.call(this); + + return this; + + } + + }); + + Two.Text.MakeObservable(Two.Text.prototype); + +})((typeof global !== 'undefined' ? global : this).Two); + +(function(Two) { + + var _ = Two.Utils; + + var Stop = Two.Stop = function(offset, color, opacity) { + + this._renderer = {}; + this._renderer.type = 'stop'; + + this.offset = _.isNumber(offset) ? offset + : Stop.Index <= 0 ? 0 : 1; + + this.opacity = _.isNumber(opacity) ? opacity : 1; + + this.color = _.isString(color) ? color + : Stop.Index <= 0 ? '#fff' : '#000'; + + Stop.Index = (Stop.Index + 1) % 2; + + }; + + _.extend(Stop, { + + Index: 0, + + Properties: [ + 'offset', + 'opacity', + 'color' + ], + + MakeObservable: function(object) { + + _.each(Stop.Properties, function(property) { + + var object = this; + var secret = '_' + property; + var flag = '_flag' + property.charAt(0).toUpperCase() + property.slice(1); + + Object.defineProperty(object, property, { + enumerable: true, + get: function() { + return this[secret]; + }, + set: function(v) { + this[secret] = v; + this[flag] = true; + if (this.parent) { + this.parent._flagStops = true; + } + } + }); + + }, object); + + } + + }); + + _.extend(Stop.prototype, Two.Utils.Events, { + + clone: function() { + + var clone = new Stop(); + + _.each(Stop.Properties, function(property) { + clone[property] = this[property]; + }, this); + + return clone; + + }, + + toObject: function() { + + var result = {}; + + _.each(Stop.Properties, function(k) { + result[k] = this[k]; + }, this); + + return result; + + }, + + flagReset: function() { + + this._flagOffset = this._flagColor = this._flagOpacity = false; + + return this; + + } + + }); + + Stop.MakeObservable(Stop.prototype); + + var Gradient = Two.Gradient = function(stops) { + + this._renderer = {}; + this._renderer.type = 'gradient'; + + this.id = Two.Identifier + Two.uniqueId(); + this.classList = []; + + this._renderer.flagStops = _.bind(Gradient.FlagStops, this); + this._renderer.bindStops = _.bind(Gradient.BindStops, this); + this._renderer.unbindStops = _.bind(Gradient.UnbindStops, this); + + this.spread = 'pad'; + + this.stops = stops; + + }; + + _.extend(Gradient, { + + Stop: Stop, + + Properties: [ + 'spread' + ], + + MakeObservable: function(object) { + + _.each(Gradient.Properties, Two.Utils.defineProperty, object); + + Object.defineProperty(object, 'stops', { + + enumerable: true, + + get: function() { + return this._stops; + }, + + set: function(stops) { + + var updateStops = this._renderer.flagStops; + var bindStops = this._renderer.bindStops; + var unbindStops = this._renderer.unbindStops; + + // Remove previous listeners + if (this._stops) { + this._stops + .unbind(Two.Events.insert, bindStops) + .unbind(Two.Events.remove, unbindStops); + } + + // Create new Collection with copy of Stops + this._stops = new Two.Utils.Collection((stops || []).slice(0)); + + // Listen for Collection changes and bind / unbind + this._stops + .bind(Two.Events.insert, bindStops) + .bind(Two.Events.remove, unbindStops); + + // Bind Initial Stops + bindStops(this._stops); + + } + + }); + + }, + + FlagStops: function() { + this._flagStops = true; + }, + + BindStops: function(items) { + + // This function is called a lot + // when importing a large SVG + var i = items.length; + while(i--) { + items[i].bind(Two.Events.change, this._renderer.flagStops); + items[i].parent = this; + } + + this._renderer.flagStops(); + + }, + + UnbindStops: function(items) { + + var i = items.length; + while(i--) { + items[i].unbind(Two.Events.change, this._renderer.flagStops); + delete items[i].parent; + } + + this._renderer.flagStops(); + + } + + }); + + _.extend(Gradient.prototype, Two.Utils.Events, { + + _flagStops: false, + _flagSpread: false, + + clone: function(parent) { + + parent = parent || this.parent; + + var stops = _.map(this.stops, function(s) { + return s.clone(); + }); + + var clone = new Gradient(stops); + + _.each(Two.Gradient.Properties, function(k) { + clone[k] = this[k]; + }, this); + + if (parent) { + parent.add(clone); + } + + return clone; + + }, + + toObject: function() { + + var result = { + stops: _.map(this.stops, function(s) { + return s.toObject(); + }) + }; + + _.each(Gradient.Properties, function(k) { + result[k] = this[k]; + }, this); + + return result; + + }, + + _update: function() { + + if (this._flagSpread || this._flagStops) { + this.trigger(Two.Events.change); + } + + return this; + + }, + + flagReset: function() { + + this._flagSpread = this._flagStops = false; + + return this; + + } + + }); + + Gradient.MakeObservable(Gradient.prototype); + +})((typeof global !== 'undefined' ? global : this).Two); + +(function(Two) { + + var _ = Two.Utils; + + var LinearGradient = Two.LinearGradient = function(x1, y1, x2, y2, stops) { + + Two.Gradient.call(this, stops); + + this._renderer.type = 'linear-gradient'; + + var flagEndPoints = _.bind(LinearGradient.FlagEndPoints, this); + this.left = new Two.Vector().bind(Two.Events.change, flagEndPoints); + this.right = new Two.Vector().bind(Two.Events.change, flagEndPoints); + + if (_.isNumber(x1)) { + this.left.x = x1; + } + if (_.isNumber(y1)) { + this.left.y = y1; + } + if (_.isNumber(x2)) { + this.right.x = x2; + } + if (_.isNumber(y2)) { + this.right.y = y2; + } + + }; + + _.extend(LinearGradient, { + + Stop: Two.Gradient.Stop, + + MakeObservable: function(object) { + Two.Gradient.MakeObservable(object); + }, + + FlagEndPoints: function() { + this._flagEndPoints = true; + } + + }); + + _.extend(LinearGradient.prototype, Two.Gradient.prototype, { + + _flagEndPoints: false, + + clone: function(parent) { + + parent = parent || this.parent; + + var stops = _.map(this.stops, function(stop) { + return stop.clone(); + }); + + var clone = new LinearGradient(this.left._x, this.left._y, + this.right._x, this.right._y, stops); + + _.each(Two.Gradient.Properties, function(k) { + clone[k] = this[k]; + }, this); + + if (parent) { + parent.add(clone); + } + + return clone; + + }, + + toObject: function() { + + var result = Two.Gradient.prototype.toObject.call(this); + + result.left = this.left.toObject(); + result.right = this.right.toObject(); + + return result; + + }, + + _update: function() { + + if (this._flagEndPoints || this._flagSpread || this._flagStops) { + this.trigger(Two.Events.change); + } + + return this; + + }, + + flagReset: function() { + + this._flagEndPoints = false; + + Two.Gradient.prototype.flagReset.call(this); + + return this; + + } + + }); + + LinearGradient.MakeObservable(LinearGradient.prototype); + +})((typeof global !== 'undefined' ? global : this).Two); + +(function(Two) { + + var _ = Two.Utils; + + var RadialGradient = Two.RadialGradient = function(cx, cy, r, stops, fx, fy) { + + Two.Gradient.call(this, stops); + + this._renderer.type = 'radial-gradient'; + + this.center = new Two.Vector() + .bind(Two.Events.change, _.bind(function() { + this._flagCenter = true; + }, this)); + + this.radius = _.isNumber(r) ? r : 20; + + this.focal = new Two.Vector() + .bind(Two.Events.change, _.bind(function() { + this._flagFocal = true; + }, this)); + + if (_.isNumber(cx)) { + this.center.x = cx; + } + if (_.isNumber(cy)) { + this.center.y = cy; + } + + this.focal.copy(this.center); + + if (_.isNumber(fx)) { + this.focal.x = fx; + } + if (_.isNumber(fy)) { + this.focal.y = fy; + } + + }; + + _.extend(RadialGradient, { + + Stop: Two.Gradient.Stop, + + Properties: [ + 'radius' + ], + + MakeObservable: function(object) { + + Two.Gradient.MakeObservable(object); + + _.each(RadialGradient.Properties, Two.Utils.defineProperty, object); + + } + + }); + + _.extend(RadialGradient.prototype, Two.Gradient.prototype, { + + _flagRadius: false, + _flagCenter: false, + _flagFocal: false, + + clone: function(parent) { + + parent = parent || this.parent; + + var stops = _.map(this.stops, function(stop) { + return stop.clone(); + }); + + var clone = new RadialGradient(this.center._x, this.center._y, + this._radius, stops, this.focal._x, this.focal._y); + + _.each(Two.Gradient.Properties.concat(RadialGradient.Properties), function(k) { + clone[k] = this[k]; + }, this); + + if (parent) { + parent.add(clone); + } + + return clone; + + }, + + toObject: function() { + + var result = Two.Gradient.prototype.toObject.call(this); + + _.each(RadialGradient.Properties, function(k) { + result[k] = this[k]; + }, this); + + result.center = this.center.toObject(); + result.focal = this.focal.toObject(); + + return result; + + }, + + _update: function() { + + if (this._flagRadius || this._flatCenter || this._flagFocal + || this._flagSpread || this._flagStops) { + this.trigger(Two.Events.change); + } + + return this; + + }, + + flagReset: function() { + + this._flagRadius = this._flagCenter = this._flagFocal = false; + + Two.Gradient.prototype.flagReset.call(this); + + return this; + + } + + }); + + RadialGradient.MakeObservable(RadialGradient.prototype); + +})((typeof global !== 'undefined' ? global : this).Two); + +(function(Two) { + + var _ = Two.Utils; + var anchor; + var regex = { + video: /\.(mp4|webm)$/i, + image: /\.(jpe?g|png|gif|tiff)$/i + }; + + if (this.document) { + anchor = document.createElement('a'); + } + + var Texture = Two.Texture = function(src, callback) { + + this._renderer = {}; + this._renderer.type = 'texture'; + this._renderer.flagOffset = _.bind(Texture.FlagOffset, this); + this._renderer.flagScale = _.bind(Texture.FlagScale, this); + + this.id = Two.Identifier + Two.uniqueId(); + this.classList = []; + + this.offset = new Two.Vector(); + + if (_.isFunction(callback)) { + var loaded = _.bind(function() { + this.unbind(Two.Events.load, loaded); + if (_.isFunction(callback)) { + callback(); + } + }, this); + this.bind(Two.Events.load, loaded); + } + + if (_.isString(src)) { + this.src = src; + } else if (_.isElement(src)) { + this.image = src; + } + + this._update(); + + }; + + _.extend(Texture, { + + Properties: [ + 'src', + 'loaded', + 'repeat' + ], + + ImageRegistry: new Two.Registry(), + + getAbsoluteURL: function(path) { + if (!anchor) { + // TODO: Fix for headless environment + return path; + } + anchor.href = path; + return anchor.href; + }, + + getImage: function(src) { + + var absoluteSrc = Texture.getAbsoluteURL(src); + + if (Texture.ImageRegistry.contains(absoluteSrc)) { + return Texture.ImageRegistry.get(absoluteSrc); + } + + var image; + + if (regex.video.test(absoluteSrc)) { + image = document.createElement('video'); + } else { + image = document.createElement('img'); + } + + image.crossOrigin = 'anonymous'; + + return image; + + }, + + Register: { + canvas: function(texture, callback) { + texture._src = '#' + texture.id; + Texture.ImageRegistry.add(texture.src, texture.image); + if (_.isFunction(callback)) { + callback(); + } + }, + img: function(texture, callback) { + + var loaded = function(e) { + texture.image.removeEventListener('load', loaded, false); + texture.image.removeEventListener('error', error, false); + if (_.isFunction(callback)) { + callback(); + } + }; + var error = function(e) { + texture.image.removeEventListener('load', loaded, false); + texture.image.removeEventListener('error', error, false); + throw new Two.Utils.Error('unable to load ' + texture.src); + }; + + if (_.isNumber(texture.image.width) && texture.image.width > 0 + && _.isNumber(texture.image.height) && texture.image.height > 0) { + loaded(); + } else { + texture.image.addEventListener('load', loaded, false); + texture.image.addEventListener('error', error, false); + } + + texture._src = Texture.getAbsoluteURL(texture._src); + + if (texture.image && texture.image.getAttribute('two-src')) { + return; + } + + texture.image.setAttribute('two-src', texture.src); + Texture.ImageRegistry.add(texture.src, texture.image); + texture.image.src = texture.src; + + }, + video: function(texture, callback) { + + var loaded = function(e) { + texture.image.removeEventListener('load', loaded, false); + texture.image.removeEventListener('error', error, false); + texture.image.width = texture.image.videoWidth; + texture.image.height = texture.image.videoHeight; + texture.image.play(); + if (_.isFunction(callback)) { + callback(); + } + }; + var error = function(e) { + texture.image.removeEventListener('load', loaded, false); + texture.image.removeEventListener('error', error, false); + throw new Two.Utils.Error('unable to load ' + texture.src); + }; + + texture._src = Texture.getAbsoluteURL(texture._src); + texture.image.addEventListener('canplaythrough', loaded, false); + texture.image.addEventListener('error', error, false); + + if (texture.image && texture.image.getAttribute('two-src')) { + return; + } + + texture.image.setAttribute('two-src', texture.src); + Texture.ImageRegistry.add(texture.src, texture.image); + texture.image.src = texture.src; + texture.image.loop = true; + texture.image.load(); + + } + }, + + load: function(texture, callback) { + + var src = texture.src; + var image = texture.image; + var tag = image && image.nodeName.toLowerCase(); + + if (texture._flagImage) { + if (/canvas/i.test(tag)) { + Texture.Register.canvas(texture, callback); + } else { + texture._src = image.getAttribute('two-src') || image.src; + Texture.Register[tag](texture, callback); + } + } + + if (texture._flagSrc) { + if (!image) { + texture.image = Texture.getImage(texture.src); + } + tag = texture.image.nodeName.toLowerCase(); + Texture.Register[tag](texture, callback); + } + + }, + + FlagOffset: function() { + this._flagOffset = true; + }, + + FlagScale: function() { + this._flagScale = true; + }, + + MakeObservable: function(object) { + + _.each(Texture.Properties, Two.Utils.defineProperty, object); + + Object.defineProperty(object, 'image', { + enumerable: true, + get: function() { + return this._image; + }, + set: function(image) { + + var tag = image && image.nodeName.toLowerCase(); + var index; + + switch (tag) { + case 'canvas': + index = '#' + image.id; + break; + default: + index = image.src; + } + + if (Texture.ImageRegistry.contains(index)) { + this._image = Texture.ImageRegistry.get(image.src); + } else { + this._image = image; + } + + this._flagImage = true; + + } + + }); + + Object.defineProperty(object, 'offset', { + enumerable: true, + get: function() { + return this._offset; + }, + set: function(v) { + if (this._offset) { + this._offset.unbind(Two.Events.change, this._renderer.flagOffset); + } + this._offset = v; + this._offset.bind(Two.Events.change, this._renderer.flagOffset); + this._flagOffset = true; + } + }); + + Object.defineProperty(object, 'scale', { + enumerable: true, + get: function() { + return this._scale; + }, + set: function(v) { + + if (this._scale instanceof Two.Vector) { + this._scale.unbind(Two.Events.change, this._renderer.flagScale); + } + + this._scale = v; + + if (this._scale instanceof Two.Vector) { + this._scale.bind(Two.Events.change, this._renderer.flagScale); + } + + this._flagScale = true; + + } + }); + + } + + }); + + _.extend(Texture.prototype, Two.Utils.Events, Two.Shape.prototype, { + + _flagSrc: false, + _flagImage: false, + _flagVideo: false, + _flagLoaded: false, + _flagRepeat: false, + + _flagOffset: false, + _flagScale: false, + + _src: '', + _image: null, + _loaded: false, + _repeat: 'no-repeat', + + _scale: 1, + _offset: null, + + clone: function() { + return new Texture(this.src); + }, + + toObject: function() { + return { + src: this.src, + image: this.image + } + }, + + _update: function() { + + if (this._flagSrc || this._flagImage || this._flagVideo) { + + this.trigger(Two.Events.change); + + if (this._flagSrc || this._flagImage) { + this.loaded = false; + Texture.load(this, _.bind(function() { + this.loaded = true; + this + .trigger(Two.Events.change) + .trigger(Two.Events.load); + }, this)); + } + + } + + if (this._image && this._image.readyState >= 4) { + this._flagVideo = true; + } + + return this; + + }, + + flagReset: function() { + + this._flagSrc = this._flagImage = this._flagLoaded + = this._flagVideo = this._flagScale = this._flagOffset = false; + + return this; + + } + + }); + + Texture.MakeObservable(Texture.prototype); + +})((typeof global !== 'undefined' ? global : this).Two); + +(function(Two) { + + var _ = Two.Utils; + var Path = Two.Path; + var Rectangle = Two.Rectangle; + + var Sprite = Two.Sprite = function(path, ox, oy, cols, rows, frameRate) { + + Path.call(this, [ + new Two.Anchor(), + new Two.Anchor(), + new Two.Anchor(), + new Two.Anchor() + ], true); + + this.noStroke(); + this.noFill(); + + if (path instanceof Two.Texture) { + this.texture = path; + } else if (_.isString(path)) { + this.texture = new Two.Texture(path); + } + + this._update(); + this.translation.set(ox || 0, oy || 0); + + if (_.isNumber(cols)) { + this.columns = cols; + } + if (_.isNumber(rows)) { + this.rows = rows; + } + if (_.isNumber(frameRate)) { + this.frameRate = frameRate; + } + + }; + + _.extend(Sprite, { + + Properties: [ + 'texture', 'columns', 'rows', 'frameRate', 'index' + ], + + MakeObservable: function(obj) { + + Rectangle.MakeObservable(obj); + _.each(Sprite.Properties, Two.Utils.defineProperty, obj); + + } + + }) + + _.extend(Sprite.prototype, Rectangle.prototype, { + + _flagTexture: false, + _flagColumns: false, + _flagRows: false, + _flagFrameRate: false, + flagIndex: false, + + // Private variables + _amount: 1, + _duration: 0, + _startTime: 0, + _playing: false, + _firstFrame: 0, + _lastFrame: 0, + _loop: true, + + // Exposed through getter-setter + _texture: null, + _columns: 1, + _rows: 1, + _frameRate: 0, + _index: 0, + + play: function(firstFrame, lastFrame, onLastFrame) { + + this._playing = true; + this._firstFrame = 0; + this._lastFrame = this.amount - 1; + this._startTime = _.performance.now(); + + if (_.isNumber(firstFrame)) { + this._firstFrame = firstFrame; + } + if (_.isNumber(lastFrame)) { + this._lastFrame = lastFrame; + } + if (_.isFunction(onLastFrame)) { + this._onLastFrame = onLastFrame; + } else { + delete this._onLastFrame; + } + + if (this._index !== this._firstFrame) { + this._startTime -= 1000 * Math.abs(this._index - this._firstFrame) + / this._frameRate; + } + + return this; + + }, + + pause: function() { + + this._playing = false; + return this; + + }, + + stop: function() { + + this._playing = false; + this._index = 0; + + return this; + + }, + + clone: function(parent) { + + parent = parent || this.parent; + + var clone = new Sprite( + this.texture, this.translation.x, this.translation.y, + this.columns, this.rows, this.frameRate + ); + + if (this.playing) { + clone.play(this._firstFrame, this._lastFrame); + clone._loop = this._loop; + } + + if (parent) { + parent.add(clone); + } + + return clone; + + }, + + _update: function() { + + var effect = this._texture; + var cols = this._columns; + var rows = this._rows; + + var width, height, elapsed, amount, duration; + var index, iw, ih, isRange, frames; + + if (this._flagColumns || this._flagRows) { + this._amount = this._columns * this._rows; + } + + if (this._flagFrameRate) { + this._duration = 1000 * this._amount / this._frameRate; + } + + if (this._flagTexture) { + this.fill = this._texture; + } + + if (this._texture.loaded) { + + iw = effect.image.width; + ih = effect.image.height; + + width = iw / cols; + height = ih / rows; + amount = this._amount; + + if (this.width !== width) { + this.width = width; + } + if (this.height !== height) { + this.height = height; + } + + if (this._playing && this._frameRate > 0) { + + if (_.isNaN(this._lastFrame)) { + this._lastFrame = amount - 1; + } + + // TODO: Offload perf logic to instance of `Two`. + elapsed = _.performance.now() - this._startTime; + frames = this._lastFrame + 1; + duration = 1000 * (frames - this._firstFrame) / this._frameRate; + + if (this._loop) { + elapsed = elapsed % duration; + } else { + elapsed = Math.min(elapsed, duration); + } + + index = _.lerp(this._firstFrame, frames, elapsed / duration); + index = Math.floor(index); + + if (index !== this._index) { + this._index = index; + if (index >= this._lastFrame - 1 && this._onLastFrame) { + this._onLastFrame(); // Shortcut for chainable sprite animations + } + } + + } + + var col = this._index % cols; + var row = Math.floor(this._index / cols); + + var ox = - width * col + (iw - width) / 2; + var oy = - height * row + (ih - height) / 2; + + // TODO: Improve performance + if (ox !== effect.offset.x) { + effect.offset.x = ox; + } + if (oy !== effect.offset.y) { + effect.offset.y = oy; + } + + } + + Rectangle.prototype._update.call(this); + + return this; + + }, + + flagReset: function() { + + this._flagTexture = this._flagColumns = this._flagRows + = this._flagFrameRate = false; + + Rectangle.prototype.flagReset.call(this); + + return this; + } + + + }); + + Sprite.MakeObservable(Sprite.prototype); + +})((typeof global !== 'undefined' ? global : this).Two); + +(function(Two) { + + var _ = Two.Utils; + var Path = Two.Path; + var Rectangle = Two.Rectangle; + + var ImageSequence = Two.ImageSequence = function(paths, ox, oy, frameRate) { + + Path.call(this, [ + new Two.Anchor(), + new Two.Anchor(), + new Two.Anchor(), + new Two.Anchor() + ], true); + + this._renderer.flagTextures = _.bind(ImageSequence.FlagTextures, this); + this._renderer.bindTextures = _.bind(ImageSequence.BindTextures, this); + this._renderer.unbindTextures = _.bind(ImageSequence.UnbindTextures, this); + + this.noStroke(); + this.noFill(); + + this.textures = _.map(paths, ImageSequence.GenerateTexture, this); + + this._update(); + this.translation.set(ox || 0, oy || 0); + + if (_.isNumber(frameRate)) { + this.frameRate = frameRate; + } else { + this.frameRate = ImageSequence.DefaultFrameRate; + } + + }; + + _.extend(ImageSequence, { + + Properties: [ + 'frameRate', + 'index' + ], + + DefaultFrameRate: 30, + + FlagTextures: function() { + this._flagTextures = true; + }, + + BindTextures: function(items) { + + var i = items.length; + while (i--) { + items[i].bind(Two.Events.change, this._renderer.flagTextures); + } + + this._renderer.flagTextures(); + + }, + + UnbindTextures: function(items) { + + var i = items.length; + while (i--) { + items[i].unbind(Two.Events.change, this._renderer.flagTextures); + } + + this._renderer.flagTextures(); + + }, + + MakeObservable: function(obj) { + + Rectangle.MakeObservable(obj); + _.each(ImageSequence.Properties, Two.Utils.defineProperty, obj); + + Object.defineProperty(obj, 'textures', { + + enumerable: true, + + get: function() { + return this._textures; + }, + + set: function(textures) { + + var updateTextures = this._renderer.flagTextures; + var bindTextures = this._renderer.bindTextures; + var unbindTextures = this._renderer.unbindTextures; + + // Remove previous listeners + if (this._textures) { + this._textures + .unbind(Two.Events.insert, bindTextures) + .unbind(Two.Events.remove, unbindTextures); + } + + // Create new Collection with copy of vertices + this._textures = new Two.Utils.Collection((textures || []).slice(0)); + + // Listen for Collection changes and bind / unbind + this._textures + .bind(Two.Events.insert, bindTextures) + .bind(Two.Events.remove, unbindTextures); + + // Bind Initial Textures + bindTextures(this._textures); + + } + + }); + + }, + + GenerateTexture: function(obj) { + if (obj instanceof Two.Texture) { + return obj; + } else if (_.isString(obj)) { + return new Two.Texture(obj); + } + } + + }); + + _.extend(ImageSequence.prototype, Rectangle.prototype, { + + _flagTextures: false, + _flagFrameRate: false, + _flagIndex: false, + + // Private variables + _amount: 1, + _duration: 0, + _index: 0, + _startTime: 0, + _playing: false, + _firstFrame: 0, + _lastFrame: 0, + _loop: true, + + // Exposed through getter-setter + _textures: null, + _frameRate: 0, + + play: function(firstFrame, lastFrame, onLastFrame) { + + this._playing = true; + this._firstFrame = 0; + this._lastFrame = this.amount - 1; + this._startTime = _.performance.now(); + + if (_.isNumber(firstFrame)) { + this._firstFrame = firstFrame; + } + if (_.isNumber(lastFrame)) { + this._lastFrame = lastFrame; + } + if (_.isFunction(onLastFrame)) { + this._onLastFrame = onLastFrame; + } else { + delete this._onLastFrame; + } + + if (this._index !== this._firstFrame) { + this._startTime -= 1000 * Math.abs(this._index - this._firstFrame) + / this._frameRate; + } + + return this; + + }, + + pause: function() { + + this._playing = false; + return this; + + }, + + stop: function() { + + this._playing = false; + this._index = 0; + + return this; + + }, + + clone: function(parent) { + + parent = parent || this.parent; + + var clone = new ImageSequence(this.textures, this.translation.x, + this.translation.y, this.frameRate) + + clone._loop = this._loop; + + if (this._playing) { + clone.play(); + } + + if (parent) { + parent.add(clone); + } + + return clone; + + }, + + _update: function() { + + var effects = this._textures; + var width, height, elapsed, amount, duration, texture; + var index, frames; + + if (this._flagTextures) { + this._amount = effects.length; + } + + if (this._flagFrameRate) { + this._duration = 1000 * this._amount / this._frameRate; + } + + if (this._playing && this._frameRate > 0) { + + amount = this._amount; + + if (_.isNaN(this._lastFrame)) { + this._lastFrame = amount - 1; + } + + // TODO: Offload perf logic to instance of `Two`. + elapsed = _.performance.now() - this._startTime; + frames = this._lastFrame + 1; + duration = 1000 * (frames - this._firstFrame) / this._frameRate; + + if (this._loop) { + elapsed = elapsed % duration; + } else { + elapsed = Math.min(elapsed, duration); + } + + index = _.lerp(this._firstFrame, frames, elapsed / duration); + index = Math.floor(index); + + if (index !== this._index) { + + this._index = index; + texture = effects[this._index]; + + if (texture.loaded) { + + width = texture.image.width; + height = texture.image.height; + + if (this.width !== width) { + this.width = width; + } + if (this.height !== height) { + this.height = height; + } + + this.fill = texture; + + if (index >= this._lastFrame - 1 && this._onLastFrame) { + this._onLastFrame(); // Shortcut for chainable sprite animations + } + + } + + } + + } else if (this._flagIndex || !(this.fill instanceof Two.Texture)) { + + texture = effects[this._index]; + + if (texture.loaded) { + + width = texture.image.width; + height = texture.image.height; + + if (this.width !== width) { + this.width = width; + } + if (this.height !== height) { + this.height = height; + } + + } + + this.fill = texture; + + } + + Rectangle.prototype._update.call(this); + + return this; + + }, + + flagReset: function() { + + this._flagTextures = this._flagFrameRate = false; + Rectangle.prototype.flagReset.call(this); + + return this; + + } + + }); + + ImageSequence.MakeObservable(ImageSequence.prototype); + +})((typeof global !== 'undefined' ? global : this).Two); + +(function(Two) { + + /** + * Constants + */ + var min = Math.min, max = Math.max; + var _ = Two.Utils; + + /** + * A children collection which is accesible both by index and by object id + * @constructor + */ + var Children = function() { + + Two.Utils.Collection.apply(this, arguments); + + Object.defineProperty(this, '_events', { + value : {}, + enumerable: false + }); + + this.ids = {}; + + this.on(Two.Events.insert, this.attach); + this.on(Two.Events.remove, this.detach); + Children.prototype.attach.apply(this, arguments); + + }; + + Children.prototype = new Two.Utils.Collection(); + Children.prototype.constructor = Children; + + _.extend(Children.prototype, { + + attach: function(children) { + for (var i = 0; i < children.length; i++) { + this.ids[children[i].id] = children[i]; + } + return this; + }, + + detach: function(children) { + for (var i = 0; i < children.length; i++) { + delete this.ids[children[i].id]; + } + return this; + } + + }); + + var Group = Two.Group = function() { + + Two.Shape.call(this, true); + + this._renderer.type = 'group'; + + this.additions = []; + this.subtractions = []; + + this.children = arguments; + + }; + + _.extend(Group, { + + Children: Children, + + InsertChildren: function(children) { + for (var i = 0; i < children.length; i++) { + replaceParent.call(this, children[i], this); + } + }, + + RemoveChildren: function(children) { + for (var i = 0; i < children.length; i++) { + replaceParent.call(this, children[i]); + } + }, + + OrderChildren: function(children) { + this._flagOrder = true; + }, + + MakeObservable: function(object) { + + var properties = Two.Path.Properties.slice(0); + var oi = _.indexOf(properties, 'opacity'); + + if (oi >= 0) { + + properties.splice(oi, 1); + + Object.defineProperty(object, 'opacity', { + + enumerable: true, + + get: function() { + return this._opacity; + }, + + set: function(v) { + // Only set flag if there is an actual difference + this._flagOpacity = (this._opacity != v); + this._opacity = v; + } + + }); + + } + + Two.Shape.MakeObservable(object); + Group.MakeGetterSetters(object, properties); + + Object.defineProperty(object, 'children', { + + enumerable: true, + + get: function() { + return this._children; + }, + + set: function(children) { + + var insertChildren = _.bind(Group.InsertChildren, this); + var removeChildren = _.bind(Group.RemoveChildren, this); + var orderChildren = _.bind(Group.OrderChildren, this); + + if (this._children) { + this._children.unbind(); + } + + this._children = new Children(children); + this._children.bind(Two.Events.insert, insertChildren); + this._children.bind(Two.Events.remove, removeChildren); + this._children.bind(Two.Events.order, orderChildren); + + } + + }); + + Object.defineProperty(object, 'mask', { + + enumerable: true, + + get: function() { + return this._mask; + }, + + set: function(v) { + this._mask = v; + this._flagMask = true; + if (!v.clip) { + v.clip = true; + } + } + + }); + + }, + + MakeGetterSetters: function(group, properties) { + + if (!_.isArray(properties)) { + properties = [properties]; + } + + _.each(properties, function(k) { + Group.MakeGetterSetter(group, k); + }); + + }, + + MakeGetterSetter: function(group, k) { + + var secret = '_' + k; + + Object.defineProperty(group, k, { + + enumerable: true, + + get: function() { + return this[secret]; + }, + + set: function(v) { + this[secret] = v; + _.each(this.children, function(child) { // Trickle down styles + child[k] = v; + }); + } + + }); + + } + + }); + + _.extend(Group.prototype, Two.Shape.prototype, { + + // Flags + // http://en.wikipedia.org/wiki/Flag + + _flagAdditions: false, + _flagSubtractions: false, + _flagOrder: false, + _flagOpacity: true, + + _flagMask: false, + + // Underlying Properties + + _fill: '#fff', + _stroke: '#000', + _linewidth: 1.0, + _opacity: 1.0, + _visible: true, + + _cap: 'round', + _join: 'round', + _miter: 4, + + _closed: true, + _curved: false, + _automatic: true, + _beginning: 0, + _ending: 1.0, + + _mask: null, + + /** + * TODO: Group has a gotcha in that it's at the moment required to be bound to + * an instance of two in order to add elements correctly. This needs to + * be rethought and fixed. + */ + clone: function(parent) { + + parent = parent || this.parent; + + var group = new Group(); + var children = _.map(this.children, function(child) { + return child.clone(group); + }); + + group.add(children); + + group.opacity = this.opacity; + + if (this.mask) { + group.mask = this.mask; + } + + group.translation.copy(this.translation); + group.rotation = this.rotation; + group.scale = this.scale; + + if (parent) { + parent.add(group); + } + + return group; + + }, + + /** + * Export the data from the instance of Two.Group into a plain JavaScript + * object. This also makes all children plain JavaScript objects. Great + * for turning into JSON and storing in a database. + */ + toObject: function() { + + var result = { + children: [], + translation: this.translation.toObject(), + rotation: this.rotation, + scale: this.scale, + opacity: this.opacity, + mask: (this.mask ? this.mask.toObject() : null) + }; + + _.each(this.children, function(child, i) { + result.children[i] = child.toObject(); + }, this); + + return result; + + }, + + /** + * Anchor all children to the upper left hand corner + * of the group. + */ + corner: function() { + + var rect = this.getBoundingClientRect(true), + corner = { x: rect.left, y: rect.top }; + + this.children.forEach(function(child) { + child.translation.subSelf(corner); + }); + + return this; + + }, + + /** + * Anchors all children around the center of the group, + * effectively placing the shape around the unit circle. + */ + center: function() { + + var rect = this.getBoundingClientRect(true); + + rect.centroid = { + x: rect.left + rect.width / 2, + y: rect.top + rect.height / 2 + }; + + this.children.forEach(function(child) { + if (child.isShape) { + child.translation.subSelf(rect.centroid); + } + }); + + // this.translation.copy(rect.centroid); + + return this; + + }, + + /** + * Recursively search for id. Returns the first element found. + * Returns null if none found. + */ + getById: function (id) { + var search = function (node, id) { + if (node.id === id) { + return node; + } else if (node.children) { + var i = node.children.length; + while (i--) { + var found = search(node.children[i], id); + if (found) return found; + } + } + + }; + return search(this, id) || null; + }, + + /** + * Recursively search for classes. Returns an array of matching elements. + * Empty array if none found. + */ + getByClassName: function (cl) { + var found = []; + var search = function (node, cl) { + if (node.classList.indexOf(cl) != -1) { + found.push(node); + } else if (node.children) { + node.children.forEach(function (child) { + search(child, cl); + }); + } + return found; + }; + return search(this, cl); + }, + + /** + * Recursively search for children of a specific type, + * e.g. Two.Polygon. Pass a reference to this type as the param. + * Returns an empty array if none found. + */ + getByType: function(type) { + var found = []; + var search = function (node, type) { + for (var id in node.children) { + if (node.children[id] instanceof type) { + found.push(node.children[id]); + } else if (node.children[id] instanceof Two.Group) { + search(node.children[id], type); + } + } + return found; + }; + return search(this, type); + }, + + /** + * Add objects to the group. + */ + add: function(objects) { + + // Allow to pass multiple objects either as array or as multiple arguments + // If it's an array also create copy of it in case we're getting passed + // a childrens array directly. + if (!(objects instanceof Array)) { + objects = _.toArray(arguments); + } else { + objects = objects.slice(); + } + + // Add the objects + for (var i = 0; i < objects.length; i++) { + if (!(objects[i] && objects[i].id)) continue; + this.children.push(objects[i]); + } + + return this; + + }, + + /** + * Remove objects from the group. + */ + remove: function(objects) { + + var l = arguments.length, + grandparent = this.parent; + + // Allow to call remove without arguments + // This will detach the object from the scene. + if (l <= 0 && grandparent) { + grandparent.remove(this); + return this; + } + + // Allow to pass multiple objects either as array or as multiple arguments + // If it's an array also create copy of it in case we're getting passed + // a childrens array directly. + if (!(objects instanceof Array)) { + objects = _.toArray(arguments); + } else { + objects = objects.slice(); + } + + // Remove the objects + for (var i = 0; i < objects.length; i++) { + if (!objects[i] || !(this.children.ids[objects[i].id])) continue; + this.children.splice(_.indexOf(this.children, objects[i]), 1); + } + + return this; + + }, + + /** + * Return an object with top, left, right, bottom, width, and height + * parameters of the group. + */ + getBoundingClientRect: function(shallow) { + var rect; + + // TODO: Update this to not __always__ update. Just when it needs to. + this._update(true); + + // Variables need to be defined here, because of nested nature of groups. + var left = Infinity, right = -Infinity, + top = Infinity, bottom = -Infinity; + + this.children.forEach(function(child) { + + if (/(linear-gradient|radial-gradient|gradient)/.test(child._renderer.type)) { + return; + } + + rect = child.getBoundingClientRect(shallow); + + if (!_.isNumber(rect.top) || !_.isNumber(rect.left) || + !_.isNumber(rect.right) || !_.isNumber(rect.bottom)) { + return; + } + + top = min(rect.top, top); + left = min(rect.left, left); + right = max(rect.right, right); + bottom = max(rect.bottom, bottom); + + }, this); + + return { + top: top, + left: left, + right: right, + bottom: bottom, + width: right - left, + height: bottom - top + }; + + }, + + /** + * Trickle down of noFill + */ + noFill: function() { + this.children.forEach(function(child) { + child.noFill(); + }); + return this; + }, + + /** + * Trickle down of noStroke + */ + noStroke: function() { + this.children.forEach(function(child) { + child.noStroke(); + }); + return this; + }, + + /** + * Trickle down subdivide + */ + subdivide: function() { + var args = arguments; + this.children.forEach(function(child) { + child.subdivide.apply(child, args); + }); + return this; + }, + + flagReset: function() { + + if (this._flagAdditions) { + this.additions.length = 0; + this._flagAdditions = false; + } + + if (this._flagSubtractions) { + this.subtractions.length = 0; + this._flagSubtractions = false; + } + + this._flagOrder = this._flagMask = this._flagOpacity = false; + + Two.Shape.prototype.flagReset.call(this); + + return this; + + } + + }); + + Group.MakeObservable(Group.prototype); + + /** + * Helper function used to sync parent-child relationship within the + * `Two.Group.children` object. + * + * Set the parent of the passed object to another object + * and updates parent-child relationships + * Calling with one arguments will simply remove the parenting + */ + function replaceParent(child, newParent) { + + var parent = child.parent; + var index; + + if (parent === newParent) { + this.additions.push(child); + this._flagAdditions = true; + return; + } + + if (parent && parent.children.ids[child.id]) { + + index = _.indexOf(parent.children, child); + parent.children.splice(index, 1); + + // If we're passing from one parent to another... + index = _.indexOf(parent.additions, child); + + if (index >= 0) { + parent.additions.splice(index, 1); + } else { + parent.subtractions.push(child); + parent._flagSubtractions = true; + } + + } + + if (newParent) { + child.parent = newParent; + this.additions.push(child); + this._flagAdditions = true; + return; + } + + // If we're passing from one parent to another... + index = _.indexOf(this.additions, child); + + if (index >= 0) { + this.additions.splice(index, 1); + } else { + this.subtractions.push(child); + this._flagSubtractions = true; + } + + delete child.parent; + + } + +})((typeof global !== 'undefined' ? global : this).Two); |