integ/ceph/ceph/debian/deb_folder/missing-sources/two.js

10245 lines
247 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 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);