Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- /**
- * 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);
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement