· 6 years ago · Aug 29, 2019, 08:00 AM
1/**
2* two.js
3* a two-dimensional drawing api meant for modern browsers. It is renderer
4* agnostic enabling the same api for rendering in multiple contexts: webgl,
5* canvas2d, and svg.
6*
7* Copyright (c) 2012 - 2017 jonobr1 / http://jonobr1.com
8*
9* Permission is hereby granted, free of charge, to any person obtaining a copy
10* of this software and associated documentation files (the "Software"), to deal
11* in the Software without restriction, including without limitation the rights
12* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13* copies of the Software, and to permit persons to whom the Software is
14* furnished to do so, subject to the following conditions:
15*
16* The above copyright notice and this permission notice shall be included in
17* all copies or substantial portions of the Software.
18*
19* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25* THE SOFTWARE.
26*
27*/
28
29this.Two = (function (previousTwo) {
30
31 var root = typeof window != 'undefined' ? window : typeof global != 'undefined' ? global : null;
32 var toString = Object.prototype.toString;
33 var _ = {
34 // http://underscorejs.org/ • 1.8.3
35 _indexAmount: 0,
36 natural: {
37 slice: Array.prototype.slice,
38 indexOf: Array.prototype.indexOf,
39 keys: Object.keys,
40 bind: Function.prototype.bind,
41 create: Object.create
42 },
43 identity: function (value) {
44 return value;
45 },
46 isArguments: function (obj) {
47 return toString.call(obj) === '[object Arguments]';
48 },
49 isFunction: function (obj) {
50 return toString.call(obj) === '[object Function]';
51 },
52 isString: function (obj) {
53 return toString.call(obj) === '[object String]';
54 },
55 isNumber: function (obj) {
56 return toString.call(obj) === '[object Number]';
57 },
58 isDate: function (obj) {
59 return toString.call(obj) === '[object Date]';
60 },
61 isRegExp: function (obj) {
62 return toString.call(obj) === '[object RegExp]';
63 },
64 isError: function (obj) {
65 return toString.call(obj) === '[object Error]';
66 },
67 isFinite: function (obj) {
68 return isFinite(obj) && !isNaN(parseFloat(obj));
69 },
70 isNaN: function (obj) {
71 return _.isNumber(obj) && obj !== +obj;
72 },
73 isBoolean: function (obj) {
74 return obj === true || obj === false || toString.call(obj) === '[object Boolean]';
75 },
76 isNull: function (obj) {
77 return obj === null;
78 },
79 isUndefined: function (obj) {
80 return obj === void 0;
81 },
82 isEmpty: function (obj) {
83 if (obj == null) return true;
84 if (isArrayLike && (_.isArray(obj) || _.isString(obj) || _.isArguments(obj))) return obj.length === 0;
85 return _.keys(obj).length === 0;
86 },
87 isElement: function (obj) {
88 return !!(obj && obj.nodeType === 1);
89 },
90 isArray: Array.isArray || function (obj) {
91 return toString.call(obj) === '[object Array]';
92 },
93 isObject: function (obj) {
94 var type = typeof obj;
95 return type === 'function' || type === 'object' && !!obj;
96 },
97 toArray: function (obj) {
98 if (!obj) {
99 return [];
100 }
101 if (_.isArray(obj)) {
102 return slice.call(obj);
103 }
104 if (isArrayLike(obj)) {
105 return _.map(obj, _.identity);
106 }
107 return _.values(obj);
108 },
109 range: function (start, stop, step) {
110 if (stop == null) {
111 stop = start || 0;
112 start = 0;
113 }
114 step = step || 1;
115
116 var length = Math.max(Math.ceil((stop - start) / step), 0);
117 var range = Array(length);
118
119 for (var idx = 0; idx < length; idx++ , start += step) {
120 range[idx] = start;
121 }
122
123 return range;
124 },
125 indexOf: function (list, item) {
126 if (!!_.natural.indexOf) {
127 return _.natural.indexOf.call(list, item);
128 }
129 for (var i = 0; i < list.length; i++) {
130 if (list[i] === item) {
131 return i;
132 }
133 }
134 return -1;
135 },
136 has: function (obj, key) {
137 return obj != null && hasOwnProperty.call(obj, key);
138 },
139 bind: function (func, ctx) {
140 var natural = _.natural.bind;
141 if (natural && func.bind === natural) {
142 return natural.apply(func, slice.call(arguments, 1));
143 }
144 var args = slice.call(arguments, 2);
145 return function () {
146 func.apply(ctx, args);
147 };
148 },
149 extend: function (base) {
150 var sources = slice.call(arguments, 1);
151 for (var i = 0; i < sources.length; i++) {
152 var obj = sources[i];
153 for (var k in obj) {
154 base[k] = obj[k];
155 }
156 }
157 return base;
158 },
159 defaults: function (base) {
160 var sources = slice.call(arguments, 1);
161 for (var i = 0; i < sources.length; i++) {
162 var obj = sources[i];
163 for (var k in obj) {
164 if (base[k] === void 0) {
165 base[k] = obj[k];
166 }
167 }
168 }
169 return base;
170 },
171 keys: function (obj) {
172 if (!_.isObject(obj)) {
173 return [];
174 }
175 if (_.natural.keys) {
176 return _.natural.keys(obj);
177 }
178 var keys = [];
179 for (var k in obj) {
180 if (_.has(obj, k)) {
181 keys.push(k);
182 }
183 }
184 return keys;
185 },
186 values: function (obj) {
187 var keys = _.keys(obj);
188 var values = [];
189 for (var i = 0; i < keys.length; i++) {
190 var k = keys[i];
191 values.push(obj[k]);
192 }
193 return values;
194 },
195 each: function (obj, iteratee, context) {
196 var ctx = context || this;
197 var keys = !isArrayLike(obj) && _.keys(obj);
198 var length = (keys || obj).length;
199 for (var i = 0; i < length; i++) {
200 var k = keys ? keys[i] : i;
201 iteratee.call(ctx, obj[k], k, obj);
202 }
203 return obj;
204 },
205 map: function (obj, iteratee, context) {
206 var ctx = context || this;
207 var keys = !isArrayLike(obj) && _.keys(obj);
208 var length = (keys || obj).length;
209 var result = [];
210 for (var i = 0; i < length; i++) {
211 var k = keys ? keys[i] : i;
212 result[i] = iteratee.call(ctx, obj[k], k, obj);
213 }
214 return result;
215 },
216 once: function (func) {
217 var init = false;
218 return function () {
219 if (!!init) {
220 return func;
221 }
222 init = true;
223 return func.apply(this, arguments);
224 }
225 },
226 after: function (times, func) {
227 return function () {
228 while (--times < 1) {
229 return func.apply(this, arguments);
230 }
231 }
232 },
233 uniqueId: function (prefix) {
234 var id = ++_._indexAmount + '';
235 return prefix ? prefix + id : id;
236 }
237 };
238
239 /**
240 * Constants
241 */
242
243 var sin = Math.sin,
244 cos = Math.cos,
245 atan2 = Math.atan2,
246 sqrt = Math.sqrt,
247 round = Math.round,
248 abs = Math.abs,
249 PI = Math.PI,
250 TWO_PI = PI * 2,
251 HALF_PI = PI / 2,
252 pow = Math.pow,
253 min = Math.min,
254 max = Math.max;
255
256 /**
257 * Localized variables
258 */
259
260 var count = 0;
261 var slice = _.natural.slice;
262 var perf = ((root.performance && root.performance.now) ? root.performance : Date);
263 var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1;
264 var getLength = function (obj) {
265 return obj == null ? void 0 : obj['length'];
266 };
267 var isArrayLike = function (collection) {
268 var length = getLength(collection);
269 return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX;
270 };
271
272 /**
273 * Cross browser dom events.
274 */
275 var dom = {
276
277 temp: (root.document ? root.document.createElement('div') : {}),
278
279 hasEventListeners: _.isFunction(root.addEventListener),
280
281 bind: function (elem, event, func, bool) {
282 if (this.hasEventListeners) {
283 elem.addEventListener(event, func, !!bool);
284 } else {
285 elem.attachEvent('on' + event, func);
286 }
287 return dom;
288 },
289
290 unbind: function (elem, event, func, bool) {
291 if (dom.hasEventListeners) {
292 elem.removeEventListeners(event, func, !!bool);
293 } else {
294 elem.detachEvent('on' + event, func);
295 }
296 return dom;
297 },
298
299 getRequestAnimationFrame: function () {
300
301 var lastTime = 0;
302 var vendors = ['ms', 'moz', 'webkit', 'o'];
303 var request = root.requestAnimationFrame, cancel;
304
305 if (!request) {
306 for (var i = 0; i < vendors.length; i++) {
307 request = root[vendors[i] + 'RequestAnimationFrame'] || request;
308 cancel = root[vendors[i] + 'CancelAnimationFrame']
309 || root[vendors[i] + 'CancelRequestAnimationFrame'] || cancel;
310 }
311
312 request = request || function (callback, element) {
313 var currTime = new Date().getTime();
314 var timeToCall = Math.max(0, 16 - (currTime - lastTime));
315 var id = root.setTimeout(function () { callback(currTime + timeToCall); }, timeToCall);
316 lastTime = currTime + timeToCall;
317 return id;
318 };
319 // cancel = cancel || function(id) {
320 // clearTimeout(id);
321 // };
322 }
323
324 request.init = _.once(loop);
325
326 return request;
327
328 }
329
330 };
331
332 /**
333 * @class
334 */
335 var Two = root.Two = function (options) {
336
337 // Determine what Renderer to use and setup a scene.
338
339 var params = _.defaults(options || {}, {
340 fullscreen: false,
341 width: 640,
342 height: 480,
343 type: Two.Types.svg,
344 autostart: false
345 });
346
347 _.each(params, function (v, k) {
348 if (k === 'fullscreen' || k === 'autostart') {
349 return;
350 }
351 this[k] = v;
352 }, this);
353
354 // Specified domElement overrides type declaration only if the element does not support declared renderer type.
355 if (_.isElement(params.domElement)) {
356 var tagName = params.domElement.tagName.toLowerCase();
357 // TODO: Reconsider this if statement's logic.
358 if (!/^(CanvasRenderer-canvas|WebGLRenderer-canvas|SVGRenderer-svg)$/.test(this.type + '-' + tagName)) {
359 this.type = Two.Types[tagName];
360 }
361 }
362
363 this.renderer = new Two[this.type](this);
364 Two.Utils.setPlaying.call(this, params.autostart);
365 this.frameCount = 0;
366
367 if (params.fullscreen) {
368
369 var fitted = _.bind(fitToWindow, this);
370 _.extend(document.body.style, {
371 overflow: 'hidden',
372 margin: 0,
373 padding: 0,
374 top: 0,
375 left: 0,
376 right: 0,
377 bottom: 0,
378 position: 'fixed'
379 });
380 _.extend(this.renderer.domElement.style, {
381 display: 'block',
382 top: 0,
383 left: 0,
384 right: 0,
385 bottom: 0,
386 position: 'fixed'
387 });
388 dom.bind(root, 'resize', fitted);
389 fitted();
390
391
392 } else if (!_.isElement(params.domElement)) {
393
394 this.renderer.setSize(params.width, params.height, this.ratio);
395 this.width = params.width;
396 this.height = params.height;
397
398 }
399
400 this.scene = this.renderer.scene;
401
402 Two.Instances.push(this);
403 raf.init();
404
405 };
406
407 _.extend(Two, {
408
409 /**
410 * Access to root in other files.
411 */
412
413 root: root,
414
415 /**
416 * Primitive
417 */
418
419 Array: root.Float32Array || Array,
420
421 Types: {
422 webgl: 'WebGLRenderer',
423 svg: 'SVGRenderer',
424 canvas: 'CanvasRenderer'
425 },
426
427 Version: 'v0.7.0',
428
429 Identifier: 'two_',
430
431 Properties: {
432 hierarchy: 'hierarchy',
433 demotion: 'demotion'
434 },
435
436 Events: {
437 play: 'play',
438 pause: 'pause',
439 update: 'update',
440 render: 'render',
441 resize: 'resize',
442 change: 'change',
443 remove: 'remove',
444 insert: 'insert',
445 order: 'order',
446 load: 'load'
447 },
448
449 Commands: {
450 move: 'M',
451 line: 'L',
452 curve: 'C',
453 close: 'Z'
454 },
455
456 Resolution: 8,
457
458 Instances: [],
459
460 noConflict: function () {
461 root.Two = previousTwo;
462 return this;
463 },
464
465 uniqueId: function () {
466 var id = count;
467 count++;
468 return id;
469 },
470
471 Utils: _.extend(_, {
472
473 performance: perf,
474
475 defineProperty: function (property) {
476
477 var object = this;
478 var secret = '_' + property;
479 var flag = '_flag' + property.charAt(0).toUpperCase() + property.slice(1);
480
481 Object.defineProperty(object, property, {
482 enumerable: true,
483 get: function () {
484 return this[secret];
485 },
486 set: function (v) {
487 this[secret] = v;
488 this[flag] = true;
489 }
490 });
491
492 },
493
494 /**
495 * Release an arbitrary class' events from the two.js corpus and recurse
496 * through its children and or vertices.
497 */
498 release: function (obj) {
499
500 if (!_.isObject(obj)) {
501 return;
502 }
503
504 if (_.isFunction(obj.unbind)) {
505 obj.unbind();
506 }
507
508 if (obj.vertices) {
509 if (_.isFunction(obj.vertices.unbind)) {
510 obj.vertices.unbind();
511 }
512 _.each(obj.vertices, function (v) {
513 if (_.isFunction(v.unbind)) {
514 v.unbind();
515 }
516 });
517 }
518
519 if (obj.children) {
520 _.each(obj.children, function (obj) {
521 Two.Utils.release(obj);
522 });
523 }
524
525 },
526
527 xhr: function (path, callback) {
528
529 var xhr = new XMLHttpRequest();
530 xhr.open('GET', path);
531
532 xhr.onreadystatechange = function () {
533 if (xhr.readyState === 4 && xhr.status === 200) {
534 callback(xhr.responseText);
535 }
536 };
537
538 xhr.send();
539 return xhr;
540
541 },
542
543 Curve: {
544
545 CollinearityEpsilon: pow(10, -30),
546
547 RecursionLimit: 16,
548
549 CuspLimit: 0,
550
551 Tolerance: {
552 distance: 0.25,
553 angle: 0,
554 epsilon: 0.01
555 },
556
557 // Lookup tables for abscissas and weights with values for n = 2 .. 16.
558 // As values are symmetric, only store half of them and adapt algorithm
559 // to factor in symmetry.
560 abscissas: [
561 [0.5773502691896257645091488],
562 [0, 0.7745966692414833770358531],
563 [0.3399810435848562648026658, 0.8611363115940525752239465],
564 [0, 0.5384693101056830910363144, 0.9061798459386639927976269],
565 [0.2386191860831969086305017, 0.6612093864662645136613996, 0.9324695142031520278123016],
566 [0, 0.4058451513773971669066064, 0.7415311855993944398638648, 0.9491079123427585245261897],
567 [0.1834346424956498049394761, 0.5255324099163289858177390, 0.7966664774136267395915539, 0.9602898564975362316835609],
568 [0, 0.3242534234038089290385380, 0.6133714327005903973087020, 0.8360311073266357942994298, 0.9681602395076260898355762],
569 [0.1488743389816312108848260, 0.4333953941292471907992659, 0.6794095682990244062343274, 0.8650633666889845107320967, 0.9739065285171717200779640],
570 [0, 0.2695431559523449723315320, 0.5190961292068118159257257, 0.7301520055740493240934163, 0.8870625997680952990751578, 0.9782286581460569928039380],
571 [0.1252334085114689154724414, 0.3678314989981801937526915, 0.5873179542866174472967024, 0.7699026741943046870368938, 0.9041172563704748566784659, 0.9815606342467192506905491],
572 [0, 0.2304583159551347940655281, 0.4484927510364468528779129, 0.6423493394403402206439846, 0.8015780907333099127942065, 0.9175983992229779652065478, 0.9841830547185881494728294],
573 [0.1080549487073436620662447, 0.3191123689278897604356718, 0.5152486363581540919652907, 0.6872929048116854701480198, 0.8272013150697649931897947, 0.9284348836635735173363911, 0.9862838086968123388415973],
574 [0, 0.2011940939974345223006283, 0.3941513470775633698972074, 0.5709721726085388475372267, 0.7244177313601700474161861, 0.8482065834104272162006483, 0.9372733924007059043077589, 0.9879925180204854284895657],
575 [0.0950125098376374401853193, 0.2816035507792589132304605, 0.4580167776572273863424194, 0.6178762444026437484466718, 0.7554044083550030338951012, 0.8656312023878317438804679, 0.9445750230732325760779884, 0.9894009349916499325961542]
576 ],
577
578 weights: [
579 [1],
580 [0.8888888888888888888888889, 0.5555555555555555555555556],
581 [0.6521451548625461426269361, 0.3478548451374538573730639],
582 [0.5688888888888888888888889, 0.4786286704993664680412915, 0.2369268850561890875142640],
583 [0.4679139345726910473898703, 0.3607615730481386075698335, 0.1713244923791703450402961],
584 [0.4179591836734693877551020, 0.3818300505051189449503698, 0.2797053914892766679014678, 0.1294849661688696932706114],
585 [0.3626837833783619829651504, 0.3137066458778872873379622, 0.2223810344533744705443560, 0.1012285362903762591525314],
586 [0.3302393550012597631645251, 0.3123470770400028400686304, 0.2606106964029354623187429, 0.1806481606948574040584720, 0.0812743883615744119718922],
587 [0.2955242247147528701738930, 0.2692667193099963550912269, 0.2190863625159820439955349, 0.1494513491505805931457763, 0.0666713443086881375935688],
588 [0.2729250867779006307144835, 0.2628045445102466621806889, 0.2331937645919904799185237, 0.1862902109277342514260976, 0.1255803694649046246346943, 0.0556685671161736664827537],
589 [0.2491470458134027850005624, 0.2334925365383548087608499, 0.2031674267230659217490645, 0.1600783285433462263346525, 0.1069393259953184309602547, 0.0471753363865118271946160],
590 [0.2325515532308739101945895, 0.2262831802628972384120902, 0.2078160475368885023125232, 0.1781459807619457382800467, 0.1388735102197872384636018, 0.0921214998377284479144218, 0.0404840047653158795200216],
591 [0.2152638534631577901958764, 0.2051984637212956039659241, 0.1855383974779378137417166, 0.1572031671581935345696019, 0.1215185706879031846894148, 0.0801580871597602098056333, 0.0351194603317518630318329],
592 [0.2025782419255612728806202, 0.1984314853271115764561183, 0.1861610000155622110268006, 0.1662692058169939335532009, 0.1395706779261543144478048, 0.1071592204671719350118695, 0.0703660474881081247092674, 0.0307532419961172683546284],
593 [0.1894506104550684962853967, 0.1826034150449235888667637, 0.1691565193950025381893121, 0.1495959888165767320815017, 0.1246289712555338720524763, 0.0951585116824927848099251, 0.0622535239386478928628438, 0.0271524594117540948517806]
594 ]
595
596 },
597
598 /**
599 * Account for high dpi rendering.
600 * http://www.html5rocks.com/en/tutorials/canvas/hidpi/
601 */
602
603 devicePixelRatio: root.devicePixelRatio || 1,
604
605 getBackingStoreRatio: function (ctx) {
606 return ctx.webkitBackingStorePixelRatio ||
607 ctx.mozBackingStorePixelRatio ||
608 ctx.msBackingStorePixelRatio ||
609 ctx.oBackingStorePixelRatio ||
610 ctx.backingStorePixelRatio || 1;
611 },
612
613 getRatio: function (ctx) {
614 return Two.Utils.devicePixelRatio / getBackingStoreRatio(ctx);
615 },
616
617 /**
618 * Properly defer play calling until after all objects
619 * have been updated with their newest styles.
620 */
621 setPlaying: function (b) {
622
623 this.playing = !!b;
624 return this;
625
626 },
627
628 /**
629 * Return the computed matrix of a nested object.
630 * TODO: Optimize traversal.
631 */
632 getComputedMatrix: function (object, matrix) {
633
634 matrix = (matrix && matrix.identity()) || new Two.Matrix();
635 var parent = object, matrices = [];
636
637 while (parent && parent._matrix) {
638 matrices.push(parent._matrix);
639 parent = parent.parent;
640 }
641
642 matrices.reverse();
643
644 _.each(matrices, function (m) {
645
646 var e = m.elements;
647 matrix.multiply(
648 e[0], e[1], e[2], e[3], e[4], e[5], e[6], e[7], e[8], e[9]);
649
650 });
651
652 return matrix;
653
654 },
655
656 deltaTransformPoint: function (matrix, x, y) {
657
658 var dx = x * matrix.a + y * matrix.c + 0;
659 var dy = x * matrix.b + y * matrix.d + 0;
660
661 return new Two.Vector(dx, dy);
662
663 },
664
665 /**
666 * https://gist.github.com/2052247
667 */
668 decomposeMatrix: function (matrix) {
669
670 // calculate delta transform point
671 var px = Two.Utils.deltaTransformPoint(matrix, 0, 1);
672 var py = Two.Utils.deltaTransformPoint(matrix, 1, 0);
673
674 // calculate skew
675 var skewX = ((180 / Math.PI) * Math.atan2(px.y, px.x) - 90);
676 var skewY = ((180 / Math.PI) * Math.atan2(py.y, py.x));
677
678 return {
679 translateX: matrix.e,
680 translateY: matrix.f,
681 scaleX: Math.sqrt(matrix.a * matrix.a + matrix.b * matrix.b),
682 scaleY: Math.sqrt(matrix.c * matrix.c + matrix.d * matrix.d),
683 skewX: skewX,
684 skewY: skewY,
685 rotation: skewX // rotation is the same as skew x
686 };
687
688 },
689
690 /**
691 * Walk through item properties and pick the ones of interest.
692 * Will try to resolve styles applied via CSS
693 *
694 * TODO: Reverse calculate `Two.Gradient`s for fill / stroke
695 * of any given path.
696 */
697 applySvgAttributes: function (node, elem) {
698
699 var attributes = {}, styles = {}, i, key, value, attr;
700
701 // Not available in non browser environments
702 if (getComputedStyle) {
703 // Convert CSSStyleDeclaration to a normal object
704 var computedStyles = getComputedStyle(node);
705 i = computedStyles.length;
706
707 while (i--) {
708 key = computedStyles[i];
709 value = computedStyles[key];
710 // Gecko returns undefined for unset properties
711 // Webkit returns the default value
712 if (value !== undefined) {
713 styles[key] = value;
714 }
715 }
716 }
717
718 // Convert NodeMap to a normal object
719 i = node.attributes.length;
720 while (i--) {
721 attr = node.attributes[i];
722 attributes[attr.nodeName] = attr.value;
723 }
724
725 // Getting the correct opacity is a bit tricky, since SVG path elements don't
726 // support opacity as an attribute, but you can apply it via CSS.
727 // So we take the opacity and set (stroke/fill)-opacity to the same value.
728 if (!_.isUndefined(styles.opacity)) {
729 styles['stroke-opacity'] = styles.opacity;
730 styles['fill-opacity'] = styles.opacity;
731 }
732
733 // Merge attributes and applied styles (attributes take precedence)
734 _.extend(styles, attributes);
735
736 // Similarly visibility is influenced by the value of both display and visibility.
737 // Calculate a unified value here which defaults to `true`.
738 styles.visible = !(_.isUndefined(styles.display) && styles.display === 'none')
739 || (_.isUndefined(styles.visibility) && styles.visibility === 'hidden');
740
741 // Now iterate the whole thing
742 for (key in styles) {
743 value = styles[key];
744
745 switch (key) {
746 case 'transform':
747 // TODO: Check this out https://github.com/paperjs/paper.js/blob/master/src/svg/SVGImport.js#L313
748 if (value === 'none') break;
749 var m = node.getCTM ? node.getCTM() : null;
750
751 // Might happen when transform string is empty or not valid.
752 if (m === null) break;
753
754 // // Option 1: edit the underlying matrix and don't force an auto calc.
755 // var m = node.getCTM();
756 // elem._matrix.manual = true;
757 // elem._matrix.set(m.a, m.b, m.c, m.d, m.e, m.f);
758
759 // Option 2: Decompose and infer Two.js related properties.
760 var transforms = Two.Utils.decomposeMatrix(node.getCTM());
761
762 elem.translation.set(transforms.translateX, transforms.translateY);
763 elem.rotation = transforms.rotation;
764 // Warning: Two.js elements only support uniform scalars...
765 elem.scale = transforms.scaleX;
766
767 var x = parseFloat((styles.x + '').replace('px'));
768 var y = parseFloat((styles.y + '').replace('px'));
769
770 // Override based on attributes.
771 if (x) {
772 elem.translation.x = x;
773 }
774
775 if (y) {
776 elem.translation.y = y;
777 }
778
779 break;
780 case 'visible':
781 elem.visible = value;
782 break;
783 case 'stroke-linecap':
784 elem.cap = value;
785 break;
786 case 'stroke-linejoin':
787 elem.join = value;
788 break;
789 case 'stroke-miterlimit':
790 elem.miter = value;
791 break;
792 case 'stroke-width':
793 elem.linewidth = parseFloat(value);
794 break;
795 case 'stroke-opacity':
796 case 'fill-opacity':
797 case 'opacity':
798 elem.opacity = parseFloat(value);
799 break;
800 case 'fill':
801 case 'stroke':
802 if (/url\(\#.*\)/i.test(value)) {
803 elem[key] = this.getById(
804 value.replace(/url\(\#(.*)\)/i, '$1'));
805 } else {
806 elem[key] = (value === 'none') ? 'transparent' : value;
807 }
808 break;
809 case 'id':
810 elem.id = value;
811 break;
812 case 'class':
813 elem.classList = value.split(' ');
814 break;
815 }
816 }
817
818 return elem;
819
820 },
821
822 /**
823 * Read any number of SVG node types and create Two equivalents of them.
824 */
825 read: {
826
827 svg: function () {
828 return Two.Utils.read.g.apply(this, arguments);
829 },
830
831 g: function (node) {
832
833 var group = new Two.Group();
834
835 // Switched up order to inherit more specific styles
836 Two.Utils.applySvgAttributes.call(this, node, group);
837
838 for (var i = 0, l = node.childNodes.length; i < l; i++) {
839 var n = node.childNodes[i];
840 var tag = n.nodeName;
841 if (!tag) return;
842
843 var tagName = tag.replace(/svg\:/ig, '').toLowerCase();
844
845 if (tagName in Two.Utils.read) {
846 var o = Two.Utils.read[tagName].call(group, n);
847 group.add(o);
848 }
849 }
850
851 return group;
852
853 },
854
855 polygon: function (node, open) {
856
857 var points = node.getAttribute('points');
858
859 var verts = [];
860 points.replace(/(-?[\d\.?]+)[,|\s](-?[\d\.?]+)/g, function (match, p1, p2) {
861 verts.push(new Two.Anchor(parseFloat(p1), parseFloat(p2)));
862 });
863
864 var poly = new Two.Path(verts, !open).noStroke();
865 poly.fill = 'black';
866
867 return Two.Utils.applySvgAttributes.call(this, node, poly);
868
869 },
870
871 polyline: function (node) {
872 return Two.Utils.read.polygon.call(this, node, true);
873 },
874
875 path: function (node) {
876
877 var path = node.getAttribute('d');
878
879 // Create a Two.Path from the paths.
880
881 var coord = new Two.Anchor();
882 var control, coords;
883 var closed = false, relative = false;
884 var commands = path.match(/[a-df-z][^a-df-z]*/ig);
885 var last = commands.length - 1;
886
887 // Split up polybeziers
888
889 _.each(commands.slice(0), function (command, i) {
890
891 var type = command[0];
892 var lower = type.toLowerCase();
893 var items = command.slice(1).trim().split(/[\s,]+|(?=\s?[+\-])/);
894 var pre, post, result = [], bin;
895
896 if (i <= 0) {
897 commands = [];
898 }
899
900 switch (lower) {
901 case 'h':
902 case 'v':
903 if (items.length > 1) {
904 bin = 1;
905 }
906 break;
907 case 'm':
908 case 'l':
909 case 't':
910 if (items.length > 2) {
911 bin = 2;
912 }
913 break;
914 case 's':
915 case 'q':
916 if (items.length > 4) {
917 bin = 4;
918 }
919 break;
920 case 'c':
921 if (items.length > 6) {
922 bin = 6;
923 }
924 break;
925 case 'a':
926 // TODO: Handle Ellipses
927 break;
928 }
929
930 if (bin) {
931
932 for (var j = 0, l = items.length, times = 0; j < l; j += bin) {
933
934 var ct = type;
935 if (times > 0) {
936
937 switch (type) {
938 case 'm':
939 ct = 'l';
940 break;
941 case 'M':
942 ct = 'L';
943 break;
944 }
945
946 }
947
948 result.push([ct].concat(items.slice(j, j + bin)).join(' '));
949 times++;
950
951 }
952
953 commands = Array.prototype.concat.apply(commands, result);
954
955 } else {
956
957 commands.push(command);
958
959 }
960
961 });
962
963 // Create the vertices for our Two.Path
964
965 var points = [];
966 _.each(commands, function (command, i) {
967
968 var result, x, y;
969 var type = command[0];
970 var lower = type.toLowerCase();
971
972 coords = command.slice(1).trim();
973 coords = coords.replace(/(-?\d+(?:\.\d*)?)[eE]([+\-]?\d+)/g, function (match, n1, n2) {
974 return parseFloat(n1) * pow(10, n2);
975 });
976 coords = coords.split(/[\s,]+|(?=\s?[+\-])/);
977 relative = type === lower;
978
979 var x1, y1, x2, y2, x3, y3, x4, y4, reflection;
980
981 switch (lower) {
982
983 case 'z':
984 if (i >= last) {
985 closed = true;
986 } else {
987 x = coord.x;
988 y = coord.y;
989 result = new Two.Anchor(
990 x, y,
991 undefined, undefined,
992 undefined, undefined,
993 Two.Commands.close
994 );
995 }
996 break;
997
998 case 'm':
999 case 'l':
1000
1001 x = parseFloat(coords[0]);
1002 y = parseFloat(coords[1]);
1003
1004 result = new Two.Anchor(
1005 x, y,
1006 undefined, undefined,
1007 undefined, undefined,
1008 lower === 'm' ? Two.Commands.move : Two.Commands.line
1009 );
1010
1011 if (relative) {
1012 result.addSelf(coord);
1013 }
1014
1015 // result.controls.left.copy(result);
1016 // result.controls.right.copy(result);
1017
1018 coord = result;
1019 break;
1020
1021 case 'h':
1022 case 'v':
1023
1024 var a = lower === 'h' ? 'x' : 'y';
1025 var b = a === 'x' ? 'y' : 'x';
1026
1027 result = new Two.Anchor(
1028 undefined, undefined,
1029 undefined, undefined,
1030 undefined, undefined,
1031 Two.Commands.line
1032 );
1033 result[a] = parseFloat(coords[0]);
1034 result[b] = coord[b];
1035
1036 if (relative) {
1037 result[a] += coord[a];
1038 }
1039
1040 // result.controls.left.copy(result);
1041 // result.controls.right.copy(result);
1042
1043 coord = result;
1044 break;
1045
1046 case 'c':
1047 case 's':
1048
1049 x1 = coord.x;
1050 y1 = coord.y;
1051
1052 if (!control) {
1053 control = new Two.Vector();//.copy(coord);
1054 }
1055
1056 if (lower === 'c') {
1057
1058 x2 = parseFloat(coords[0]);
1059 y2 = parseFloat(coords[1]);
1060 x3 = parseFloat(coords[2]);
1061 y3 = parseFloat(coords[3]);
1062 x4 = parseFloat(coords[4]);
1063 y4 = parseFloat(coords[5]);
1064
1065 } else {
1066
1067 // Calculate reflection control point for proper x2, y2
1068 // inclusion.
1069
1070 reflection = getReflection(coord, control, relative);
1071
1072 x2 = reflection.x;
1073 y2 = reflection.y;
1074 x3 = parseFloat(coords[0]);
1075 y3 = parseFloat(coords[1]);
1076 x4 = parseFloat(coords[2]);
1077 y4 = parseFloat(coords[3]);
1078
1079 }
1080
1081 if (relative) {
1082 x2 += x1;
1083 y2 += y1;
1084 x3 += x1;
1085 y3 += y1;
1086 x4 += x1;
1087 y4 += y1;
1088 }
1089
1090 if (!_.isObject(coord.controls)) {
1091 Two.Anchor.AppendCurveProperties(coord);
1092 }
1093
1094 coord.controls.right.set(x2 - coord.x, y2 - coord.y);
1095 result = new Two.Anchor(
1096 x4, y4,
1097 x3 - x4, y3 - y4,
1098 undefined, undefined,
1099 Two.Commands.curve
1100 );
1101
1102 coord = result;
1103 control = result.controls.left;
1104
1105 break;
1106
1107 case 't':
1108 case 'q':
1109
1110 x1 = coord.x;
1111 y1 = coord.y;
1112
1113 if (!control) {
1114 control = new Two.Vector();//.copy(coord);
1115 }
1116
1117 if (control.isZero()) {
1118 x2 = x1;
1119 y2 = y1;
1120 } else {
1121 x2 = control.x;
1122 y1 = control.y;
1123 }
1124
1125 if (lower === 'q') {
1126
1127 x3 = parseFloat(coords[0]);
1128 y3 = parseFloat(coords[1]);
1129 x4 = parseFloat(coords[1]);
1130 y4 = parseFloat(coords[2]);
1131
1132 } else {
1133
1134 reflection = getReflection(coord, control, relative);
1135
1136 x3 = reflection.x;
1137 y3 = reflection.y;
1138 x4 = parseFloat(coords[0]);
1139 y4 = parseFloat(coords[1]);
1140
1141 }
1142
1143 if (relative) {
1144 x2 += x1;
1145 y2 += y1;
1146 x3 += x1;
1147 y3 += y1;
1148 x4 += x1;
1149 y4 += y1;
1150 }
1151
1152 if (!_.isObject(coord.controls)) {
1153 Two.Anchor.AppendCurveProperties(coord);
1154 }
1155
1156 coord.controls.right.set(x2 - coord.x, y2 - coord.y);
1157 result = new Two.Anchor(
1158 x4, y4,
1159 x3 - x4, y3 - y4,
1160 undefined, undefined,
1161 Two.Commands.curve
1162 );
1163
1164 coord = result;
1165 control = result.controls.left;
1166
1167 break;
1168
1169 case 'a':
1170
1171 // throw new Two.Utils.Error('not yet able to interpret Elliptical Arcs.');
1172 x1 = coord.x;
1173 y1 = coord.y;
1174
1175 var rx = parseFloat(coords[0]);
1176 var ry = parseFloat(coords[1]);
1177 var xAxisRotation = parseFloat(coords[2]) * Math.PI / 180;
1178 var largeArcFlag = parseFloat(coords[3]);
1179 var sweepFlag = parseFloat(coords[4]);
1180
1181 x4 = parseFloat(coords[5]);
1182 y4 = parseFloat(coords[6]);
1183
1184 if (relative) {
1185 x4 += x1;
1186 y4 += y1;
1187 }
1188
1189 // http://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter
1190
1191 // Calculate midpoint mx my
1192 var mx = (x4 - x1) / 2;
1193 var my = (y4 - y1) / 2;
1194
1195 // Calculate x1' y1' F.6.5.1
1196 var _x = mx * Math.cos(xAxisRotation) + my * Math.sin(xAxisRotation);
1197 var _y = - mx * Math.sin(xAxisRotation) + my * Math.cos(xAxisRotation);
1198
1199 var rx2 = rx * rx;
1200 var ry2 = ry * ry;
1201 var _x2 = _x * _x;
1202 var _y2 = _y * _y;
1203
1204 // adjust radii
1205 var l = _x2 / rx2 + _y2 / ry2;
1206 if (l > 1) {
1207 rx *= Math.sqrt(l);
1208 ry *= Math.sqrt(l);
1209 }
1210
1211 var amp = Math.sqrt((rx2 * ry2 - rx2 * _y2 - ry2 * _x2) / (rx2 * _y2 + ry2 * _x2));
1212
1213 if (_.isNaN(amp)) {
1214 amp = 0;
1215 } else if (largeArcFlag != sweepFlag && amp > 0) {
1216 amp *= -1;
1217 }
1218
1219 // Calculate cx' cy' F.6.5.2
1220 var _cx = amp * rx * _y / ry;
1221 var _cy = - amp * ry * _x / rx;
1222
1223 // Calculate cx cy F.6.5.3
1224 var cx = _cx * Math.cos(xAxisRotation) - _cy * Math.sin(xAxisRotation) + (x1 + x4) / 2;
1225 var cy = _cx * Math.sin(xAxisRotation) + _cy * Math.cos(xAxisRotation) + (y1 + y4) / 2;
1226
1227 // vector magnitude
1228 var m = function (v) { return Math.sqrt(Math.pow(v[0], 2) + Math.pow(v[1], 2)); }
1229 // ratio between two vectors
1230 var r = function (u, v) { return (u[0] * v[0] + u[1] * v[1]) / (m(u) * m(v)) }
1231 // angle between two vectors
1232 var a = function (u, v) { return (u[0] * v[1] < u[1] * v[0] ? - 1 : 1) * Math.acos(r(u, v)); }
1233
1234 // Calculate theta1 and delta theta F.6.5.4 + F.6.5.5
1235 var t1 = a([1, 0], [(_x - _cx) / rx, (_y - _cy) / ry]);
1236 var u = [(_x - _cx) / rx, (_y - _cy) / ry];
1237 var v = [(- _x - _cx) / rx, (- _y - _cy) / ry];
1238 var dt = a(u, v);
1239
1240 if (r(u, v) <= -1) dt = Math.PI;
1241 if (r(u, v) >= 1) dt = 0;
1242
1243 // F.6.5.6
1244 if (largeArcFlag) {
1245 dt = mod(dt, Math.PI * 2);
1246 }
1247
1248 if (sweepFlag && dt > 0) {
1249 dt -= Math.PI * 2;
1250 }
1251
1252 var length = Two.Resolution;
1253
1254 // Save a projection of our rotation and translation to apply
1255 // to the set of points.
1256 var projection = new Two.Matrix()
1257 .translate(cx, cy)
1258 .rotate(xAxisRotation);
1259
1260 // Create a resulting array of Two.Anchor's to export to the
1261 // the path.
1262 result = _.map(_.range(length), function (i) {
1263 var pct = 1 - (i / (length - 1));
1264 var theta = pct * dt + t1;
1265 var x = rx * Math.cos(theta);
1266 var y = ry * Math.sin(theta);
1267 var projected = projection.multiply(x, y, 1);
1268 return new Two.Anchor(projected.x, projected.y, false, false, false, false, Two.Commands.line);;
1269 });
1270
1271 result.push(new Two.Anchor(x4, y4, false, false, false, false, Two.Commands.line));
1272
1273 coord = result[result.length - 1];
1274 control = coord.controls.left;
1275
1276 break;
1277
1278 }
1279
1280 if (result) {
1281 if (_.isArray(result)) {
1282 points = points.concat(result);
1283 } else {
1284 points.push(result);
1285 }
1286 }
1287
1288 });
1289
1290 if (points.length <= 1) {
1291 return;
1292 }
1293
1294 var path = new Two.Path(points, closed, undefined, true).noStroke();
1295 path.fill = 'black';
1296
1297 var rect = path.getBoundingClientRect(true);
1298
1299 // Center objects to stay consistent
1300 // with the rest of the Two.js API.
1301 rect.centroid = {
1302 x: rect.left + rect.width / 2,
1303 y: rect.top + rect.height / 2
1304 };
1305
1306 _.each(path.vertices, function (v) {
1307 v.subSelf(rect.centroid);
1308 });
1309
1310 path.translation.addSelf(rect.centroid);
1311
1312 return Two.Utils.applySvgAttributes.call(this, node, path);
1313
1314 },
1315
1316 circle: function (node) {
1317
1318 var x = parseFloat(node.getAttribute('cx'));
1319 var y = parseFloat(node.getAttribute('cy'));
1320 var r = parseFloat(node.getAttribute('r'));
1321
1322 var circle = new Two.Circle(x, y, r).noStroke();
1323 circle.fill = 'black';
1324
1325 return Two.Utils.applySvgAttributes.call(this, node, circle);
1326
1327 },
1328
1329 ellipse: function (node) {
1330
1331 var x = parseFloat(node.getAttribute('cx'));
1332 var y = parseFloat(node.getAttribute('cy'));
1333 var width = parseFloat(node.getAttribute('rx'));
1334 var height = parseFloat(node.getAttribute('ry'));
1335
1336 var ellipse = new Two.Ellipse(x, y, width, height).noStroke();
1337 ellipse.fill = 'black';
1338
1339 return Two.Utils.applySvgAttributes.call(this, node, ellipse);
1340
1341 },
1342
1343 rect: function (node) {
1344
1345 var x = parseFloat(node.getAttribute('x')) || 0;
1346 var y = parseFloat(node.getAttribute('y')) || 0;
1347 var width = parseFloat(node.getAttribute('width'));
1348 var height = parseFloat(node.getAttribute('height'));
1349
1350 var w2 = width / 2;
1351 var h2 = height / 2;
1352
1353 var rect = new Two.Rectangle(x + w2, y + h2, width, height)
1354 .noStroke();
1355 rect.fill = 'black';
1356
1357 return Two.Utils.applySvgAttributes.call(this, node, rect);
1358
1359 },
1360
1361 line: function (node) {
1362
1363 var x1 = parseFloat(node.getAttribute('x1'));
1364 var y1 = parseFloat(node.getAttribute('y1'));
1365 var x2 = parseFloat(node.getAttribute('x2'));
1366 var y2 = parseFloat(node.getAttribute('y2'));
1367
1368 var line = new Two.Line(x1, y1, x2, y2).noFill();
1369
1370 return Two.Utils.applySvgAttributes.call(this, node, line);
1371
1372 },
1373
1374 lineargradient: function (node) {
1375
1376 var x1 = parseFloat(node.getAttribute('x1'));
1377 var y1 = parseFloat(node.getAttribute('y1'));
1378 var x2 = parseFloat(node.getAttribute('x2'));
1379 var y2 = parseFloat(node.getAttribute('y2'));
1380
1381 var ox = (x2 + x1) / 2;
1382 var oy = (y2 + y1) / 2;
1383
1384 var stops = [];
1385 for (var i = 0; i < node.children.length; i++) {
1386
1387 var child = node.children[i];
1388
1389 var offset = parseFloat(child.getAttribute('offset'));
1390 var color = child.getAttribute('stop-color');
1391 var opacity = child.getAttribute('stop-opacity');
1392 var style = child.getAttribute('style');
1393
1394 if (_.isNull(color)) {
1395 var matches = style ? style.match(/stop\-color\:\s?([\#a-fA-F0-9]*)/) : false;
1396 color = matches && matches.length > 1 ? matches[1] : undefined;
1397 }
1398
1399 if (_.isNull(opacity)) {
1400 var matches = style ? style.match(/stop\-opacity\:\s?([0-9\.\-]*)/) : false;
1401 opacity = matches && matches.length > 1 ? parseFloat(matches[1]) : 1;
1402 }
1403
1404 stops.push(new Two.Gradient.Stop(offset, color, opacity));
1405
1406 }
1407
1408 var gradient = new Two.LinearGradient(x1 - ox, y1 - oy, x2 - ox,
1409 y2 - oy, stops);
1410
1411 return Two.Utils.applySvgAttributes.call(this, node, gradient);
1412
1413 },
1414
1415 radialgradient: function (node) {
1416
1417 var cx = parseFloat(node.getAttribute('cx')) || 0;
1418 var cy = parseFloat(node.getAttribute('cy')) || 0;
1419 var r = parseFloat(node.getAttribute('r'));
1420
1421 var fx = parseFloat(node.getAttribute('fx'));
1422 var fy = parseFloat(node.getAttribute('fy'));
1423
1424 if (_.isNaN(fx)) {
1425 fx = cx;
1426 }
1427
1428 if (_.isNaN(fy)) {
1429 fy = cy;
1430 }
1431
1432 var ox = Math.abs(cx + fx) / 2;
1433 var oy = Math.abs(cy + fy) / 2;
1434
1435 var stops = [];
1436 for (var i = 0; i < node.children.length; i++) {
1437
1438 var child = node.children[i];
1439
1440 var offset = parseFloat(child.getAttribute('offset'));
1441 var color = child.getAttribute('stop-color');
1442 var opacity = child.getAttribute('stop-opacity');
1443 var style = child.getAttribute('style');
1444
1445 if (_.isNull(color)) {
1446 var matches = style ? style.match(/stop\-color\:\s?([\#a-fA-F0-9]*)/) : false;
1447 color = matches && matches.length > 1 ? matches[1] : undefined;
1448 }
1449
1450 if (_.isNull(opacity)) {
1451 var matches = style ? style.match(/stop\-opacity\:\s?([0-9\.\-]*)/) : false;
1452 opacity = matches && matches.length > 1 ? parseFloat(matches[1]) : 1;
1453 }
1454
1455 stops.push(new Two.Gradient.Stop(offset, color, opacity));
1456
1457 }
1458
1459 var gradient = new Two.RadialGradient(cx - ox, cy - oy, r,
1460 stops, fx - ox, fy - oy);
1461
1462 return Two.Utils.applySvgAttributes.call(this, node, gradient);
1463
1464 }
1465
1466 },
1467
1468 /**
1469 * Given 2 points (a, b) and corresponding control point for each
1470 * return an array of points that represent points plotted along
1471 * the curve. Number points determined by limit.
1472 */
1473 subdivide: function (x1, y1, x2, y2, x3, y3, x4, y4, limit) {
1474
1475 limit = limit || Two.Utils.Curve.RecursionLimit;
1476 var amount = limit + 1;
1477
1478 // TODO: Issue 73
1479 // Don't recurse if the end points are identical
1480 if (x1 === x4 && y1 === y4) {
1481 return [new Two.Anchor(x4, y4)];
1482 }
1483
1484 return _.map(_.range(0, amount), function (i) {
1485
1486 var t = i / amount;
1487 var x = getPointOnCubicBezier(t, x1, x2, x3, x4);
1488 var y = getPointOnCubicBezier(t, y1, y2, y3, y4);
1489
1490 return new Two.Anchor(x, y);
1491
1492 });
1493
1494 },
1495
1496 getPointOnCubicBezier: function (t, a, b, c, d) {
1497 var k = 1 - t;
1498 return (k * k * k * a) + (3 * k * k * t * b) + (3 * k * t * t * c) +
1499 (t * t * t * d);
1500 },
1501
1502 /**
1503 * Given 2 points (a, b) and corresponding control point for each
1504 * return a float that represents the length of the curve using
1505 * Gauss-Legendre algorithm. Limit iterations of calculation by `limit`.
1506 */
1507 getCurveLength: function (x1, y1, x2, y2, x3, y3, x4, y4, limit) {
1508
1509 // TODO: Better / fuzzier equality check
1510 // Linear calculation
1511 if (x1 === x2 && y1 === y2 && x3 === x4 && y3 === y4) {
1512 var dx = x4 - x1;
1513 var dy = y4 - y1;
1514 return sqrt(dx * dx + dy * dy);
1515 }
1516
1517 // Calculate the coefficients of a Bezier derivative.
1518 var ax = 9 * (x2 - x3) + 3 * (x4 - x1),
1519 bx = 6 * (x1 + x3) - 12 * x2,
1520 cx = 3 * (x2 - x1),
1521
1522 ay = 9 * (y2 - y3) + 3 * (y4 - y1),
1523 by = 6 * (y1 + y3) - 12 * y2,
1524 cy = 3 * (y2 - y1);
1525
1526 var integrand = function (t) {
1527 // Calculate quadratic equations of derivatives for x and y
1528 var dx = (ax * t + bx) * t + cx,
1529 dy = (ay * t + by) * t + cy;
1530 return sqrt(dx * dx + dy * dy);
1531 };
1532
1533 return integrate(
1534 integrand, 0, 1, limit || Two.Utils.Curve.RecursionLimit
1535 );
1536
1537 },
1538
1539 /**
1540 * Integration for `getCurveLength` calculations. Referenced from
1541 * Paper.js: https://github.com/paperjs/paper.js/blob/master/src/util/Numerical.js#L101
1542 */
1543 integrate: function (f, a, b, n) {
1544 var x = Two.Utils.Curve.abscissas[n - 2],
1545 w = Two.Utils.Curve.weights[n - 2],
1546 A = 0.5 * (b - a),
1547 B = A + a,
1548 i = 0,
1549 m = (n + 1) >> 1,
1550 sum = n & 1 ? w[i++] * f(B) : 0; // Handle odd n
1551 while (i < m) {
1552 var Ax = A * x[i];
1553 sum += w[i++] * (f(B + Ax) + f(B - Ax));
1554 }
1555 return A * sum;
1556 },
1557
1558 /**
1559 * Creates a set of points that have u, v values for anchor positions
1560 */
1561 getCurveFromPoints: function (points, closed) {
1562
1563 var l = points.length, last = l - 1;
1564
1565 for (var i = 0; i < l; i++) {
1566
1567 var point = points[i];
1568
1569 if (!_.isObject(point.controls)) {
1570 Two.Anchor.AppendCurveProperties(point);
1571 }
1572
1573 var prev = closed ? mod(i - 1, l) : max(i - 1, 0);
1574 var next = closed ? mod(i + 1, l) : min(i + 1, last);
1575
1576 var a = points[prev];
1577 var b = point;
1578 var c = points[next];
1579 getControlPoints(a, b, c);
1580
1581 b._command = i === 0 ? Two.Commands.move : Two.Commands.curve;
1582
1583 b.controls.left.x = _.isNumber(b.controls.left.x) ? b.controls.left.x : b.x;
1584 b.controls.left.y = _.isNumber(b.controls.left.y) ? b.controls.left.y : b.y;
1585
1586 b.controls.right.x = _.isNumber(b.controls.right.x) ? b.controls.right.x : b.x;
1587 b.controls.right.y = _.isNumber(b.controls.right.y) ? b.controls.right.y : b.y;
1588
1589 }
1590
1591 },
1592
1593 /**
1594 * Given three coordinates return the control points for the middle, b,
1595 * vertex.
1596 */
1597 getControlPoints: function (a, b, c) {
1598
1599 var a1 = angleBetween(a, b);
1600 var a2 = angleBetween(c, b);
1601
1602 var d1 = distanceBetween(a, b);
1603 var d2 = distanceBetween(c, b);
1604
1605 var mid = (a1 + a2) / 2;
1606
1607 // So we know which angle corresponds to which side.
1608
1609 b.u = _.isObject(b.controls.left) ? b.controls.left : new Two.Vector(0, 0);
1610 b.v = _.isObject(b.controls.right) ? b.controls.right : new Two.Vector(0, 0);
1611
1612 // TODO: Issue 73
1613 if (d1 < 0.0001 || d2 < 0.0001) {
1614 if (!b._relative) {
1615 b.controls.left.copy(b);
1616 b.controls.right.copy(b);
1617 }
1618 return b;
1619 }
1620
1621 d1 *= 0.33; // Why 0.33?
1622 d2 *= 0.33;
1623
1624 if (a2 < a1) {
1625 mid += HALF_PI;
1626 } else {
1627 mid -= HALF_PI;
1628 }
1629
1630 b.controls.left.x = cos(mid) * d1;
1631 b.controls.left.y = sin(mid) * d1;
1632
1633 mid -= PI;
1634
1635 b.controls.right.x = cos(mid) * d2;
1636 b.controls.right.y = sin(mid) * d2;
1637
1638 if (!b._relative) {
1639 b.controls.left.x += b.x;
1640 b.controls.left.y += b.y;
1641 b.controls.right.x += b.x;
1642 b.controls.right.y += b.y;
1643 }
1644
1645 return b;
1646
1647 },
1648
1649 /**
1650 * Get the reflection of a point "b" about point "a". Where "a" is in
1651 * absolute space and "b" is relative to "a".
1652 *
1653 * http://www.w3.org/TR/SVG11/implnote.html#PathElementImplementationNotes
1654 */
1655 getReflection: function (a, b, relative) {
1656
1657 return new Two.Vector(
1658 2 * a.x - (b.x + a.x) - (relative ? a.x : 0),
1659 2 * a.y - (b.y + a.y) - (relative ? a.y : 0)
1660 );
1661
1662 },
1663
1664 getAnchorsFromArcData: function (center, xAxisRotation, rx, ry, ts, td, ccw) {
1665
1666 var matrix = new Two.Matrix()
1667 .translate(center.x, center.y)
1668 .rotate(xAxisRotation);
1669
1670 var l = Two.Resolution;
1671
1672 return _.map(_.range(l), function (i) {
1673
1674 var pct = (i + 1) / l;
1675 if (!!ccw) {
1676 pct = 1 - pct;
1677 }
1678
1679 var theta = pct * td + ts;
1680 var x = rx * Math.cos(theta);
1681 var y = ry * Math.sin(theta);
1682
1683 // x += center.x;
1684 // y += center.y;
1685
1686 var anchor = new Two.Anchor(x, y);
1687 Two.Anchor.AppendCurveProperties(anchor);
1688 anchor.command = Two.Commands.line;
1689
1690 // TODO: Calculate control points here...
1691
1692 return anchor;
1693
1694 });
1695
1696 },
1697
1698 ratioBetween: function (A, B) {
1699
1700 return (A.x * B.x + A.y * B.y) / (A.length() * B.length());
1701
1702 },
1703
1704 angleBetween: function (A, B) {
1705
1706 var dx, dy;
1707
1708 if (arguments.length >= 4) {
1709
1710 dx = arguments[0] - arguments[2];
1711 dy = arguments[1] - arguments[3];
1712
1713 return atan2(dy, dx);
1714
1715 }
1716
1717 dx = A.x - B.x;
1718 dy = A.y - B.y;
1719
1720 return atan2(dy, dx);
1721
1722 },
1723
1724 distanceBetweenSquared: function (p1, p2) {
1725
1726 var dx = p1.x - p2.x;
1727 var dy = p1.y - p2.y;
1728
1729 return dx * dx + dy * dy;
1730
1731 },
1732
1733 distanceBetween: function (p1, p2) {
1734
1735 return sqrt(distanceBetweenSquared(p1, p2));
1736
1737 },
1738
1739 lerp: function (a, b, t) {
1740 return t * (b - a) + a;
1741 },
1742
1743 // A pretty fast toFixed(3) alternative
1744 // See http://jsperf.com/parsefloat-tofixed-vs-math-round/18
1745 toFixed: function (v) {
1746 return Math.floor(v * 1000) / 1000;
1747 },
1748
1749 mod: function (v, l) {
1750
1751 while (v < 0) {
1752 v += l;
1753 }
1754
1755 return v % l;
1756
1757 },
1758
1759 /**
1760 * Array like collection that triggers inserted and removed events
1761 * removed : pop / shift / splice
1762 * inserted : push / unshift / splice (with > 2 arguments)
1763 */
1764 Collection: function () {
1765
1766 Array.call(this);
1767
1768 if (arguments.length > 1) {
1769 Array.prototype.push.apply(this, arguments);
1770 } else if (arguments[0] && Array.isArray(arguments[0])) {
1771 Array.prototype.push.apply(this, arguments[0]);
1772 }
1773
1774 },
1775
1776 // Custom Error Throwing for Two.js
1777
1778 Error: function (message) {
1779 this.name = 'two.js';
1780 this.message = message;
1781 },
1782
1783 Events: {
1784
1785 on: function (name, callback) {
1786
1787 this._events || (this._events = {});
1788 var list = this._events[name] || (this._events[name] = []);
1789
1790 list.push(callback);
1791
1792 return this;
1793
1794 },
1795
1796 off: function (name, callback) {
1797
1798 if (!this._events) {
1799 return this;
1800 }
1801 if (!name && !callback) {
1802 this._events = {};
1803 return this;
1804 }
1805
1806 var names = name ? [name] : _.keys(this._events);
1807 for (var i = 0, l = names.length; i < l; i++) {
1808
1809 var name = names[i];
1810 var list = this._events[name];
1811
1812 if (!!list) {
1813 var events = [];
1814 if (callback) {
1815 for (var j = 0, k = list.length; j < k; j++) {
1816 var ev = list[j];
1817 ev = ev.callback ? ev.callback : ev;
1818 if (callback && callback !== ev) {
1819 events.push(ev);
1820 }
1821 }
1822 }
1823 this._events[name] = events;
1824 }
1825 }
1826
1827 return this;
1828 },
1829
1830 trigger: function (name) {
1831 if (!this._events) return this;
1832 var args = slice.call(arguments, 1);
1833 var events = this._events[name];
1834 if (events) trigger(this, events, args);
1835 return this;
1836 },
1837
1838 listen: function (obj, name, callback) {
1839
1840 var bound = this;
1841
1842 if (obj) {
1843 var ev = function () {
1844 callback.apply(bound, arguments);
1845 };
1846
1847 // add references about the object that assigned this listener
1848 ev.obj = obj;
1849 ev.name = name;
1850 ev.callback = callback;
1851
1852 obj.on(name, ev);
1853 }
1854
1855 return this;
1856
1857 },
1858
1859 ignore: function (obj, name, callback) {
1860
1861 obj.off(name, callback);
1862
1863 return this;
1864
1865 }
1866
1867 }
1868
1869 })
1870
1871 });
1872
1873 Two.Utils.Events.bind = Two.Utils.Events.on;
1874 Two.Utils.Events.unbind = Two.Utils.Events.off;
1875
1876 var trigger = function (obj, events, args) {
1877 var method;
1878 switch (args.length) {
1879 case 0:
1880 method = function (i) {
1881 events[i].call(obj, args[0]);
1882 };
1883 break;
1884 case 1:
1885 method = function (i) {
1886 events[i].call(obj, args[0], args[1]);
1887 };
1888 break;
1889 case 2:
1890 method = function (i) {
1891 events[i].call(obj, args[0], args[1], args[2]);
1892 };
1893 break;
1894 case 3:
1895 method = function (i) {
1896 events[i].call(obj, args[0], args[1], args[2], args[3]);
1897 };
1898 break;
1899 default:
1900 method = function (i) {
1901 events[i].apply(obj, args);
1902 };
1903 }
1904 for (var i = 0; i < events.length; i++) {
1905 method(i);
1906 }
1907 };
1908
1909 Two.Utils.Error.prototype = new Error();
1910 Two.Utils.Error.prototype.constructor = Two.Utils.Error;
1911
1912 Two.Utils.Collection.prototype = new Array();
1913 Two.Utils.Collection.prototype.constructor = Two.Utils.Collection;
1914
1915 _.extend(Two.Utils.Collection.prototype, Two.Utils.Events, {
1916
1917 pop: function () {
1918 var popped = Array.prototype.pop.apply(this, arguments);
1919 this.trigger(Two.Events.remove, [popped]);
1920 return popped;
1921 },
1922
1923 shift: function () {
1924 var shifted = Array.prototype.shift.apply(this, arguments);
1925 this.trigger(Two.Events.remove, [shifted]);
1926 return shifted;
1927 },
1928
1929 push: function () {
1930 var pushed = Array.prototype.push.apply(this, arguments);
1931 this.trigger(Two.Events.insert, arguments);
1932 return pushed;
1933 },
1934
1935 unshift: function () {
1936 var unshifted = Array.prototype.unshift.apply(this, arguments);
1937 this.trigger(Two.Events.insert, arguments);
1938 return unshifted;
1939 },
1940
1941 splice: function () {
1942 var spliced = Array.prototype.splice.apply(this, arguments);
1943 var inserted;
1944
1945 this.trigger(Two.Events.remove, spliced);
1946
1947 if (arguments.length > 2) {
1948 inserted = this.slice(arguments[0], arguments[0] + arguments.length - 2);
1949 this.trigger(Two.Events.insert, inserted);
1950 this.trigger(Two.Events.order);
1951 }
1952 return spliced;
1953 },
1954
1955 sort: function () {
1956 Array.prototype.sort.apply(this, arguments);
1957 this.trigger(Two.Events.order);
1958 return this;
1959 },
1960
1961 reverse: function () {
1962 Array.prototype.reverse.apply(this, arguments);
1963 this.trigger(Two.Events.order);
1964 return this;
1965 }
1966
1967 });
1968
1969 // Localize utils
1970
1971 var distanceBetween = Two.Utils.distanceBetween,
1972 getAnchorsFromArcData = Two.Utils.getAnchorsFromArcData,
1973 distanceBetweenSquared = Two.Utils.distanceBetweenSquared,
1974 ratioBetween = Two.Utils.ratioBetween,
1975 angleBetween = Two.Utils.angleBetween,
1976 getControlPoints = Two.Utils.getControlPoints,
1977 getCurveFromPoints = Two.Utils.getCurveFromPoints,
1978 solveSegmentIntersection = Two.Utils.solveSegmentIntersection,
1979 decoupleShapes = Two.Utils.decoupleShapes,
1980 mod = Two.Utils.mod,
1981 getBackingStoreRatio = Two.Utils.getBackingStoreRatio,
1982 getPointOnCubicBezier = Two.Utils.getPointOnCubicBezier,
1983 getCurveLength = Two.Utils.getCurveLength,
1984 integrate = Two.Utils.integrate,
1985 getReflection = Two.Utils.getReflection;
1986
1987 _.extend(Two.prototype, Two.Utils.Events, {
1988
1989 appendTo: function (elem) {
1990
1991 elem.appendChild(this.renderer.domElement);
1992 return this;
1993
1994 },
1995
1996 play: function () {
1997
1998 Two.Utils.setPlaying.call(this, true);
1999 return this.trigger(Two.Events.play);
2000
2001 },
2002
2003 pause: function () {
2004
2005 this.playing = false;
2006 return this.trigger(Two.Events.pause);
2007
2008 },
2009
2010 /**
2011 * Update positions and calculations in one pass before rendering.
2012 */
2013 update: function () {
2014
2015 var animated = !!this._lastFrame;
2016 var now = perf.now();
2017
2018 this.frameCount++;
2019
2020 if (animated) {
2021 this.timeDelta = parseFloat((now - this._lastFrame).toFixed(3));
2022 }
2023 this._lastFrame = now;
2024
2025 var width = this.width;
2026 var height = this.height;
2027 var renderer = this.renderer;
2028
2029 // Update width / height for the renderer
2030 if (width !== renderer.width || height !== renderer.height) {
2031 renderer.setSize(width, height, this.ratio);
2032 }
2033
2034 this.trigger(Two.Events.update, this.frameCount, this.timeDelta);
2035
2036 return this.render();
2037
2038 },
2039
2040 /**
2041 * Render all drawable - visible objects of the scene.
2042 */
2043 render: function () {
2044
2045 this.renderer.render();
2046 return this.trigger(Two.Events.render, this.frameCount);
2047
2048 },
2049
2050 /**
2051 * Convenience Methods
2052 */
2053
2054 add: function (o) {
2055
2056 var objects = o;
2057 if (!(objects instanceof Array)) {
2058 objects = _.toArray(arguments);
2059 }
2060
2061 this.scene.add(objects);
2062 return this;
2063
2064 },
2065
2066 remove: function (o) {
2067
2068 var objects = o;
2069 if (!(objects instanceof Array)) {
2070 objects = _.toArray(arguments);
2071 }
2072
2073 this.scene.remove(objects);
2074
2075 return this;
2076
2077 },
2078
2079 clear: function () {
2080
2081 this.scene.remove(_.toArray(this.scene.children));
2082 return this;
2083
2084 },
2085
2086 makeLine: function (x1, y1, x2, y2) {
2087
2088 var line = new Two.Line(x1, y1, x2, y2);
2089 this.scene.add(line);
2090
2091 return line;
2092
2093 },
2094
2095 makeRectangle: function (x, y, width, height) {
2096
2097 var rect = new Two.Rectangle(x, y, width, height);
2098 this.scene.add(rect);
2099
2100 return rect;
2101
2102 },
2103
2104 makeRoundedRectangle: function (x, y, width, height, sides) {
2105
2106 var rect = new Two.RoundedRectangle(x, y, width, height, sides);
2107 this.scene.add(rect);
2108
2109 return rect;
2110
2111 },
2112
2113 makeCircle: function (ox, oy, r) {
2114
2115 var circle = new Two.Circle(ox, oy, r);
2116 this.scene.add(circle);
2117
2118 return circle;
2119
2120 },
2121
2122 makeEllipse: function (ox, oy, rx, ry) {
2123
2124 var ellipse = new Two.Ellipse(ox, oy, rx, ry);
2125 this.scene.add(ellipse);
2126
2127 return ellipse;
2128
2129 },
2130
2131 makeStar: function (ox, oy, or, ir, sides) {
2132
2133 var star = new Two.Star(ox, oy, or, ir, sides);
2134 this.scene.add(star);
2135
2136 return star;
2137
2138 },
2139
2140 makeCurve: function (p) {
2141
2142 var l = arguments.length, points = p;
2143 if (!_.isArray(p)) {
2144 points = [];
2145 for (var i = 0; i < l; i += 2) {
2146 var x = arguments[i];
2147 if (!_.isNumber(x)) {
2148 break;
2149 }
2150 var y = arguments[i + 1];
2151 points.push(new Two.Anchor(x, y));
2152 }
2153 }
2154
2155 var last = arguments[l - 1];
2156 var curve = new Two.Path(points, !(_.isBoolean(last) ? last : undefined), true);
2157 var rect = curve.getBoundingClientRect();
2158 curve.center().translation
2159 .set(rect.left + rect.width / 2, rect.top + rect.height / 2);
2160
2161 this.scene.add(curve);
2162
2163 return curve;
2164
2165 },
2166
2167 makePolygon: function (ox, oy, r, sides) {
2168
2169 var poly = new Two.Polygon(ox, oy, r, sides);
2170 this.scene.add(poly);
2171
2172 return poly;
2173
2174 },
2175
2176 /*
2177 * Make an Arc Segment
2178 */
2179
2180 makeArcSegment: function (ox, oy, ir, or, sa, ea, res) {
2181 var arcSegment = new Two.ArcSegment(ox, oy, ir, or, sa, ea, res);
2182 this.scene.add(arcSegment);
2183 return arcSegment;
2184 },
2185
2186 /**
2187 * Convenience method to make and draw a Two.Path.
2188 */
2189 makePath: function (p) {
2190
2191 var l = arguments.length, points = p;
2192 if (!_.isArray(p)) {
2193 points = [];
2194 for (var i = 0; i < l; i += 2) {
2195 var x = arguments[i];
2196 if (!_.isNumber(x)) {
2197 break;
2198 }
2199 var y = arguments[i + 1];
2200 points.push(new Two.Anchor(x, y));
2201 }
2202 }
2203
2204 var last = arguments[l - 1];
2205 var path = new Two.Path(points, !(_.isBoolean(last) ? last : undefined));
2206 var rect = path.getBoundingClientRect();
2207 path.center().translation
2208 .set(rect.left + rect.width / 2, rect.top + rect.height / 2);
2209
2210 this.scene.add(path);
2211
2212 return path;
2213
2214 },
2215
2216 /**
2217 * Convenience method to make and add a Two.Text.
2218 */
2219 makeText: function (message, x, y, styles) {
2220 var text = new Two.Text(message, x, y, styles);
2221 this.add(text);
2222 return text;
2223 },
2224
2225 /**
2226 * Convenience method to make and add a Two.LinearGradient.
2227 */
2228 makeLinearGradient: function (x1, y1, x2, y2 /* stops */) {
2229
2230 var stops = slice.call(arguments, 4);
2231 var gradient = new Two.LinearGradient(x1, y1, x2, y2, stops);
2232
2233 this.add(gradient);
2234
2235 return gradient;
2236
2237 },
2238
2239 /**
2240 * Convenience method to make and add a Two.RadialGradient.
2241 */
2242 makeRadialGradient: function (x1, y1, r /* stops */) {
2243
2244 var stops = slice.call(arguments, 3);
2245 var gradient = new Two.RadialGradient(x1, y1, r, stops);
2246
2247 this.add(gradient);
2248
2249 return gradient;
2250
2251 },
2252
2253 makeSprite: function (path, x, y, cols, rows, frameRate, autostart) {
2254
2255 var sprite = new Two.Sprite(path, x, y, cols, rows, frameRate);
2256 if (!!autostart) {
2257 sprite.play();
2258 }
2259 this.add(sprite);
2260
2261 return sprite;
2262
2263 },
2264
2265 makeImageSequence: function (paths, x, y, frameRate, autostart) {
2266
2267 var imageSequence = new Two.ImageSequence(paths, x, y, frameRate);
2268 if (!!autostart) {
2269 imageSequence.play();
2270 }
2271 this.add(imageSequence);
2272
2273 return imageSequence;
2274
2275 },
2276
2277 makeTexture: function (path, callback) {
2278
2279 var texture = new Two.Texture(path, callback);
2280 return texture;
2281
2282 },
2283
2284 makeGroup: function (o) {
2285
2286 var objects = o;
2287 if (!(objects instanceof Array)) {
2288 objects = _.toArray(arguments);
2289 }
2290
2291 var group = new Two.Group();
2292 this.scene.add(group);
2293 group.add(objects);
2294
2295 return group;
2296
2297 },
2298
2299 /**
2300 * Interpret an SVG Node and add it to this instance's scene. The
2301 * distinction should be made that this doesn't `import` svg's, it solely
2302 * interprets them into something compatible for Two.js — this is slightly
2303 * different than a direct transcription.
2304 *
2305 * @param {Object} svgNode - The node to be parsed
2306 * @param {Boolean} shallow - Don't create a top-most group but
2307 * append all contents directly
2308 */
2309 interpret: function (svgNode, shallow) {
2310
2311 var tag = svgNode.tagName.toLowerCase();
2312
2313 if (!(tag in Two.Utils.read)) {
2314 return null;
2315 }
2316
2317 var node = Two.Utils.read[tag].call(this, svgNode);
2318
2319 if (shallow && node instanceof Two.Group) {
2320 this.add(node.children);
2321 } else {
2322 this.add(node);
2323 }
2324
2325 return node;
2326
2327 },
2328
2329 /**
2330 * Load an SVG file / text and interpret.
2331 */
2332 load: function (text, callback) {
2333
2334 var nodes = [], elem, i;
2335
2336 if (/.*\.svg/ig.test(text)) {
2337
2338 Two.Utils.xhr(text, _.bind(function (data) {
2339
2340 dom.temp.innerHTML = data;
2341 for (i = 0; i < dom.temp.children.length; i++) {
2342 elem = dom.temp.children[i];
2343 nodes.push(this.interpret(elem));
2344 }
2345
2346 callback(nodes.length <= 1 ? nodes[0] : nodes,
2347 dom.temp.children.length <= 1 ? dom.temp.children[0] : dom.temp.children);
2348
2349 }, this));
2350
2351 return this;
2352
2353 }
2354
2355 dom.temp.innerHTML = text;
2356 for (i = 0; i < dom.temp.children.length; i++) {
2357 elem = dom.temp.children[i];
2358 nodes.push(this.interpret(elem));
2359 }
2360
2361 callback(nodes.length <= 1 ? nodes[0] : nodes,
2362 dom.temp.children.length <= 1 ? dom.temp.children[0] : dom.temp.children);
2363
2364 return this;
2365
2366 }
2367
2368 });
2369
2370 function fitToWindow() {
2371
2372 var wr = document.body.getBoundingClientRect();
2373
2374 var width = this.width = wr.width;
2375 var height = this.height = wr.height;
2376
2377 this.renderer.setSize(width, height, this.ratio);
2378 this.trigger(Two.Events.resize, width, height);
2379
2380 }
2381
2382 // Request Animation Frame
2383
2384 var raf = dom.getRequestAnimationFrame();
2385
2386 function loop() {
2387
2388 raf(loop);
2389
2390 for (var i = 0; i < Two.Instances.length; i++) {
2391 var t = Two.Instances[i];
2392 if (t.playing) {
2393 t.update();
2394 }
2395 }
2396
2397 }
2398
2399 if (typeof define === 'function' && define.amd) {
2400 define('two', [], function () {
2401 return Two;
2402 });
2403 } else if (typeof module != 'undefined' && module.exports) {
2404 module.exports = Two;
2405 }
2406
2407 return Two;
2408
2409})((typeof global !== 'undefined' ? global : this).Two);
2410
2411(function (Two) {
2412
2413 var _ = Two.Utils;
2414
2415 var Registry = Two.Registry = function () {
2416
2417 this.map = {};
2418
2419 };
2420
2421 _.extend(Registry, {
2422
2423 });
2424
2425 _.extend(Registry.prototype, {
2426
2427 add: function (id, obj) {
2428 this.map[id] = obj;
2429 return this;
2430 },
2431
2432 remove: function (id) {
2433 delete this.map[id];
2434 return this;
2435 },
2436
2437 get: function (id) {
2438 return this.map[id];
2439 },
2440
2441 contains: function (id) {
2442 return id in this.map;
2443 }
2444
2445 });
2446
2447})((typeof global !== 'undefined' ? global : this).Two);
2448
2449(function (Two) {
2450
2451 var _ = Two.Utils;
2452
2453 var Vector = Two.Vector = function (x, y) {
2454
2455 this.x = x || 0;
2456 this.y = y || 0;
2457
2458 };
2459
2460 _.extend(Vector, {
2461
2462 zero: new Two.Vector()
2463
2464 });
2465
2466 _.extend(Vector.prototype, Two.Utils.Events, {
2467
2468 set: function (x, y) {
2469 this.x = x;
2470 this.y = y;
2471 return this;
2472 },
2473
2474 copy: function (v) {
2475 this.x = v.x;
2476 this.y = v.y;
2477 return this;
2478 },
2479
2480 clear: function () {
2481 this.x = 0;
2482 this.y = 0;
2483 return this;
2484 },
2485
2486 clone: function () {
2487 return new Vector(this.x, this.y);
2488 },
2489
2490 add: function (v1, v2) {
2491 this.x = v1.x + v2.x;
2492 this.y = v1.y + v2.y;
2493 return this;
2494 },
2495
2496 addSelf: function (v) {
2497 this.x += v.x;
2498 this.y += v.y;
2499 return this;
2500 },
2501
2502 sub: function (v1, v2) {
2503 this.x = v1.x - v2.x;
2504 this.y = v1.y - v2.y;
2505 return this;
2506 },
2507
2508 subSelf: function (v) {
2509 this.x -= v.x;
2510 this.y -= v.y;
2511 return this;
2512 },
2513
2514 multiplySelf: function (v) {
2515 this.x *= v.x;
2516 this.y *= v.y;
2517 return this;
2518 },
2519
2520 multiplyScalar: function (s) {
2521 this.x *= s;
2522 this.y *= s;
2523 return this;
2524 },
2525
2526 divideScalar: function (s) {
2527 if (s) {
2528 this.x /= s;
2529 this.y /= s;
2530 } else {
2531 this.set(0, 0);
2532 }
2533 return this;
2534 },
2535
2536 negate: function () {
2537 return this.multiplyScalar(-1);
2538 },
2539
2540 dot: function (v) {
2541 return this.x * v.x + this.y * v.y;
2542 },
2543
2544 lengthSquared: function () {
2545 return this.x * this.x + this.y * this.y;
2546 },
2547
2548 length: function () {
2549 return Math.sqrt(this.lengthSquared());
2550 },
2551
2552 normalize: function () {
2553 return this.divideScalar(this.length());
2554 },
2555
2556 distanceTo: function (v) {
2557 return Math.sqrt(this.distanceToSquared(v));
2558 },
2559
2560 distanceToSquared: function (v) {
2561 var dx = this.x - v.x,
2562 dy = this.y - v.y;
2563 return dx * dx + dy * dy;
2564 },
2565
2566 setLength: function (l) {
2567 return this.normalize().multiplyScalar(l);
2568 },
2569
2570 equals: function (v, eps) {
2571 eps = (typeof eps === 'undefined') ? 0.0001 : eps;
2572 return (this.distanceTo(v) < eps);
2573 },
2574
2575 lerp: function (v, t) {
2576 var x = (v.x - this.x) * t + this.x;
2577 var y = (v.y - this.y) * t + this.y;
2578 return this.set(x, y);
2579 },
2580
2581 isZero: function (eps) {
2582 eps = (typeof eps === 'undefined') ? 0.0001 : eps;
2583 return (this.length() < eps);
2584 },
2585
2586 toString: function () {
2587 return this.x + ', ' + this.y;
2588 },
2589
2590 toObject: function () {
2591 return { x: this.x, y: this.y };
2592 },
2593
2594 rotate: function (radians) {
2595 var cos = Math.cos(radians);
2596 var sin = Math.sin(radians);
2597 this.x = this.x * cos - this.y * sin;
2598 this.y = this.x * sin + this.y * cos;
2599 return this;
2600 }
2601
2602 });
2603
2604 var BoundProto = {
2605
2606 set: function (x, y) {
2607 this._x = x;
2608 this._y = y;
2609 return this.trigger(Two.Events.change);
2610 },
2611
2612 copy: function (v) {
2613 this._x = v.x;
2614 this._y = v.y;
2615 return this.trigger(Two.Events.change);
2616 },
2617
2618 clear: function () {
2619 this._x = 0;
2620 this._y = 0;
2621 return this.trigger(Two.Events.change);
2622 },
2623
2624 clone: function () {
2625 return new Vector(this._x, this._y);
2626 },
2627
2628 add: function (v1, v2) {
2629 this._x = v1.x + v2.x;
2630 this._y = v1.y + v2.y;
2631 return this.trigger(Two.Events.change);
2632 },
2633
2634 addSelf: function (v) {
2635 this._x += v.x;
2636 this._y += v.y;
2637 return this.trigger(Two.Events.change);
2638 },
2639
2640 sub: function (v1, v2) {
2641 this._x = v1.x - v2.x;
2642 this._y = v1.y - v2.y;
2643 return this.trigger(Two.Events.change);
2644 },
2645
2646 subSelf: function (v) {
2647 this._x -= v.x;
2648 this._y -= v.y;
2649 return this.trigger(Two.Events.change);
2650 },
2651
2652 multiplySelf: function (v) {
2653 this._x *= v.x;
2654 this._y *= v.y;
2655 return this.trigger(Two.Events.change);
2656 },
2657
2658 multiplyScalar: function (s) {
2659 this._x *= s;
2660 this._y *= s;
2661 return this.trigger(Two.Events.change);
2662 },
2663
2664 divideScalar: function (s) {
2665 if (s) {
2666 this._x /= s;
2667 this._y /= s;
2668 return this.trigger(Two.Events.change);
2669 }
2670 return this.clear();
2671 },
2672
2673 negate: function () {
2674 return this.multiplyScalar(-1);
2675 },
2676
2677 dot: function (v) {
2678 return this._x * v.x + this._y * v.y;
2679 },
2680
2681 lengthSquared: function () {
2682 return this._x * this._x + this._y * this._y;
2683 },
2684
2685 length: function () {
2686 return Math.sqrt(this.lengthSquared());
2687 },
2688
2689 normalize: function () {
2690 return this.divideScalar(this.length());
2691 },
2692
2693 distanceTo: function (v) {
2694 return Math.sqrt(this.distanceToSquared(v));
2695 },
2696
2697 distanceToSquared: function (v) {
2698 var dx = this._x - v.x,
2699 dy = this._y - v.y;
2700 return dx * dx + dy * dy;
2701 },
2702
2703 setLength: function (l) {
2704 return this.normalize().multiplyScalar(l);
2705 },
2706
2707 equals: function (v, eps) {
2708 eps = (typeof eps === 'undefined') ? 0.0001 : eps;
2709 return (this.distanceTo(v) < eps);
2710 },
2711
2712 lerp: function (v, t) {
2713 var x = (v.x - this._x) * t + this._x;
2714 var y = (v.y - this._y) * t + this._y;
2715 return this.set(x, y);
2716 },
2717
2718 isZero: function (eps) {
2719 eps = (typeof eps === 'undefined') ? 0.0001 : eps;
2720 return (this.length() < eps);
2721 },
2722
2723 toString: function () {
2724 return this._x + ', ' + this._y;
2725 },
2726
2727 toObject: function () {
2728 return { x: this._x, y: this._y };
2729 },
2730
2731 rotate: function (radians) {
2732 var cos = Math.cos(radians);
2733 var sin = Math.sin(radians);
2734 this._x = this._x * cos - this._y * sin;
2735 this._y = this._x * sin + this._y * cos;
2736 return this;
2737 }
2738
2739 };
2740
2741 var xgs = {
2742 enumerable: true,
2743 get: function () {
2744 return this._x;
2745 },
2746 set: function (v) {
2747 this._x = v;
2748 this.trigger(Two.Events.change, 'x');
2749 }
2750 };
2751
2752 var ygs = {
2753 enumerable: true,
2754 get: function () {
2755 return this._y;
2756 },
2757 set: function (v) {
2758 this._y = v;
2759 this.trigger(Two.Events.change, 'y');
2760 }
2761 };
2762
2763 /**
2764 * Override Backbone bind / on in order to add properly broadcasting.
2765 * This allows Two.Vector to not broadcast events unless event listeners
2766 * are explicity bound to it.
2767 */
2768
2769 Two.Vector.prototype.bind = Two.Vector.prototype.on = function () {
2770
2771 if (!this._bound) {
2772 this._x = this.x;
2773 this._y = this.y;
2774 Object.defineProperty(this, 'x', xgs);
2775 Object.defineProperty(this, 'y', ygs);
2776 _.extend(this, BoundProto);
2777 this._bound = true; // Reserved for event initialization check
2778 }
2779
2780 Two.Utils.Events.bind.apply(this, arguments);
2781
2782 return this;
2783
2784 };
2785
2786})((typeof global !== 'undefined' ? global : this).Two);
2787
2788(function (Two) {
2789
2790 // Localized variables
2791 var commands = Two.Commands;
2792 var _ = Two.Utils;
2793
2794 /**
2795 * An object that holds 3 `Two.Vector`s, the anchor point and its
2796 * corresponding handles: `left` and `right`.
2797 */
2798 var Anchor = Two.Anchor = function (x, y, ux, uy, vx, vy, command) {
2799
2800 Two.Vector.call(this, x, y);
2801
2802 this._broadcast = _.bind(function () {
2803 this.trigger(Two.Events.change);
2804 }, this);
2805
2806 this._command = command || commands.move;
2807 this._relative = true;
2808
2809 if (!command) {
2810 return this;
2811 }
2812
2813 Anchor.AppendCurveProperties(this);
2814
2815 if (_.isNumber(ux)) {
2816 this.controls.left.x = ux;
2817 }
2818 if (_.isNumber(uy)) {
2819 this.controls.left.y = uy;
2820 }
2821 if (_.isNumber(vx)) {
2822 this.controls.right.x = vx;
2823 }
2824 if (_.isNumber(vy)) {
2825 this.controls.right.y = vy;
2826 }
2827
2828 };
2829
2830 _.extend(Anchor, {
2831
2832 AppendCurveProperties: function (anchor) {
2833 anchor.controls = {
2834 left: new Two.Vector(0, 0),
2835 right: new Two.Vector(0, 0)
2836 };
2837 }
2838
2839 });
2840
2841 var AnchorProto = {
2842
2843 listen: function () {
2844
2845 if (!_.isObject(this.controls)) {
2846 Anchor.AppendCurveProperties(this);
2847 }
2848
2849 this.controls.left.bind(Two.Events.change, this._broadcast);
2850 this.controls.right.bind(Two.Events.change, this._broadcast);
2851
2852 return this;
2853
2854 },
2855
2856 ignore: function () {
2857
2858 this.controls.left.unbind(Two.Events.change, this._broadcast);
2859 this.controls.right.unbind(Two.Events.change, this._broadcast);
2860
2861 return this;
2862
2863 },
2864
2865 clone: function () {
2866
2867 var controls = this.controls;
2868
2869 var clone = new Two.Anchor(
2870 this.x,
2871 this.y,
2872 controls && controls.left.x,
2873 controls && controls.left.y,
2874 controls && controls.right.x,
2875 controls && controls.right.y,
2876 this.command
2877 );
2878 clone.relative = this._relative;
2879 return clone;
2880
2881 },
2882
2883 toObject: function () {
2884 var o = {
2885 x: this.x,
2886 y: this.y
2887 };
2888 if (this._command) {
2889 o.command = this._command;
2890 }
2891 if (this._relative) {
2892 o.relative = this._relative;
2893 }
2894 if (this.controls) {
2895 o.controls = {
2896 left: this.controls.left.toObject(),
2897 right: this.controls.right.toObject()
2898 };
2899 }
2900 return o;
2901 },
2902
2903 toString: function () {
2904 if (!this.controls) {
2905 return [this._x, this._y].join(', ');
2906 }
2907 return [this._x, this._y, this.controls.left.x, this.controls.left.y,
2908 this.controls.right.x, this.controls.right.y].join(', ');
2909 }
2910
2911 };
2912
2913 Object.defineProperty(Anchor.prototype, 'command', {
2914
2915 enumerable: true,
2916
2917 get: function () {
2918 return this._command;
2919 },
2920
2921 set: function (c) {
2922 this._command = c;
2923 if (this._command === commands.curve && !_.isObject(this.controls)) {
2924 Anchor.AppendCurveProperties(this);
2925 }
2926 return this.trigger(Two.Events.change);
2927 }
2928
2929 });
2930
2931 Object.defineProperty(Anchor.prototype, 'relative', {
2932
2933 enumerable: true,
2934
2935 get: function () {
2936 return this._relative;
2937 },
2938
2939 set: function (b) {
2940 if (this._relative == b) {
2941 return this;
2942 }
2943 this._relative = !!b;
2944 return this.trigger(Two.Events.change);
2945 }
2946
2947 });
2948
2949 _.extend(Anchor.prototype, Two.Vector.prototype, AnchorProto);
2950
2951 // Make it possible to bind and still have the Anchor specific
2952 // inheritance from Two.Vector
2953 Two.Anchor.prototype.bind = Two.Anchor.prototype.on = function () {
2954 Two.Vector.prototype.bind.apply(this, arguments);
2955 _.extend(this, AnchorProto);
2956 };
2957
2958 Two.Anchor.prototype.unbind = Two.Anchor.prototype.off = function () {
2959 Two.Vector.prototype.unbind.apply(this, arguments);
2960 _.extend(this, AnchorProto);
2961 };
2962
2963})((typeof global !== 'undefined' ? global : this).Two);
2964
2965(function (Two) {
2966
2967 /**
2968 * Constants
2969 */
2970 var cos = Math.cos, sin = Math.sin, tan = Math.tan;
2971 var _ = Two.Utils;
2972
2973 /**
2974 * Two.Matrix contains an array of elements that represent
2975 * the two dimensional 3 x 3 matrix as illustrated below:
2976 *
2977 * =====
2978 * a b c
2979 * d e f
2980 * g h i // this row is not really used in 2d transformations
2981 * =====
2982 *
2983 * String order is for transform strings: a, d, b, e, c, f
2984 *
2985 * @class
2986 */
2987 var Matrix = Two.Matrix = function (a, b, c, d, e, f) {
2988
2989 this.elements = new Two.Array(9);
2990
2991 var elements = a;
2992 if (!_.isArray(elements)) {
2993 elements = _.toArray(arguments);
2994 }
2995
2996 // initialize the elements with default values.
2997
2998 this.identity().set(elements);
2999
3000 };
3001
3002 _.extend(Matrix, {
3003
3004 Identity: [
3005 1, 0, 0,
3006 0, 1, 0,
3007 0, 0, 1
3008 ],
3009
3010 /**
3011 * Multiply two matrix 3x3 arrays
3012 */
3013 Multiply: function (A, B, C) {
3014
3015 if (B.length <= 3) { // Multiply Vector
3016
3017 var x, y, z, e = A;
3018
3019 var a = B[0] || 0,
3020 b = B[1] || 0,
3021 c = B[2] || 0;
3022
3023 // Go down rows first
3024 // a, d, g, b, e, h, c, f, i
3025
3026 x = e[0] * a + e[1] * b + e[2] * c;
3027 y = e[3] * a + e[4] * b + e[5] * c;
3028 z = e[6] * a + e[7] * b + e[8] * c;
3029
3030 return { x: x, y: y, z: z };
3031
3032 }
3033
3034 var A0 = A[0], A1 = A[1], A2 = A[2];
3035 var A3 = A[3], A4 = A[4], A5 = A[5];
3036 var A6 = A[6], A7 = A[7], A8 = A[8];
3037
3038 var B0 = B[0], B1 = B[1], B2 = B[2];
3039 var B3 = B[3], B4 = B[4], B5 = B[5];
3040 var B6 = B[6], B7 = B[7], B8 = B[8];
3041
3042 C = C || new Two.Array(9);
3043
3044 C[0] = A0 * B0 + A1 * B3 + A2 * B6;
3045 C[1] = A0 * B1 + A1 * B4 + A2 * B7;
3046 C[2] = A0 * B2 + A1 * B5 + A2 * B8;
3047 C[3] = A3 * B0 + A4 * B3 + A5 * B6;
3048 C[4] = A3 * B1 + A4 * B4 + A5 * B7;
3049 C[5] = A3 * B2 + A4 * B5 + A5 * B8;
3050 C[6] = A6 * B0 + A7 * B3 + A8 * B6;
3051 C[7] = A6 * B1 + A7 * B4 + A8 * B7;
3052 C[8] = A6 * B2 + A7 * B5 + A8 * B8;
3053
3054 return C;
3055
3056 }
3057
3058 });
3059
3060 _.extend(Matrix.prototype, Two.Utils.Events, {
3061
3062 /**
3063 * Takes an array of elements or the arguments list itself to
3064 * set and update the current matrix's elements. Only updates
3065 * specified values.
3066 */
3067 set: function (a) {
3068
3069 var elements = a;
3070 if (!_.isArray(elements)) {
3071 elements = _.toArray(arguments);
3072 }
3073
3074 _.extend(this.elements, elements);
3075
3076 return this.trigger(Two.Events.change);
3077
3078 },
3079
3080 /**
3081 * Turn matrix to identity, like resetting.
3082 */
3083 identity: function () {
3084
3085 this.set(Matrix.Identity);
3086
3087 return this;
3088
3089 },
3090
3091 /**
3092 * Multiply scalar or multiply by another matrix.
3093 */
3094 multiply: function (a, b, c, d, e, f, g, h, i) {
3095
3096 var elements = arguments, l = elements.length;
3097
3098 // Multiply scalar
3099
3100 if (l <= 1) {
3101
3102 _.each(this.elements, function (v, i) {
3103 this.elements[i] = v * a;
3104 }, this);
3105
3106 return this.trigger(Two.Events.change);
3107
3108 }
3109
3110 if (l <= 3) { // Multiply Vector
3111
3112 var x, y, z;
3113 a = a || 0;
3114 b = b || 0;
3115 c = c || 0;
3116 e = this.elements;
3117
3118 // Go down rows first
3119 // a, d, g, b, e, h, c, f, i
3120
3121 x = e[0] * a + e[1] * b + e[2] * c;
3122 y = e[3] * a + e[4] * b + e[5] * c;
3123 z = e[6] * a + e[7] * b + e[8] * c;
3124
3125 return { x: x, y: y, z: z };
3126
3127 }
3128
3129 // Multiple matrix
3130
3131 var A = this.elements;
3132 var B = elements;
3133
3134 var A0 = A[0], A1 = A[1], A2 = A[2];
3135 var A3 = A[3], A4 = A[4], A5 = A[5];
3136 var A6 = A[6], A7 = A[7], A8 = A[8];
3137
3138 var B0 = B[0], B1 = B[1], B2 = B[2];
3139 var B3 = B[3], B4 = B[4], B5 = B[5];
3140 var B6 = B[6], B7 = B[7], B8 = B[8];
3141
3142 this.elements[0] = A0 * B0 + A1 * B3 + A2 * B6;
3143 this.elements[1] = A0 * B1 + A1 * B4 + A2 * B7;
3144 this.elements[2] = A0 * B2 + A1 * B5 + A2 * B8;
3145
3146 this.elements[3] = A3 * B0 + A4 * B3 + A5 * B6;
3147 this.elements[4] = A3 * B1 + A4 * B4 + A5 * B7;
3148 this.elements[5] = A3 * B2 + A4 * B5 + A5 * B8;
3149
3150 this.elements[6] = A6 * B0 + A7 * B3 + A8 * B6;
3151 this.elements[7] = A6 * B1 + A7 * B4 + A8 * B7;
3152 this.elements[8] = A6 * B2 + A7 * B5 + A8 * B8;
3153
3154 return this.trigger(Two.Events.change);
3155
3156 },
3157
3158 inverse: function (out) {
3159
3160 var a = this.elements;
3161 out = out || new Two.Matrix();
3162
3163 var a00 = a[0], a01 = a[1], a02 = a[2];
3164 var a10 = a[3], a11 = a[4], a12 = a[5];
3165 var a20 = a[6], a21 = a[7], a22 = a[8];
3166
3167 var b01 = a22 * a11 - a12 * a21;
3168 var b11 = -a22 * a10 + a12 * a20;
3169 var b21 = a21 * a10 - a11 * a20;
3170
3171 // Calculate the determinant
3172 var det = a00 * b01 + a01 * b11 + a02 * b21;
3173
3174 if (!det) {
3175 return null;
3176 }
3177
3178 det = 1.0 / det;
3179
3180 out.elements[0] = b01 * det;
3181 out.elements[1] = (-a22 * a01 + a02 * a21) * det;
3182 out.elements[2] = (a12 * a01 - a02 * a11) * det;
3183 out.elements[3] = b11 * det;
3184 out.elements[4] = (a22 * a00 - a02 * a20) * det;
3185 out.elements[5] = (-a12 * a00 + a02 * a10) * det;
3186 out.elements[6] = b21 * det;
3187 out.elements[7] = (-a21 * a00 + a01 * a20) * det;
3188 out.elements[8] = (a11 * a00 - a01 * a10) * det;
3189
3190 return out;
3191
3192 },
3193
3194 /**
3195 * Set a scalar onto the matrix.
3196 */
3197 scale: function (sx, sy) {
3198
3199 var l = arguments.length;
3200 if (l <= 1) {
3201 sy = sx;
3202 }
3203
3204 return this.multiply(sx, 0, 0, 0, sy, 0, 0, 0, 1);
3205
3206 },
3207
3208 /**
3209 * Rotate the matrix.
3210 */
3211 rotate: function (radians) {
3212
3213 var c = cos(radians);
3214 var s = sin(radians);
3215
3216 return this.multiply(c, -s, 0, s, c, 0, 0, 0, 1);
3217
3218 },
3219
3220 /**
3221 * Translate the matrix.
3222 */
3223 translate: function (x, y) {
3224
3225 return this.multiply(1, 0, x, 0, 1, y, 0, 0, 1);
3226
3227 },
3228
3229 /*
3230 * Skew the matrix by an angle in the x axis direction.
3231 */
3232 skewX: function (radians) {
3233
3234 var a = tan(radians);
3235
3236 return this.multiply(1, a, 0, 0, 1, 0, 0, 0, 1);
3237
3238 },
3239
3240 /*
3241 * Skew the matrix by an angle in the y axis direction.
3242 */
3243 skewY: function (radians) {
3244
3245 var a = tan(radians);
3246
3247 return this.multiply(1, 0, 0, a, 1, 0, 0, 0, 1);
3248
3249 },
3250
3251 /**
3252 * Create a transform string to be used with rendering apis.
3253 */
3254 toString: function (fullMatrix) {
3255 var temp = [];
3256
3257 this.toArray(fullMatrix, temp);
3258
3259 return temp.join(' ');
3260
3261 },
3262
3263 /**
3264 * Create a transform array to be used with rendering apis.
3265 */
3266 toArray: function (fullMatrix, output) {
3267
3268 var elements = this.elements;
3269 var hasOutput = !!output;
3270
3271 var a = parseFloat(elements[0].toFixed(3));
3272 var b = parseFloat(elements[1].toFixed(3));
3273 var c = parseFloat(elements[2].toFixed(3));
3274 var d = parseFloat(elements[3].toFixed(3));
3275 var e = parseFloat(elements[4].toFixed(3));
3276 var f = parseFloat(elements[5].toFixed(3));
3277
3278 if (!!fullMatrix) {
3279
3280 var g = parseFloat(elements[6].toFixed(3));
3281 var h = parseFloat(elements[7].toFixed(3));
3282 var i = parseFloat(elements[8].toFixed(3));
3283
3284 if (hasOutput) {
3285 output[0] = a;
3286 output[1] = d;
3287 output[2] = g;
3288 output[3] = b;
3289 output[4] = e;
3290 output[5] = h;
3291 output[6] = c;
3292 output[7] = f;
3293 output[8] = i;
3294 return;
3295 }
3296
3297 return [
3298 a, d, g, b, e, h, c, f, i
3299 ];
3300 }
3301
3302 if (hasOutput) {
3303 output[0] = a;
3304 output[1] = d;
3305 output[2] = b;
3306 output[3] = e;
3307 output[4] = c;
3308 output[5] = f;
3309 return;
3310 }
3311
3312 return [
3313 a, d, b, e, c, f // Specific format see LN:19
3314 ];
3315
3316 },
3317
3318 /**
3319 * Clone the current matrix.
3320 */
3321 clone: function () {
3322 var a, b, c, d, e, f, g, h, i;
3323
3324 a = this.elements[0];
3325 b = this.elements[1];
3326 c = this.elements[2];
3327 d = this.elements[3];
3328 e = this.elements[4];
3329 f = this.elements[5];
3330 g = this.elements[6];
3331 h = this.elements[7];
3332 i = this.elements[8];
3333
3334 return new Two.Matrix(a, b, c, d, e, f, g, h, i);
3335
3336 }
3337
3338 });
3339
3340})((typeof global !== 'undefined' ? global : this).Two);
3341
3342(function (Two) {
3343
3344 // Localize variables
3345 var mod = Two.Utils.mod, toFixed = Two.Utils.toFixed;
3346 var _ = Two.Utils;
3347
3348 var svg = {
3349
3350 version: 1.1,
3351
3352 ns: 'http://www.w3.org/2000/svg',
3353 xlink: 'http://www.w3.org/1999/xlink',
3354
3355 alignments: {
3356 left: 'start',
3357 center: 'middle',
3358 right: 'end'
3359 },
3360
3361 /**
3362 * Create an svg namespaced element.
3363 */
3364 createElement: function (name, attrs) {
3365 var tag = name;
3366 var elem = document.createElementNS(svg.ns, tag);
3367 if (tag === 'svg') {
3368 attrs = _.defaults(attrs || {}, {
3369 version: svg.version
3370 });
3371 }
3372 if (!_.isEmpty(attrs)) {
3373 svg.setAttributes(elem, attrs);
3374 }
3375 return elem;
3376 },
3377
3378 /**
3379 * Add attributes from an svg element.
3380 */
3381 setAttributes: function (elem, attrs) {
3382 var keys = Object.keys(attrs);
3383 for (var i = 0; i < keys.length; i++) {
3384 if (/href/.test(keys[i])) {
3385 elem.setAttributeNS(svg.xlink, keys[i], attrs[keys[i]]);
3386 } else {
3387 elem.setAttribute(keys[i], attrs[keys[i]]);
3388 }
3389 }
3390 return this;
3391 },
3392
3393 /**
3394 * Remove attributes from an svg element.
3395 */
3396 removeAttributes: function (elem, attrs) {
3397 for (var key in attrs) {
3398 elem.removeAttribute(key);
3399 }
3400 return this;
3401 },
3402
3403 /**
3404 * Turn a set of vertices into a string for the d property of a path
3405 * element. It is imperative that the string collation is as fast as
3406 * possible, because this call will be happening multiple times a
3407 * second.
3408 */
3409 toString: function (points, closed) {
3410
3411 var l = points.length,
3412 last = l - 1,
3413 d, // The elusive last Two.Commands.move point
3414 ret = '';
3415
3416 for (var i = 0; i < l; i++) {
3417 var b = points[i];
3418 var command;
3419 var prev = closed ? mod(i - 1, l) : Math.max(i - 1, 0);
3420 var next = closed ? mod(i + 1, l) : Math.min(i + 1, last);
3421
3422 var a = points[prev];
3423 var c = points[next];
3424
3425 var vx, vy, ux, uy, ar, bl, br, cl;
3426
3427 // Access x and y directly,
3428 // bypassing the getter
3429 var x = toFixed(b._x);
3430 var y = toFixed(b._y);
3431
3432 switch (b._command) {
3433
3434 case Two.Commands.close:
3435 command = Two.Commands.close;
3436 break;
3437
3438 case Two.Commands.curve:
3439
3440 ar = (a.controls && a.controls.right) || Two.Vector.zero;
3441 bl = (b.controls && b.controls.left) || Two.Vector.zero;
3442
3443 if (a._relative) {
3444 vx = toFixed((ar.x + a.x));
3445 vy = toFixed((ar.y + a.y));
3446 } else {
3447 vx = toFixed(ar.x);
3448 vy = toFixed(ar.y);
3449 }
3450
3451 if (b._relative) {
3452 ux = toFixed((bl.x + b.x));
3453 uy = toFixed((bl.y + b.y));
3454 } else {
3455 ux = toFixed(bl.x);
3456 uy = toFixed(bl.y);
3457 }
3458
3459 command = ((i === 0) ? Two.Commands.move : Two.Commands.curve) +
3460 ' ' + vx + ' ' + vy + ' ' + ux + ' ' + uy + ' ' + x + ' ' + y;
3461 break;
3462
3463 case Two.Commands.move:
3464 d = b;
3465 command = Two.Commands.move + ' ' + x + ' ' + y;
3466 break;
3467
3468 default:
3469 command = b._command + ' ' + x + ' ' + y;
3470
3471 }
3472
3473 // Add a final point and close it off
3474
3475 if (i >= last && closed) {
3476
3477 if (b._command === Two.Commands.curve) {
3478
3479 // Make sure we close to the most previous Two.Commands.move
3480 c = d;
3481
3482 br = (b.controls && b.controls.right) || b;
3483 cl = (c.controls && c.controls.left) || c;
3484
3485 if (b._relative) {
3486 vx = toFixed((br.x + b.x));
3487 vy = toFixed((br.y + b.y));
3488 } else {
3489 vx = toFixed(br.x);
3490 vy = toFixed(br.y);
3491 }
3492
3493 if (c._relative) {
3494 ux = toFixed((cl.x + c.x));
3495 uy = toFixed((cl.y + c.y));
3496 } else {
3497 ux = toFixed(cl.x);
3498 uy = toFixed(cl.y);
3499 }
3500
3501 x = toFixed(c.x);
3502 y = toFixed(c.y);
3503
3504 command +=
3505 ' C ' + vx + ' ' + vy + ' ' + ux + ' ' + uy + ' ' + x + ' ' + y;
3506 }
3507
3508 command += ' Z';
3509
3510 }
3511
3512 ret += command + ' ';
3513
3514 }
3515
3516 return ret;
3517
3518 },
3519
3520 getClip: function (shape) {
3521
3522 var clip = shape._renderer.clip;
3523
3524 if (!clip) {
3525
3526 var root = shape;
3527
3528 while (root.parent) {
3529 root = root.parent;
3530 }
3531
3532 clip = shape._renderer.clip = svg.createElement('clipPath');
3533 root.defs.appendChild(clip);
3534
3535 }
3536
3537 return clip;
3538
3539 },
3540
3541 group: {
3542
3543 // TODO: Can speed up.
3544 // TODO: How does this effect a f
3545 appendChild: function (object) {
3546
3547 var elem = object._renderer.elem;
3548
3549 if (!elem) {
3550 return;
3551 }
3552
3553 var tag = elem.nodeName;
3554
3555 if (!tag || /(radial|linear)gradient/i.test(tag) || object._clip) {
3556 return;
3557 }
3558
3559 this.elem.appendChild(elem);
3560
3561 },
3562
3563 removeChild: function (object) {
3564
3565 var elem = object._renderer.elem;
3566
3567 if (!elem || elem.parentNode != this.elem) {
3568 return;
3569 }
3570
3571 var tag = elem.nodeName;
3572
3573 if (!tag) {
3574 return;
3575 }
3576
3577 // Defer subtractions while clipping.
3578 if (object._clip) {
3579 return;
3580 }
3581
3582 this.elem.removeChild(elem);
3583
3584 },
3585
3586 orderChild: function (object) {
3587 this.elem.appendChild(object._renderer.elem);
3588 },
3589
3590 renderChild: function (child) {
3591 svg[child._renderer.type].render.call(child, this);
3592 },
3593
3594 render: function (domElement) {
3595
3596 this._update();
3597
3598 // Shortcut for hidden objects.
3599 // Doesn't reset the flags, so changes are stored and
3600 // applied once the object is visible again
3601 if (this._opacity === 0 && !this._flagOpacity) {
3602 return this;
3603 }
3604
3605 if (!this._renderer.elem) {
3606 this._renderer.elem = svg.createElement('g', {
3607 id: this.id
3608 });
3609 domElement.appendChild(this._renderer.elem);
3610 }
3611
3612 // _Update styles for the <g>
3613 var flagMatrix = this._matrix.manual || this._flagMatrix;
3614 var context = {
3615 domElement: domElement,
3616 elem: this._renderer.elem
3617 };
3618
3619 if (flagMatrix) {
3620 this._renderer.elem.setAttribute('transform', 'matrix(' + this._matrix.toString() + ')');
3621 }
3622
3623 for (var i = 0; i < this.children.length; i++) {
3624 var child = this.children[i];
3625 svg[child._renderer.type].render.call(child, domElement);
3626 }
3627
3628 if (this._flagOpacity) {
3629 this._renderer.elem.setAttribute('opacity', this._opacity);
3630 }
3631
3632 if (this._flagAdditions) {
3633 this.additions.forEach(svg.group.appendChild, context);
3634 }
3635
3636 if (this._flagSubtractions) {
3637 this.subtractions.forEach(svg.group.removeChild, context);
3638 }
3639
3640 if (this._flagOrder) {
3641 this.children.forEach(svg.group.orderChild, context);
3642 }
3643
3644 /**
3645 * Commented two-way functionality of clips / masks with groups and
3646 * polygons. Uncomment when this bug is fixed:
3647 * https://code.google.com/p/chromium/issues/detail?id=370951
3648 */
3649
3650 // if (this._flagClip) {
3651
3652 // clip = svg.getClip(this);
3653 // elem = this._renderer.elem;
3654
3655 // if (this._clip) {
3656 // elem.removeAttribute('id');
3657 // clip.setAttribute('id', this.id);
3658 // clip.appendChild(elem);
3659 // } else {
3660 // clip.removeAttribute('id');
3661 // elem.setAttribute('id', this.id);
3662 // this.parent._renderer.elem.appendChild(elem); // TODO: should be insertBefore
3663 // }
3664
3665 // }
3666
3667 if (this._flagMask) {
3668 if (this._mask) {
3669 this._renderer.elem.setAttribute('clip-path', 'url(#' + this._mask.id + ')');
3670 } else {
3671 this._renderer.elem.removeAttribute('clip-path');
3672 }
3673 }
3674
3675 return this.flagReset();
3676
3677 }
3678
3679 },
3680
3681 path: {
3682
3683 render: function (domElement) {
3684
3685 this._update();
3686
3687 // Shortcut for hidden objects.
3688 // Doesn't reset the flags, so changes are stored and
3689 // applied once the object is visible again
3690 if (this._opacity === 0 && !this._flagOpacity) {
3691 return this;
3692 }
3693
3694 // Collect any attribute that needs to be changed here
3695 var changed = {};
3696
3697 var flagMatrix = this._matrix.manual || this._flagMatrix;
3698
3699 if (flagMatrix) {
3700 changed.transform = 'matrix(' + this._matrix.toString() + ')';
3701 }
3702
3703 if (this._flagVertices) {
3704 var vertices = svg.toString(this._vertices, this._closed);
3705 changed.d = vertices;
3706 }
3707
3708 if (this._fill && this._fill._renderer) {
3709 this._fill._update();
3710 svg[this._fill._renderer.type].render.call(this._fill, domElement, true);
3711 }
3712
3713 if (this._flagFill) {
3714 changed.fill = this._fill && this._fill.id
3715 ? 'url(#' + this._fill.id + ')' : this._fill;
3716 }
3717
3718 if (this._stroke && this._stroke._renderer) {
3719 this._stroke._update();
3720 svg[this._stroke._renderer.type].render.call(this._stroke, domElement, true);
3721 }
3722
3723 if (this._flagStroke) {
3724 changed.stroke = this._stroke && this._stroke.id
3725 ? 'url(#' + this._stroke.id + ')' : this._stroke;
3726 }
3727
3728 if (this._flagLinewidth) {
3729 changed['stroke-width'] = this._linewidth;
3730 }
3731
3732 if (this._flagOpacity) {
3733 changed['stroke-opacity'] = this._opacity;
3734 changed['fill-opacity'] = this._opacity;
3735 }
3736
3737 if (this._flagVisible) {
3738 changed.visibility = this._visible ? 'visible' : 'hidden';
3739 }
3740
3741 if (this._flagCap) {
3742 changed['stroke-linecap'] = this._cap;
3743 }
3744
3745 if (this._flagJoin) {
3746 changed['stroke-linejoin'] = this._join;
3747 }
3748
3749 if (this._flagMiter) {
3750 changed['stroke-miterlimit'] = this._miter;
3751 }
3752
3753 // If there is no attached DOM element yet,
3754 // create it with all necessary attributes.
3755 if (!this._renderer.elem) {
3756
3757 changed.id = this.id;
3758 this._renderer.elem = svg.createElement('path', changed);
3759 domElement.appendChild(this._renderer.elem);
3760
3761 // Otherwise apply all pending attributes
3762 } else {
3763 svg.setAttributes(this._renderer.elem, changed);
3764 }
3765
3766 if (this._flagClip) {
3767
3768 var clip = svg.getClip(this);
3769 var elem = this._renderer.elem;
3770
3771 if (this._clip) {
3772 elem.removeAttribute('id');
3773 clip.setAttribute('id', this.id);
3774 clip.appendChild(elem);
3775 } else {
3776 clip.removeAttribute('id');
3777 elem.setAttribute('id', this.id);
3778 this.parent._renderer.elem.appendChild(elem); // TODO: should be insertBefore
3779 }
3780
3781 }
3782
3783 /**
3784 * Commented two-way functionality of clips / masks with groups and
3785 * polygons. Uncomment when this bug is fixed:
3786 * https://code.google.com/p/chromium/issues/detail?id=370951
3787 */
3788
3789 // if (this._flagMask) {
3790 // if (this._mask) {
3791 // elem.setAttribute('clip-path', 'url(#' + this._mask.id + ')');
3792 // } else {
3793 // elem.removeAttribute('clip-path');
3794 // }
3795 // }
3796
3797 return this.flagReset();
3798
3799 }
3800
3801 },
3802
3803 text: {
3804
3805 render: function (domElement) {
3806
3807 this._update();
3808
3809 var changed = {};
3810
3811 var flagMatrix = this._matrix.manual || this._flagMatrix;
3812
3813 if (flagMatrix) {
3814 changed.transform = 'matrix(' + this._matrix.toString() + ')';
3815 }
3816
3817 if (this._flagFamily) {
3818 changed['font-family'] = this._family;
3819 }
3820 if (this._flagSize) {
3821 changed['font-size'] = this._size;
3822 }
3823 if (this._flagLeading) {
3824 changed['line-height'] = this._leading;
3825 }
3826 if (this._flagAlignment) {
3827 changed['text-anchor'] = svg.alignments[this._alignment] || this._alignment;
3828 }
3829 if (this._flagBaseline) {
3830 changed['alignment-baseline'] = changed['dominant-baseline'] = this._baseline;
3831 }
3832 if (this._flagStyle) {
3833 changed['font-style'] = this._style;
3834 }
3835 if (this._flagWeight) {
3836 changed['font-weight'] = this._weight;
3837 }
3838 if (this._flagDecoration) {
3839 changed['text-decoration'] = this._decoration;
3840 }
3841 if (this._fill && this._fill._renderer) {
3842 this._fill._update();
3843 svg[this._fill._renderer.type].render.call(this._fill, domElement, true);
3844 }
3845 if (this._flagFill) {
3846 changed.fill = this._fill && this._fill.id
3847 ? 'url(#' + this._fill.id + ')' : this._fill;
3848 }
3849 if (this._stroke && this._stroke._renderer) {
3850 this._stroke._update();
3851 svg[this._stroke._renderer.type].render.call(this._stroke, domElement, true);
3852 }
3853 if (this._flagStroke) {
3854 changed.stroke = this._stroke && this._stroke.id
3855 ? 'url(#' + this._stroke.id + ')' : this._stroke;
3856 }
3857 if (this._flagLinewidth) {
3858 changed['stroke-width'] = this._linewidth;
3859 }
3860 if (this._flagOpacity) {
3861 changed.opacity = this._opacity;
3862 }
3863 if (this._flagVisible) {
3864 changed.visibility = this._visible ? 'visible' : 'hidden';
3865 }
3866
3867 if (!this._renderer.elem) {
3868
3869 changed.id = this.id;
3870
3871 this._renderer.elem = svg.createElement('text', changed);
3872 domElement.defs.appendChild(this._renderer.elem);
3873
3874 } else {
3875
3876 svg.setAttributes(this._renderer.elem, changed);
3877
3878 }
3879
3880 if (this._flagClip) {
3881
3882 var clip = svg.getClip(this);
3883 var elem = this._renderer.elem;
3884
3885 if (this._clip) {
3886 elem.removeAttribute('id');
3887 clip.setAttribute('id', this.id);
3888 clip.appendChild(elem);
3889 } else {
3890 clip.removeAttribute('id');
3891 elem.setAttribute('id', this.id);
3892 this.parent._renderer.elem.appendChild(elem); // TODO: should be insertBefore
3893 }
3894
3895 }
3896
3897 if (this._flagValue) {
3898 this._renderer.elem.textContent = this._value;
3899 }
3900
3901 return this.flagReset();
3902
3903 }
3904
3905 },
3906
3907 'linear-gradient': {
3908
3909 render: function (domElement, silent) {
3910
3911 if (!silent) {
3912 this._update();
3913 }
3914
3915 var changed = {};
3916
3917 if (this._flagEndPoints) {
3918 changed.x1 = this.left._x;
3919 changed.y1 = this.left._y;
3920 changed.x2 = this.right._x;
3921 changed.y2 = this.right._y;
3922 }
3923
3924 if (this._flagSpread) {
3925 changed.spreadMethod = this._spread;
3926 }
3927
3928 // If there is no attached DOM element yet,
3929 // create it with all necessary attributes.
3930 if (!this._renderer.elem) {
3931
3932 changed.id = this.id;
3933 changed.gradientUnits = 'userSpaceOnUse';
3934 this._renderer.elem = svg.createElement('linearGradient', changed);
3935 domElement.defs.appendChild(this._renderer.elem);
3936
3937 // Otherwise apply all pending attributes
3938 } else {
3939
3940 svg.setAttributes(this._renderer.elem, changed);
3941
3942 }
3943
3944 if (this._flagStops) {
3945
3946 var lengthChanged = this._renderer.elem.childNodes.length
3947 !== this.stops.length;
3948
3949 if (lengthChanged) {
3950 this._renderer.elem.childNodes.length = 0;
3951 }
3952
3953 for (var i = 0; i < this.stops.length; i++) {
3954
3955 var stop = this.stops[i];
3956 var attrs = {};
3957
3958 if (stop._flagOffset) {
3959 attrs.offset = 100 * stop._offset + '%';
3960 }
3961 if (stop._flagColor) {
3962 attrs['stop-color'] = stop._color;
3963 }
3964 if (stop._flagOpacity) {
3965 attrs['stop-opacity'] = stop._opacity;
3966 }
3967
3968 if (!stop._renderer.elem) {
3969 stop._renderer.elem = svg.createElement('stop', attrs);
3970 } else {
3971 svg.setAttributes(stop._renderer.elem, attrs);
3972 }
3973
3974 if (lengthChanged) {
3975 this._renderer.elem.appendChild(stop._renderer.elem);
3976 }
3977 stop.flagReset();
3978
3979 }
3980
3981 }
3982
3983 return this.flagReset();
3984
3985 }
3986
3987 },
3988
3989 'radial-gradient': {
3990
3991 render: function (domElement, silent) {
3992
3993 if (!silent) {
3994 this._update();
3995 }
3996
3997 var changed = {};
3998
3999 if (this._flagCenter) {
4000 changed.cx = this.center._x;
4001 changed.cy = this.center._y;
4002 }
4003 if (this._flagFocal) {
4004 changed.fx = this.focal._x;
4005 changed.fy = this.focal._y;
4006 }
4007
4008 if (this._flagRadius) {
4009 changed.r = this._radius;
4010 }
4011
4012 if (this._flagSpread) {
4013 changed.spreadMethod = this._spread;
4014 }
4015
4016 // If there is no attached DOM element yet,
4017 // create it with all necessary attributes.
4018 if (!this._renderer.elem) {
4019
4020 changed.id = this.id;
4021 changed.gradientUnits = 'userSpaceOnUse';
4022 this._renderer.elem = svg.createElement('radialGradient', changed);
4023 domElement.defs.appendChild(this._renderer.elem);
4024
4025 // Otherwise apply all pending attributes
4026 } else {
4027
4028 svg.setAttributes(this._renderer.elem, changed);
4029
4030 }
4031
4032 if (this._flagStops) {
4033
4034 var lengthChanged = this._renderer.elem.childNodes.length
4035 !== this.stops.length;
4036
4037 if (lengthChanged) {
4038 this._renderer.elem.childNodes.length = 0;
4039 }
4040
4041 for (var i = 0; i < this.stops.length; i++) {
4042
4043 var stop = this.stops[i];
4044 var attrs = {};
4045
4046 if (stop._flagOffset) {
4047 attrs.offset = 100 * stop._offset + '%';
4048 }
4049 if (stop._flagColor) {
4050 attrs['stop-color'] = stop._color;
4051 }
4052 if (stop._flagOpacity) {
4053 attrs['stop-opacity'] = stop._opacity;
4054 }
4055
4056 if (!stop._renderer.elem) {
4057 stop._renderer.elem = svg.createElement('stop', attrs);
4058 } else {
4059 svg.setAttributes(stop._renderer.elem, attrs);
4060 }
4061
4062 if (lengthChanged) {
4063 this._renderer.elem.appendChild(stop._renderer.elem);
4064 }
4065 stop.flagReset();
4066
4067 }
4068
4069 }
4070
4071 return this.flagReset();
4072
4073 }
4074
4075 },
4076
4077 texture: {
4078
4079 render: function (domElement, silent) {
4080
4081 if (!silent) {
4082 this._update();
4083 }
4084
4085 var changed = {};
4086 var styles = { x: 0, y: 0 };
4087 var image = this.image;
4088
4089 if (this._flagLoaded && this.loaded) {
4090
4091 switch (image.nodeName.toLowerCase()) {
4092
4093 case 'canvas':
4094 styles.href = styles['xlink:href'] = image.toDataURL('image/png');
4095 break;
4096 case 'img':
4097 case 'image':
4098 styles.href = styles['xlink:href'] = this.src;
4099 break;
4100
4101 }
4102
4103 }
4104
4105 if (this._flagOffset || this._flagLoaded || this._flagScale) {
4106
4107 changed.x = this._offset.x;
4108 changed.y = this._offset.y;
4109
4110 if (image) {
4111
4112 changed.x -= image.width / 2;
4113 changed.y -= image.height / 2;
4114
4115 if (this._scale instanceof Two.Vector) {
4116 changed.x *= this._scale.x;
4117 changed.y *= this._scale.y;
4118 } else {
4119 changed.x *= this._scale;
4120 changed.y *= this._scale;
4121 }
4122 }
4123
4124 if (changed.x > 0) {
4125 changed.x *= - 1;
4126 }
4127 if (changed.y > 0) {
4128 changed.y *= - 1;
4129 }
4130
4131 }
4132
4133 if (this._flagScale || this._flagLoaded || this._flagRepeat) {
4134
4135 changed.width = 0;
4136 changed.height = 0;
4137
4138 if (image) {
4139
4140 styles.width = changed.width = image.width;
4141 styles.height = changed.height = image.height;
4142
4143 // TODO: Hack / Bandaid
4144 switch (this._repeat) {
4145 case 'no-repeat':
4146 changed.width += 1;
4147 changed.height += 1;
4148 break;
4149 }
4150
4151 if (this._scale instanceof Two.Vector) {
4152 changed.width *= this._scale.x;
4153 changed.height *= this._scale.y;
4154 } else {
4155 changed.width *= this._scale;
4156 changed.height *= this._scale;
4157 }
4158 }
4159
4160 }
4161
4162 if (this._flagScale || this._flagLoaded) {
4163 if (!this._renderer.image) {
4164 this._renderer.image = svg.createElement('image', styles);
4165 } else if (!_.isEmpty(styles)) {
4166 svg.setAttributes(this._renderer.image, styles);
4167 }
4168 }
4169
4170 if (!this._renderer.elem) {
4171
4172 changed.id = this.id;
4173 changed.patternUnits = 'userSpaceOnUse';
4174 this._renderer.elem = svg.createElement('pattern', changed);
4175 domElement.defs.appendChild(this._renderer.elem);
4176
4177 } else if (!_.isEmpty(changed)) {
4178
4179 svg.setAttributes(this._renderer.elem, changed);
4180
4181 }
4182
4183 if (this._renderer.elem && this._renderer.image && !this._renderer.appended) {
4184 this._renderer.elem.appendChild(this._renderer.image);
4185 this._renderer.appended = true;
4186 }
4187
4188 return this.flagReset();
4189
4190 }
4191
4192 }
4193
4194 };
4195
4196 /**
4197 * @class
4198 */
4199 var Renderer = Two[Two.Types.svg] = function (params) {
4200
4201 this.domElement = params.domElement || svg.createElement('svg');
4202
4203 this.scene = new Two.Group();
4204 this.scene.parent = this;
4205
4206 this.defs = svg.createElement('defs');
4207 this.domElement.appendChild(this.defs);
4208 this.domElement.defs = this.defs;
4209 this.domElement.style.overflow = 'hidden';
4210
4211 };
4212
4213 _.extend(Renderer, {
4214
4215 Utils: svg
4216
4217 });
4218
4219 _.extend(Renderer.prototype, Two.Utils.Events, {
4220
4221 setSize: function (width, height) {
4222
4223 this.width = width;
4224 this.height = height;
4225
4226 svg.setAttributes(this.domElement, {
4227 width: width,
4228 height: height
4229 });
4230
4231 return this;
4232
4233 },
4234
4235 render: function () {
4236
4237 svg.group.render.call(this.scene, this.domElement);
4238
4239 return this;
4240
4241 }
4242
4243 });
4244
4245})((typeof global !== 'undefined' ? global : this).Two);
4246
4247(function (Two) {
4248
4249 /**
4250 * Constants
4251 */
4252 var mod = Two.Utils.mod, toFixed = Two.Utils.toFixed;
4253 var getRatio = Two.Utils.getRatio;
4254 var _ = Two.Utils;
4255
4256 // Returns true if this is a non-transforming matrix
4257 var isDefaultMatrix = function (m) {
4258 return (m[0] == 1 && m[3] == 0 && m[1] == 0 && m[4] == 1 && m[2] == 0 && m[5] == 0);
4259 };
4260
4261 var canvas = {
4262
4263 isHidden: /(none|transparent)/i,
4264
4265 alignments: {
4266 left: 'start',
4267 middle: 'center',
4268 right: 'end'
4269 },
4270
4271 shim: function (elem) {
4272 elem.tagName = 'canvas';
4273 elem.nodeType = 1;
4274 return elem;
4275 },
4276
4277 group: {
4278
4279 renderChild: function (child) {
4280 canvas[child._renderer.type].render.call(child, this.ctx, true, this.clip);
4281 },
4282
4283 render: function (ctx) {
4284
4285 // TODO: Add a check here to only invoke _update if need be.
4286 this._update();
4287
4288 var matrix = this._matrix.elements;
4289 var parent = this.parent;
4290 this._renderer.opacity = this._opacity * (parent && parent._renderer ? parent._renderer.opacity : 1);
4291
4292 var defaultMatrix = isDefaultMatrix(matrix);
4293
4294 var mask = this._mask;
4295 // var clip = this._clip;
4296
4297 if (!this._renderer.context) {
4298 this._renderer.context = {};
4299 }
4300
4301 this._renderer.context.ctx = ctx;
4302 // this._renderer.context.clip = clip;
4303
4304 if (!defaultMatrix) {
4305 ctx.save();
4306 ctx.transform(matrix[0], matrix[3], matrix[1], matrix[4], matrix[2], matrix[5]);
4307 }
4308
4309 if (mask) {
4310 canvas[mask._renderer.type].render.call(mask, ctx, true);
4311 }
4312
4313 if (this.opacity > 0 && this.scale !== 0) {
4314 for (var i = 0; i < this.children.length; i++) {
4315 var child = this.children[i];
4316 canvas[child._renderer.type].render.call(child, ctx);
4317 }
4318 }
4319
4320 if (!defaultMatrix) {
4321 ctx.restore();
4322 }
4323
4324 /**
4325 * Commented two-way functionality of clips / masks with groups and
4326 * polygons. Uncomment when this bug is fixed:
4327 * https://code.google.com/p/chromium/issues/detail?id=370951
4328 */
4329
4330 // if (clip) {
4331 // ctx.clip();
4332 // }
4333
4334 return this.flagReset();
4335
4336 }
4337
4338 },
4339
4340 path: {
4341
4342 render: function (ctx, forced, parentClipped) {
4343
4344 var matrix, stroke, linewidth, fill, opacity, visible, cap, join, miter,
4345 closed, commands, length, last, next, prev, a, b, c, d, ux, uy, vx, vy,
4346 ar, bl, br, cl, x, y, mask, clip, defaultMatrix, isOffset;
4347
4348 // TODO: Add a check here to only invoke _update if need be.
4349 this._update();
4350
4351 matrix = this._matrix.elements;
4352 stroke = this._stroke;
4353 linewidth = this._linewidth;
4354 fill = this._fill;
4355 opacity = this._opacity * this.parent._renderer.opacity;
4356 visible = this._visible;
4357 cap = this._cap;
4358 join = this._join;
4359 miter = this._miter;
4360 closed = this._closed;
4361 commands = this._vertices; // Commands
4362 length = commands.length;
4363 last = length - 1;
4364 defaultMatrix = isDefaultMatrix(matrix);
4365
4366 // mask = this._mask;
4367 clip = this._clip;
4368
4369 if (!forced && (!visible || clip)) {
4370 return this;
4371 }
4372
4373 // Transform
4374 if (!defaultMatrix) {
4375 ctx.save();
4376 ctx.transform(matrix[0], matrix[3], matrix[1], matrix[4], matrix[2], matrix[5]);
4377 }
4378
4379 /**
4380 * Commented two-way functionality of clips / masks with groups and
4381 * polygons. Uncomment when this bug is fixed:
4382 * https://code.google.com/p/chromium/issues/detail?id=370951
4383 */
4384
4385 // if (mask) {
4386 // canvas[mask._renderer.type].render.call(mask, ctx, true);
4387 // }
4388
4389 // Styles
4390 if (fill) {
4391 if (_.isString(fill)) {
4392 ctx.fillStyle = fill;
4393 } else {
4394 canvas[fill._renderer.type].render.call(fill, ctx);
4395 ctx.fillStyle = fill._renderer.effect;
4396 }
4397 }
4398 if (stroke) {
4399 if (_.isString(stroke)) {
4400 ctx.strokeStyle = stroke;
4401 } else {
4402 canvas[stroke._renderer.type].render.call(stroke, ctx);
4403 ctx.strokeStyle = stroke._renderer.effect;
4404 }
4405 }
4406 if (linewidth) {
4407 ctx.lineWidth = linewidth;
4408 }
4409 if (miter) {
4410 ctx.miterLimit = miter;
4411 }
4412 if (join) {
4413 ctx.lineJoin = join;
4414 }
4415 if (cap) {
4416 ctx.lineCap = cap;
4417 }
4418 if (_.isNumber(opacity)) {
4419 ctx.globalAlpha = opacity;
4420 }
4421
4422 ctx.beginPath();
4423
4424 for (var i = 0; i < commands.length; i++) {
4425
4426 b = commands[i];
4427
4428 x = toFixed(b._x);
4429 y = toFixed(b._y);
4430
4431 switch (b._command) {
4432
4433 case Two.Commands.close:
4434 ctx.closePath();
4435 break;
4436
4437 case Two.Commands.curve:
4438
4439 prev = closed ? mod(i - 1, length) : Math.max(i - 1, 0);
4440 next = closed ? mod(i + 1, length) : Math.min(i + 1, last);
4441
4442 a = commands[prev];
4443 c = commands[next];
4444 ar = (a.controls && a.controls.right) || Two.Vector.zero;
4445 bl = (b.controls && b.controls.left) || Two.Vector.zero;
4446
4447 if (a._relative) {
4448 vx = (ar.x + toFixed(a._x));
4449 vy = (ar.y + toFixed(a._y));
4450 } else {
4451 vx = toFixed(ar.x);
4452 vy = toFixed(ar.y);
4453 }
4454
4455 if (b._relative) {
4456 ux = (bl.x + toFixed(b._x));
4457 uy = (bl.y + toFixed(b._y));
4458 } else {
4459 ux = toFixed(bl.x);
4460 uy = toFixed(bl.y);
4461 }
4462
4463 ctx.bezierCurveTo(vx, vy, ux, uy, x, y);
4464
4465 if (i >= last && closed) {
4466
4467 c = d;
4468
4469 br = (b.controls && b.controls.right) || Two.Vector.zero;
4470 cl = (c.controls && c.controls.left) || Two.Vector.zero;
4471
4472 if (b._relative) {
4473 vx = (br.x + toFixed(b._x));
4474 vy = (br.y + toFixed(b._y));
4475 } else {
4476 vx = toFixed(br.x);
4477 vy = toFixed(br.y);
4478 }
4479
4480 if (c._relative) {
4481 ux = (cl.x + toFixed(c._x));
4482 uy = (cl.y + toFixed(c._y));
4483 } else {
4484 ux = toFixed(cl.x);
4485 uy = toFixed(cl.y);
4486 }
4487
4488 x = toFixed(c._x);
4489 y = toFixed(c._y);
4490
4491 ctx.bezierCurveTo(vx, vy, ux, uy, x, y);
4492
4493 }
4494
4495 break;
4496
4497 case Two.Commands.line:
4498 ctx.lineTo(x, y);
4499 break;
4500
4501 case Two.Commands.move:
4502 d = b;
4503 ctx.moveTo(x, y);
4504 break;
4505
4506 }
4507 }
4508
4509 // Loose ends
4510
4511 if (closed) {
4512 ctx.closePath();
4513 }
4514
4515 if (!clip && !parentClipped) {
4516 if (!canvas.isHidden.test(fill)) {
4517 isOffset = fill._renderer && fill._renderer.offset
4518 if (isOffset) {
4519 ctx.save();
4520 ctx.translate(
4521 - fill._renderer.offset.x, - fill._renderer.offset.y);
4522 ctx.scale(fill._renderer.scale.x, fill._renderer.scale.y);
4523 }
4524 ctx.fill();
4525 if (isOffset) {
4526 ctx.restore();
4527 }
4528 }
4529 if (!canvas.isHidden.test(stroke)) {
4530 isOffset = stroke._renderer && stroke._renderer.offset;
4531 if (isOffset) {
4532 ctx.save();
4533 ctx.translate(
4534 - stroke._renderer.offset.x, - stroke._renderer.offset.y);
4535 ctx.scale(stroke._renderer.scale.x, stroke._renderer.scale.y);
4536 ctx.lineWidth = linewidth / stroke._renderer.scale.x;
4537 }
4538 ctx.stroke();
4539 if (isOffset) {
4540 ctx.restore();
4541 }
4542 }
4543 }
4544
4545 if (!defaultMatrix) {
4546 ctx.restore();
4547 }
4548
4549 if (clip && !parentClipped) {
4550 ctx.clip();
4551 }
4552
4553 return this.flagReset();
4554
4555 }
4556
4557 },
4558
4559 text: {
4560
4561 render: function (ctx, forced, parentClipped) {
4562
4563 // TODO: Add a check here to only invoke _update if need be.
4564 this._update();
4565
4566 var matrix = this._matrix.elements;
4567 var stroke = this._stroke;
4568 var linewidth = this._linewidth;
4569 var fill = this._fill;
4570 var opacity = this._opacity * this.parent._renderer.opacity;
4571 var visible = this._visible;
4572 var defaultMatrix = isDefaultMatrix(matrix);
4573 var isOffset = fill._renderer && fill._renderer.offset
4574 && stroke._renderer && stroke._renderer.offset;
4575
4576 var a, b, c, d, e, sx, sy;
4577
4578 // mask = this._mask;
4579 var clip = this._clip;
4580
4581 if (!forced && (!visible || clip)) {
4582 return this;
4583 }
4584
4585 // Transform
4586 if (!defaultMatrix) {
4587 ctx.save();
4588 ctx.transform(matrix[0], matrix[3], matrix[1], matrix[4], matrix[2], matrix[5]);
4589 }
4590
4591 /**
4592 * Commented two-way functionality of clips / masks with groups and
4593 * polygons. Uncomment when this bug is fixed:
4594 * https://code.google.com/p/chromium/issues/detail?id=370951
4595 */
4596
4597 // if (mask) {
4598 // canvas[mask._renderer.type].render.call(mask, ctx, true);
4599 // }
4600
4601 if (!isOffset) {
4602 ctx.font = [this._style, this._weight, this._size + 'px/' +
4603 this._leading + 'px', this._family].join(' ');
4604 }
4605
4606 ctx.textAlign = canvas.alignments[this._alignment] || this._alignment;
4607 ctx.textBaseline = this._baseline;
4608
4609 // Styles
4610 if (fill) {
4611 if (_.isString(fill)) {
4612 ctx.fillStyle = fill;
4613 } else {
4614 canvas[fill._renderer.type].render.call(fill, ctx);
4615 ctx.fillStyle = fill._renderer.effect;
4616 }
4617 }
4618 if (stroke) {
4619 if (_.isString(stroke)) {
4620 ctx.strokeStyle = stroke;
4621 } else {
4622 canvas[stroke._renderer.type].render.call(stroke, ctx);
4623 ctx.strokeStyle = stroke._renderer.effect;
4624 }
4625 }
4626 if (linewidth) {
4627 ctx.lineWidth = linewidth;
4628 }
4629 if (_.isNumber(opacity)) {
4630 ctx.globalAlpha = opacity;
4631 }
4632
4633 if (!clip && !parentClipped) {
4634
4635 if (!canvas.isHidden.test(fill)) {
4636
4637 if (fill._renderer && fill._renderer.offset) {
4638
4639 sx = toFixed(fill._renderer.scale.x);
4640 sy = toFixed(fill._renderer.scale.y);
4641
4642 ctx.save();
4643 ctx.translate(- toFixed(fill._renderer.offset.x),
4644 - toFixed(fill._renderer.offset.y));
4645 ctx.scale(sx, sy);
4646
4647 a = this._size / fill._renderer.scale.y;
4648 b = this._leading / fill._renderer.scale.y;
4649 ctx.font = [this._style, this._weight, toFixed(a) + 'px/',
4650 toFixed(b) + 'px', this._family].join(' ');
4651
4652 c = fill._renderer.offset.x / fill._renderer.scale.x;
4653 d = fill._renderer.offset.y / fill._renderer.scale.y;
4654
4655 ctx.fillText(this.value, toFixed(c), toFixed(d));
4656 ctx.restore();
4657
4658 } else {
4659 ctx.fillText(this.value, 0, 0);
4660 }
4661
4662 }
4663
4664 if (!canvas.isHidden.test(stroke)) {
4665
4666 if (stroke._renderer && stroke._renderer.offset) {
4667
4668 sx = toFixed(stroke._renderer.scale.x);
4669 sy = toFixed(stroke._renderer.scale.y);
4670
4671 ctx.save();
4672 ctx.translate(- toFixed(stroke._renderer.offset.x),
4673 - toFixed(stroke._renderer.offset.y));
4674 ctx.scale(sx, sy);
4675
4676 a = this._size / stroke._renderer.scale.y;
4677 b = this._leading / stroke._renderer.scale.y;
4678 ctx.font = [this._style, this._weight, toFixed(a) + 'px/',
4679 toFixed(b) + 'px', this._family].join(' ');
4680
4681 c = stroke._renderer.offset.x / stroke._renderer.scale.x;
4682 d = stroke._renderer.offset.y / stroke._renderer.scale.y;
4683 e = linewidth / stroke._renderer.scale.x;
4684
4685 ctx.lineWidth = toFixed(e);
4686 ctx.strokeText(this.value, toFixed(c), toFixed(d));
4687 ctx.restore();
4688
4689 } else {
4690 ctx.strokeText(this.value, 0, 0);
4691 }
4692 }
4693 }
4694
4695 if (!defaultMatrix) {
4696 ctx.restore();
4697 }
4698
4699 // TODO: Test for text
4700 if (clip && !parentClipped) {
4701 ctx.clip();
4702 }
4703
4704 return this.flagReset();
4705
4706 }
4707
4708 },
4709
4710 'linear-gradient': {
4711
4712 render: function (ctx) {
4713
4714 this._update();
4715
4716 if (!this._renderer.effect || this._flagEndPoints || this._flagStops) {
4717
4718 this._renderer.effect = ctx.createLinearGradient(
4719 this.left._x, this.left._y,
4720 this.right._x, this.right._y
4721 );
4722
4723 for (var i = 0; i < this.stops.length; i++) {
4724 var stop = this.stops[i];
4725 this._renderer.effect.addColorStop(stop._offset, stop._color);
4726 }
4727
4728 }
4729
4730 return this.flagReset();
4731
4732 }
4733
4734 },
4735
4736 'radial-gradient': {
4737
4738 render: function (ctx) {
4739
4740 this._update();
4741
4742 if (!this._renderer.effect || this._flagCenter || this._flagFocal
4743 || this._flagRadius || this._flagStops) {
4744
4745 this._renderer.effect = ctx.createRadialGradient(
4746 this.center._x, this.center._y, 0,
4747 this.focal._x, this.focal._y, this._radius
4748 );
4749
4750 for (var i = 0; i < this.stops.length; i++) {
4751 var stop = this.stops[i];
4752 this._renderer.effect.addColorStop(stop._offset, stop._color);
4753 }
4754
4755 }
4756
4757 return this.flagReset();
4758
4759 }
4760
4761 },
4762
4763 texture: {
4764
4765 render: function (ctx) {
4766
4767 this._update();
4768
4769 var image = this.image;
4770 var repeat;
4771
4772 if (!this._renderer.effect || ((this._flagLoaded || this._flagImage || this._flagVideo || this._flagRepeat) && this.loaded)) {
4773 this._renderer.effect = ctx.createPattern(this.image, this._repeat);
4774 }
4775
4776 if (this._flagOffset || this._flagLoaded || this._flagScale) {
4777
4778 if (!(this._renderer.offset instanceof Two.Vector)) {
4779 this._renderer.offset = new Two.Vector();
4780 }
4781
4782 this._renderer.offset.x = - this._offset.x;
4783 this._renderer.offset.y = - this._offset.y;
4784
4785 if (image) {
4786
4787 this._renderer.offset.x += image.width / 2;
4788 this._renderer.offset.y += image.height / 2;
4789
4790 if (this._scale instanceof Two.Vector) {
4791 this._renderer.offset.x *= this._scale.x;
4792 this._renderer.offset.y *= this._scale.y;
4793 } else {
4794 this._renderer.offset.x *= this._scale;
4795 this._renderer.offset.y *= this._scale;
4796 }
4797 }
4798
4799 }
4800
4801 if (this._flagScale || this._flagLoaded) {
4802
4803 if (!(this._renderer.scale instanceof Two.Vector)) {
4804 this._renderer.scale = new Two.Vector();
4805 }
4806
4807 if (this._scale instanceof Two.Vector) {
4808 this._renderer.scale.copy(this._scale);
4809 } else {
4810 this._renderer.scale.set(this._scale, this._scale);
4811 }
4812
4813 }
4814
4815 return this.flagReset();
4816
4817 }
4818
4819 }
4820
4821 };
4822
4823 var Renderer = Two[Two.Types.canvas] = function (params) {
4824 // Smoothing property. Defaults to true
4825 // Set it to false when working with pixel art.
4826 // false can lead to better performance, since it would use a cheaper interpolation algorithm.
4827 // It might not make a big difference on GPU backed canvases.
4828 var smoothing = (params.smoothing !== false);
4829 this.domElement = params.domElement || document.createElement('canvas');
4830 this.ctx = this.domElement.getContext('2d');
4831 this.overdraw = params.overdraw || false;
4832
4833 if (!_.isUndefined(this.ctx.imageSmoothingEnabled)) {
4834 this.ctx.imageSmoothingEnabled = smoothing;
4835 }
4836
4837 // Everything drawn on the canvas needs to be added to the scene.
4838 this.scene = new Two.Group();
4839 this.scene.parent = this;
4840 };
4841
4842
4843 _.extend(Renderer, {
4844
4845 Utils: canvas
4846
4847 });
4848
4849 _.extend(Renderer.prototype, Two.Utils.Events, {
4850
4851 setSize: function (width, height, ratio) {
4852
4853 this.width = width;
4854 this.height = height;
4855
4856 this.ratio = _.isUndefined(ratio) ? getRatio(this.ctx) : ratio;
4857
4858 this.domElement.width = width * this.ratio;
4859 this.domElement.height = height * this.ratio;
4860
4861 if (this.domElement.style) {
4862 _.extend(this.domElement.style, {
4863 width: width + 'px',
4864 height: height + 'px'
4865 });
4866 }
4867
4868 return this;
4869
4870 },
4871
4872 render: function () {
4873
4874 var isOne = this.ratio === 1;
4875
4876 if (!isOne) {
4877 this.ctx.save();
4878 this.ctx.scale(this.ratio, this.ratio);
4879 }
4880
4881 if (!this.overdraw) {
4882 this.ctx.clearRect(0, 0, this.width, this.height);
4883 }
4884
4885 canvas.group.render.call(this.scene, this.ctx);
4886
4887 if (!isOne) {
4888 this.ctx.restore();
4889 }
4890
4891 return this;
4892
4893 }
4894
4895 });
4896
4897 function resetTransform(ctx) {
4898 ctx.setTransform(1, 0, 0, 1, 0, 0);
4899 }
4900
4901})((typeof global !== 'undefined' ? global : this).Two);
4902
4903(function (Two) {
4904
4905 /**
4906 * Constants
4907 */
4908
4909 var root = Two.root,
4910 multiplyMatrix = Two.Matrix.Multiply,
4911 mod = Two.Utils.mod,
4912 identity = [1, 0, 0, 0, 1, 0, 0, 0, 1],
4913 transformation = new Two.Array(9),
4914 getRatio = Two.Utils.getRatio,
4915 getComputedMatrix = Two.Utils.getComputedMatrix,
4916 toFixed = Two.Utils.toFixed,
4917 _ = Two.Utils;
4918
4919 var webgl = {
4920
4921 isHidden: /(none|transparent)/i,
4922
4923 canvas: (root.document ? root.document.createElement('canvas') : { getContext: _.identity }),
4924
4925 alignments: {
4926 left: 'start',
4927 middle: 'center',
4928 right: 'end'
4929 },
4930
4931 matrix: new Two.Matrix(),
4932
4933 uv: new Two.Array([
4934 0, 0,
4935 1, 0,
4936 0, 1,
4937 0, 1,
4938 1, 0,
4939 1, 1
4940 ]),
4941
4942 group: {
4943
4944 removeChild: function (child, gl) {
4945 if (child.children) {
4946 for (var i = 0; i < child.children.length; i++) {
4947 webgl.group.removeChild(child.children[i], gl);
4948 }
4949 return;
4950 }
4951 // Deallocate texture to free up gl memory.
4952 gl.deleteTexture(child._renderer.texture);
4953 delete child._renderer.texture;
4954 },
4955
4956 renderChild: function (child) {
4957 webgl[child._renderer.type].render.call(child, this.gl, this.program);
4958 },
4959
4960 render: function (gl, program) {
4961
4962 this._update();
4963
4964 var parent = this.parent;
4965 var flagParentMatrix = (parent._matrix && parent._matrix.manual) || parent._flagMatrix;
4966 var flagMatrix = this._matrix.manual || this._flagMatrix;
4967
4968 if (flagParentMatrix || flagMatrix) {
4969
4970 if (!this._renderer.matrix) {
4971 this._renderer.matrix = new Two.Array(9);
4972 }
4973
4974 // Reduce amount of object / array creation / deletion
4975 this._matrix.toArray(true, transformation);
4976
4977 multiplyMatrix(transformation, parent._renderer.matrix, this._renderer.matrix);
4978 this._renderer.scale = this._scale * parent._renderer.scale;
4979
4980 if (flagParentMatrix) {
4981 this._flagMatrix = true;
4982 }
4983
4984 }
4985
4986 if (this._mask) {
4987
4988 gl.enable(gl.STENCIL_TEST);
4989 gl.stencilFunc(gl.ALWAYS, 1, 1);
4990
4991 gl.colorMask(false, false, false, true);
4992 gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR);
4993
4994 webgl[this._mask._renderer.type].render.call(this._mask, gl, program, this);
4995
4996 gl.colorMask(true, true, true, true);
4997 gl.stencilFunc(gl.NOTEQUAL, 0, 1);
4998 gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP);
4999
5000 }
5001
5002 this._flagOpacity = parent._flagOpacity || this._flagOpacity;
5003
5004 this._renderer.opacity = this._opacity
5005 * (parent && parent._renderer ? parent._renderer.opacity : 1);
5006
5007 if (this._flagSubtractions) {
5008 for (var i = 0; i < this.subtractions.length; i++) {
5009 webgl.group.removeChild(this.subtractions[i], gl);
5010 }
5011 }
5012
5013 this.children.forEach(webgl.group.renderChild, {
5014 gl: gl,
5015 program: program
5016 });
5017
5018 if (this._mask) {
5019
5020 gl.colorMask(false, false, false, false);
5021 gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR);
5022
5023 webgl[this._mask._renderer.type].render.call(this._mask, gl, program, this);
5024
5025 gl.colorMask(true, true, true, true);
5026 gl.stencilFunc(gl.NOTEQUAL, 0, 1);
5027 gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP);
5028
5029 gl.disable(gl.STENCIL_TEST);
5030
5031 }
5032
5033 return this.flagReset();
5034
5035 }
5036
5037 },
5038
5039 path: {
5040
5041 updateCanvas: function (elem) {
5042
5043 var next, prev, a, c, ux, uy, vx, vy, ar, bl, br, cl, x, y;
5044 var isOffset;
5045
5046 var commands = elem._vertices;
5047 var canvas = this.canvas;
5048 var ctx = this.ctx;
5049
5050 // Styles
5051 var scale = elem._renderer.scale;
5052 var stroke = elem._stroke;
5053 var linewidth = elem._linewidth;
5054 var fill = elem._fill;
5055 var opacity = elem._renderer.opacity || elem._opacity;
5056 var cap = elem._cap;
5057 var join = elem._join;
5058 var miter = elem._miter;
5059 var closed = elem._closed;
5060 var length = commands.length;
5061 var last = length - 1;
5062
5063 canvas.width = Math.max(Math.ceil(elem._renderer.rect.width * scale), 1);
5064 canvas.height = Math.max(Math.ceil(elem._renderer.rect.height * scale), 1);
5065
5066 var centroid = elem._renderer.rect.centroid;
5067 var cx = centroid.x;
5068 var cy = centroid.y;
5069
5070 ctx.clearRect(0, 0, canvas.width, canvas.height);
5071
5072 if (fill) {
5073 if (_.isString(fill)) {
5074 ctx.fillStyle = fill;
5075 } else {
5076 webgl[fill._renderer.type].render.call(fill, ctx, elem);
5077 ctx.fillStyle = fill._renderer.effect;
5078 }
5079 }
5080 if (stroke) {
5081 if (_.isString(stroke)) {
5082 ctx.strokeStyle = stroke;
5083 } else {
5084 webgl[stroke._renderer.type].render.call(stroke, ctx, elem);
5085 ctx.strokeStyle = stroke._renderer.effect;
5086 }
5087 }
5088 if (linewidth) {
5089 ctx.lineWidth = linewidth;
5090 }
5091 if (miter) {
5092 ctx.miterLimit = miter;
5093 }
5094 if (join) {
5095 ctx.lineJoin = join;
5096 }
5097 if (cap) {
5098 ctx.lineCap = cap;
5099 }
5100 if (_.isNumber(opacity)) {
5101 ctx.globalAlpha = opacity;
5102 }
5103
5104 var d;
5105 ctx.save();
5106 ctx.scale(scale, scale);
5107 ctx.translate(cx, cy);
5108
5109 ctx.beginPath();
5110 for (var i = 0; i < commands.length; i++) {
5111
5112 b = commands[i];
5113
5114 x = toFixed(b._x);
5115 y = toFixed(b._y);
5116
5117 switch (b._command) {
5118
5119 case Two.Commands.close:
5120 ctx.closePath();
5121 break;
5122
5123 case Two.Commands.curve:
5124
5125 prev = closed ? mod(i - 1, length) : Math.max(i - 1, 0);
5126 next = closed ? mod(i + 1, length) : Math.min(i + 1, last);
5127
5128 a = commands[prev];
5129 c = commands[next];
5130 ar = (a.controls && a.controls.right) || Two.Vector.zero;
5131 bl = (b.controls && b.controls.left) || Two.Vector.zero;
5132
5133 if (a._relative) {
5134 vx = toFixed((ar.x + a._x));
5135 vy = toFixed((ar.y + a._y));
5136 } else {
5137 vx = toFixed(ar.x);
5138 vy = toFixed(ar.y);
5139 }
5140
5141 if (b._relative) {
5142 ux = toFixed((bl.x + b._x));
5143 uy = toFixed((bl.y + b._y));
5144 } else {
5145 ux = toFixed(bl.x);
5146 uy = toFixed(bl.y);
5147 }
5148
5149 ctx.bezierCurveTo(vx, vy, ux, uy, x, y);
5150
5151 if (i >= last && closed) {
5152
5153 c = d;
5154
5155 br = (b.controls && b.controls.right) || Two.Vector.zero;
5156 cl = (c.controls && c.controls.left) || Two.Vector.zero;
5157
5158 if (b._relative) {
5159 vx = toFixed((br.x + b._x));
5160 vy = toFixed((br.y + b._y));
5161 } else {
5162 vx = toFixed(br.x);
5163 vy = toFixed(br.y);
5164 }
5165
5166 if (c._relative) {
5167 ux = toFixed((cl.x + c._x));
5168 uy = toFixed((cl.y + c._y));
5169 } else {
5170 ux = toFixed(cl.x);
5171 uy = toFixed(cl.y);
5172 }
5173
5174 x = toFixed(c._x);
5175 y = toFixed(c._y);
5176
5177 ctx.bezierCurveTo(vx, vy, ux, uy, x, y);
5178
5179 }
5180
5181 break;
5182
5183 case Two.Commands.line:
5184 ctx.lineTo(x, y);
5185 break;
5186
5187 case Two.Commands.move:
5188 d = b;
5189 ctx.moveTo(x, y);
5190 break;
5191
5192 }
5193
5194 }
5195
5196 // Loose ends
5197
5198 if (closed) {
5199 ctx.closePath();
5200 }
5201
5202 if (!webgl.isHidden.test(fill)) {
5203 isOffset = fill._renderer && fill._renderer.offset
5204 if (isOffset) {
5205 ctx.save();
5206 ctx.translate(
5207 - fill._renderer.offset.x, - fill._renderer.offset.y);
5208 ctx.scale(fill._renderer.scale.x, fill._renderer.scale.y);
5209 }
5210 ctx.fill();
5211 if (isOffset) {
5212 ctx.restore();
5213 }
5214 }
5215
5216 if (!webgl.isHidden.test(stroke)) {
5217 isOffset = stroke._renderer && stroke._renderer.offset;
5218 if (isOffset) {
5219 ctx.save();
5220 ctx.translate(
5221 - stroke._renderer.offset.x, - stroke._renderer.offset.y);
5222 ctx.scale(stroke._renderer.scale.x, stroke._renderer.scale.y);
5223 ctx.lineWidth = linewidth / stroke._renderer.scale.x;
5224 }
5225 ctx.stroke();
5226 if (isOffset) {
5227 ctx.restore();
5228 }
5229 }
5230
5231 ctx.restore();
5232
5233 },
5234
5235 /**
5236 * Returns the rect of a set of verts. Typically takes vertices that are
5237 * "centered" around 0 and returns them to be anchored upper-left.
5238 */
5239 getBoundingClientRect: function (vertices, border, rect) {
5240
5241 var left = Infinity, right = -Infinity,
5242 top = Infinity, bottom = -Infinity,
5243 width, height;
5244
5245 vertices.forEach(function (v) {
5246
5247 var x = v.x, y = v.y, controls = v.controls;
5248 var a, b, c, d, cl, cr;
5249
5250 top = Math.min(y, top);
5251 left = Math.min(x, left);
5252 right = Math.max(x, right);
5253 bottom = Math.max(y, bottom);
5254
5255 if (!v.controls) {
5256 return;
5257 }
5258
5259 cl = controls.left;
5260 cr = controls.right;
5261
5262 if (!cl || !cr) {
5263 return;
5264 }
5265
5266 a = v._relative ? cl.x + x : cl.x;
5267 b = v._relative ? cl.y + y : cl.y;
5268 c = v._relative ? cr.x + x : cr.x;
5269 d = v._relative ? cr.y + y : cr.y;
5270
5271 if (!a || !b || !c || !d) {
5272 return;
5273 }
5274
5275 top = Math.min(b, d, top);
5276 left = Math.min(a, c, left);
5277 right = Math.max(a, c, right);
5278 bottom = Math.max(b, d, bottom);
5279
5280 });
5281
5282 // Expand borders
5283
5284 if (_.isNumber(border)) {
5285 top -= border;
5286 left -= border;
5287 right += border;
5288 bottom += border;
5289 }
5290
5291 width = right - left;
5292 height = bottom - top;
5293
5294 rect.top = top;
5295 rect.left = left;
5296 rect.right = right;
5297 rect.bottom = bottom;
5298 rect.width = width;
5299 rect.height = height;
5300
5301 if (!rect.centroid) {
5302 rect.centroid = {};
5303 }
5304
5305 rect.centroid.x = - left;
5306 rect.centroid.y = - top;
5307
5308 },
5309
5310 render: function (gl, program, forcedParent) {
5311
5312 if (!this._visible || !this._opacity) {
5313 return this;
5314 }
5315
5316 this._update();
5317
5318 // Calculate what changed
5319
5320 var parent = this.parent;
5321 var flagParentMatrix = parent._matrix.manual || parent._flagMatrix;
5322 var flagMatrix = this._matrix.manual || this._flagMatrix;
5323 var flagTexture = this._flagVertices || this._flagFill
5324 || (this._fill instanceof Two.LinearGradient && (this._fill._flagSpread || this._fill._flagStops || this._fill._flagEndPoints))
5325 || (this._fill instanceof Two.RadialGradient && (this._fill._flagSpread || this._fill._flagStops || this._fill._flagRadius || this._fill._flagCenter || this._fill._flagFocal))
5326 || (this._fill instanceof Two.Texture && (this._fill._flagLoaded && this._fill.loaded || this._fill._flagOffset || this._fill._flagScale))
5327 || (this._stroke instanceof Two.LinearGradient && (this._stroke._flagSpread || this._stroke._flagStops || this._stroke._flagEndPoints))
5328 || (this._stroke instanceof Two.RadialGradient && (this._stroke._flagSpread || this._stroke._flagStops || this._stroke._flagRadius || this._stroke._flagCenter || this._stroke._flagFocal))
5329 || (this._stroke instanceof Two.Texture && (this._stroke._flagLoaded && this._stroke.loaded || this._stroke._flagOffset || this._fill._flagScale))
5330 || this._flagStroke || this._flagLinewidth || this._flagOpacity
5331 || parent._flagOpacity || this._flagVisible || this._flagCap
5332 || this._flagJoin || this._flagMiter || this._flagScale
5333 || !this._renderer.texture;
5334
5335 if (flagParentMatrix || flagMatrix) {
5336
5337 if (!this._renderer.matrix) {
5338 this._renderer.matrix = new Two.Array(9);
5339 }
5340
5341 // Reduce amount of object / array creation / deletion
5342
5343 this._matrix.toArray(true, transformation);
5344
5345 multiplyMatrix(transformation, parent._renderer.matrix, this._renderer.matrix);
5346 this._renderer.scale = this._scale * parent._renderer.scale;
5347
5348 }
5349
5350 if (flagTexture) {
5351
5352 if (!this._renderer.rect) {
5353 this._renderer.rect = {};
5354 }
5355
5356 if (!this._renderer.triangles) {
5357 this._renderer.triangles = new Two.Array(12);
5358 }
5359
5360 this._renderer.opacity = this._opacity * parent._renderer.opacity;
5361
5362 webgl.path.getBoundingClientRect(this._vertices, this._linewidth, this._renderer.rect);
5363 webgl.getTriangles(this._renderer.rect, this._renderer.triangles);
5364
5365 webgl.updateBuffer.call(webgl, gl, this, program);
5366 webgl.updateTexture.call(webgl, gl, this);
5367
5368 }
5369
5370 // if (this._mask) {
5371 // webgl[this._mask._renderer.type].render.call(mask, gl, program, this);
5372 // }
5373
5374 if (this._clip && !forcedParent) {
5375 return;
5376 }
5377
5378 // Draw Texture
5379
5380 gl.bindBuffer(gl.ARRAY_BUFFER, this._renderer.textureCoordsBuffer);
5381
5382 gl.vertexAttribPointer(program.textureCoords, 2, gl.FLOAT, false, 0, 0);
5383
5384 gl.bindTexture(gl.TEXTURE_2D, this._renderer.texture);
5385
5386
5387 // Draw Rect
5388
5389 gl.uniformMatrix3fv(program.matrix, false, this._renderer.matrix);
5390
5391 gl.bindBuffer(gl.ARRAY_BUFFER, this._renderer.buffer);
5392
5393 gl.vertexAttribPointer(program.position, 2, gl.FLOAT, false, 0, 0);
5394
5395 gl.drawArrays(gl.TRIANGLES, 0, 6);
5396
5397 return this.flagReset();
5398
5399 }
5400
5401 },
5402
5403 text: {
5404
5405 updateCanvas: function (elem) {
5406
5407 var canvas = this.canvas;
5408 var ctx = this.ctx;
5409
5410 // Styles
5411 var scale = elem._renderer.scale;
5412 var stroke = elem._stroke;
5413 var linewidth = elem._linewidth * scale;
5414 var fill = elem._fill;
5415 var opacity = elem._renderer.opacity || elem._opacity;
5416
5417 canvas.width = Math.max(Math.ceil(elem._renderer.rect.width * scale), 1);
5418 canvas.height = Math.max(Math.ceil(elem._renderer.rect.height * scale), 1);
5419
5420 var centroid = elem._renderer.rect.centroid;
5421 var cx = centroid.x;
5422 var cy = centroid.y;
5423
5424 var a, b, c, d, e, sx, sy;
5425 var isOffset = fill._renderer && fill._renderer.offset
5426 && stroke._renderer && stroke._renderer.offset;
5427
5428 ctx.clearRect(0, 0, canvas.width, canvas.height);
5429
5430 if (!isOffset) {
5431 ctx.font = [elem._style, elem._weight, elem._size + 'px/' +
5432 elem._leading + 'px', elem._family].join(' ');
5433 }
5434
5435 ctx.textAlign = 'center';
5436 ctx.textBaseline = 'middle';
5437
5438 // Styles
5439 if (fill) {
5440 if (_.isString(fill)) {
5441 ctx.fillStyle = fill;
5442 } else {
5443 webgl[fill._renderer.type].render.call(fill, ctx, elem);
5444 ctx.fillStyle = fill._renderer.effect;
5445 }
5446 }
5447 if (stroke) {
5448 if (_.isString(stroke)) {
5449 ctx.strokeStyle = stroke;
5450 } else {
5451 webgl[stroke._renderer.type].render.call(stroke, ctx, elem);
5452 ctx.strokeStyle = stroke._renderer.effect;
5453 }
5454 }
5455 if (linewidth) {
5456 ctx.lineWidth = linewidth;
5457 }
5458 if (_.isNumber(opacity)) {
5459 ctx.globalAlpha = opacity;
5460 }
5461
5462 ctx.save();
5463 ctx.scale(scale, scale);
5464 ctx.translate(cx, cy);
5465
5466 if (!webgl.isHidden.test(fill)) {
5467
5468 if (fill._renderer && fill._renderer.offset) {
5469
5470 sx = toFixed(fill._renderer.scale.x);
5471 sy = toFixed(fill._renderer.scale.y);
5472
5473 ctx.save();
5474 ctx.translate(- toFixed(fill._renderer.offset.x),
5475 - toFixed(fill._renderer.offset.y));
5476 ctx.scale(sx, sy);
5477
5478 a = elem._size / fill._renderer.scale.y;
5479 b = elem._leading / fill._renderer.scale.y;
5480 ctx.font = [elem._style, elem._weight, toFixed(a) + 'px/',
5481 toFixed(b) + 'px', elem._family].join(' ');
5482
5483 c = fill._renderer.offset.x / fill._renderer.scale.x;
5484 d = fill._renderer.offset.y / fill._renderer.scale.y;
5485
5486 ctx.fillText(elem.value, toFixed(c), toFixed(d));
5487 ctx.restore();
5488
5489 } else {
5490 ctx.fillText(elem.value, 0, 0);
5491 }
5492
5493 }
5494
5495 if (!webgl.isHidden.test(stroke)) {
5496
5497 if (stroke._renderer && stroke._renderer.offset) {
5498
5499 sx = toFixed(stroke._renderer.scale.x);
5500 sy = toFixed(stroke._renderer.scale.y);
5501
5502 ctx.save();
5503 ctx.translate(- toFixed(stroke._renderer.offset.x),
5504 - toFixed(stroke._renderer.offset.y));
5505 ctx.scale(sx, sy);
5506
5507 a = elem._size / stroke._renderer.scale.y;
5508 b = elem._leading / stroke._renderer.scale.y;
5509 ctx.font = [elem._style, elem._weight, toFixed(a) + 'px/',
5510 toFixed(b) + 'px', elem._family].join(' ');
5511
5512 c = stroke._renderer.offset.x / stroke._renderer.scale.x;
5513 d = stroke._renderer.offset.y / stroke._renderer.scale.y;
5514 e = linewidth / stroke._renderer.scale.x;
5515
5516 ctx.lineWidth = toFixed(e);
5517 ctx.strokeText(elem.value, toFixed(c), toFixed(d));
5518 ctx.restore();
5519
5520 } else {
5521 ctx.strokeText(elem.value, 0, 0);
5522 }
5523
5524 }
5525
5526 ctx.restore();
5527
5528 },
5529
5530 getBoundingClientRect: function (elem, rect) {
5531
5532 var ctx = webgl.ctx;
5533
5534 ctx.font = [elem._style, elem._weight, elem._size + 'px/' +
5535 elem._leading + 'px', elem._family].join(' ');
5536
5537 ctx.textAlign = 'center';
5538 ctx.textBaseline = elem._baseline;
5539
5540 // TODO: Estimate this better
5541 var width = ctx.measureText(elem._value).width;
5542 var height = Math.max(elem._size || elem._leading);
5543
5544 if (this._linewidth && !webgl.isHidden.test(this._stroke)) {
5545 // width += this._linewidth; // TODO: Not sure if the `measure` calcs this.
5546 height += this._linewidth;
5547 }
5548
5549 var w = width / 2;
5550 var h = height / 2;
5551
5552 switch (webgl.alignments[elem._alignment] || elem._alignment) {
5553
5554 case webgl.alignments.left:
5555 rect.left = 0;
5556 rect.right = width;
5557 break;
5558 case webgl.alignments.right:
5559 rect.left = - width;
5560 rect.right = 0;
5561 break;
5562 default:
5563 rect.left = - w;
5564 rect.right = w;
5565 }
5566
5567 // TODO: Gradients aren't inherited...
5568 switch (elem._baseline) {
5569 case 'bottom':
5570 rect.top = - height;
5571 rect.bottom = 0;
5572 break;
5573 case 'top':
5574 rect.top = 0;
5575 rect.bottom = height;
5576 break;
5577 default:
5578 rect.top = - h;
5579 rect.bottom = h;
5580 }
5581
5582 rect.width = width;
5583 rect.height = height;
5584
5585 if (!rect.centroid) {
5586 rect.centroid = {};
5587 }
5588
5589 // TODO:
5590 rect.centroid.x = w;
5591 rect.centroid.y = h;
5592
5593 },
5594
5595 render: function (gl, program, forcedParent) {
5596
5597 if (!this._visible || !this._opacity) {
5598 return this;
5599 }
5600
5601 this._update();
5602
5603 // Calculate what changed
5604
5605 var parent = this.parent;
5606 var flagParentMatrix = parent._matrix.manual || parent._flagMatrix;
5607 var flagMatrix = this._matrix.manual || this._flagMatrix;
5608 var flagTexture = this._flagVertices || this._flagFill
5609 || (this._fill instanceof Two.LinearGradient && (this._fill._flagSpread || this._fill._flagStops || this._fill._flagEndPoints))
5610 || (this._fill instanceof Two.RadialGradient && (this._fill._flagSpread || this._fill._flagStops || this._fill._flagRadius || this._fill._flagCenter || this._fill._flagFocal))
5611 || (this._fill instanceof Two.Texture && (this._fill._flagLoaded && this._fill.loaded))
5612 || (this._stroke instanceof Two.LinearGradient && (this._stroke._flagSpread || this._stroke._flagStops || this._stroke._flagEndPoints))
5613 || (this._stroke instanceof Two.RadialGradient && (this._stroke._flagSpread || this._stroke._flagStops || this._stroke._flagRadius || this._stroke._flagCenter || this._stroke._flagFocal))
5614 || (this._texture instanceof Two.Texture && (this._texture._flagLoaded && this._texture.loaded))
5615 || this._flagStroke || this._flagLinewidth || this._flagOpacity
5616 || parent._flagOpacity || this._flagVisible || this._flagScale
5617 || this._flagValue || this._flagFamily || this._flagSize
5618 || this._flagLeading || this._flagAlignment || this._flagBaseline
5619 || this._flagStyle || this._flagWeight || this._flagDecoration
5620 || !this._renderer.texture;
5621
5622 if (flagParentMatrix || flagMatrix) {
5623
5624 if (!this._renderer.matrix) {
5625 this._renderer.matrix = new Two.Array(9);
5626 }
5627
5628 // Reduce amount of object / array creation / deletion
5629
5630 this._matrix.toArray(true, transformation);
5631
5632 multiplyMatrix(transformation, parent._renderer.matrix, this._renderer.matrix);
5633 this._renderer.scale = this._scale * parent._renderer.scale;
5634
5635 }
5636
5637 if (flagTexture) {
5638
5639 if (!this._renderer.rect) {
5640 this._renderer.rect = {};
5641 }
5642
5643 if (!this._renderer.triangles) {
5644 this._renderer.triangles = new Two.Array(12);
5645 }
5646
5647 this._renderer.opacity = this._opacity * parent._renderer.opacity;
5648
5649 webgl.text.getBoundingClientRect(this, this._renderer.rect);
5650 webgl.getTriangles(this._renderer.rect, this._renderer.triangles);
5651
5652 webgl.updateBuffer.call(webgl, gl, this, program);
5653 webgl.updateTexture.call(webgl, gl, this);
5654
5655 }
5656
5657 // if (this._mask) {
5658 // webgl[this._mask._renderer.type].render.call(mask, gl, program, this);
5659 // }
5660
5661 if (this._clip && !forcedParent) {
5662 return;
5663 }
5664
5665 // Draw Texture
5666
5667 gl.bindBuffer(gl.ARRAY_BUFFER, this._renderer.textureCoordsBuffer);
5668
5669 gl.vertexAttribPointer(program.textureCoords, 2, gl.FLOAT, false, 0, 0);
5670
5671 gl.bindTexture(gl.TEXTURE_2D, this._renderer.texture);
5672
5673
5674 // Draw Rect
5675
5676 gl.uniformMatrix3fv(program.matrix, false, this._renderer.matrix);
5677
5678 gl.bindBuffer(gl.ARRAY_BUFFER, this._renderer.buffer);
5679
5680 gl.vertexAttribPointer(program.position, 2, gl.FLOAT, false, 0, 0);
5681
5682 gl.drawArrays(gl.TRIANGLES, 0, 6);
5683
5684 return this.flagReset();
5685
5686 }
5687
5688 },
5689
5690 'linear-gradient': {
5691
5692 render: function (ctx, elem) {
5693
5694 if (!ctx.canvas.getContext('2d')) {
5695 return;
5696 }
5697
5698 this._update();
5699
5700 if (!this._renderer.effect || this._flagEndPoints || this._flagStops) {
5701
5702 this._renderer.effect = ctx.createLinearGradient(
5703 this.left._x, this.left._y,
5704 this.right._x, this.right._y
5705 );
5706
5707 for (var i = 0; i < this.stops.length; i++) {
5708 var stop = this.stops[i];
5709 this._renderer.effect.addColorStop(stop._offset, stop._color);
5710 }
5711
5712 }
5713
5714 return this.flagReset();
5715
5716 }
5717
5718 },
5719
5720 'radial-gradient': {
5721
5722 render: function (ctx, elem) {
5723
5724 if (!ctx.canvas.getContext('2d')) {
5725 return;
5726 }
5727
5728 this._update();
5729
5730 if (!this._renderer.effect || this._flagCenter || this._flagFocal
5731 || this._flagRadius || this._flagStops) {
5732
5733 this._renderer.effect = ctx.createRadialGradient(
5734 this.center._x, this.center._y, 0,
5735 this.focal._x, this.focal._y, this._radius
5736 );
5737
5738 for (var i = 0; i < this.stops.length; i++) {
5739 var stop = this.stops[i];
5740 this._renderer.effect.addColorStop(stop._offset, stop._color);
5741 }
5742
5743 }
5744
5745 return this.flagReset();
5746
5747 }
5748
5749 },
5750
5751 texture: {
5752
5753 render: function (ctx, elem) {
5754
5755 if (!ctx.canvas.getContext('2d')) {
5756 return;
5757 }
5758
5759 this._update();
5760
5761 var image = this.image;
5762 var repeat;
5763
5764 if (!this._renderer.effect || ((this._flagLoaded || this._flagRepeat) && this.loaded)) {
5765 this._renderer.effect = ctx.createPattern(image, this._repeat);
5766 }
5767
5768 if (this._flagOffset || this._flagLoaded || this._flagScale) {
5769
5770 if (!(this._renderer.offset instanceof Two.Vector)) {
5771 this._renderer.offset = new Two.Vector();
5772 }
5773
5774 this._renderer.offset.x = this._offset.x;
5775 this._renderer.offset.y = this._offset.y;
5776
5777 if (image) {
5778
5779 this._renderer.offset.x -= image.width / 2;
5780 this._renderer.offset.y += image.height / 2;
5781
5782 if (this._scale instanceof Two.Vector) {
5783 this._renderer.offset.x *= this._scale.x;
5784 this._renderer.offset.y *= this._scale.y;
5785 } else {
5786 this._renderer.offset.x *= this._scale;
5787 this._renderer.offset.y *= this._scale;
5788 }
5789 }
5790
5791 }
5792
5793 if (this._flagScale || this._flagLoaded) {
5794
5795 if (!(this._renderer.scale instanceof Two.Vector)) {
5796 this._renderer.scale = new Two.Vector();
5797 }
5798
5799 if (this._scale instanceof Two.Vector) {
5800 this._renderer.scale.copy(this._scale);
5801 } else {
5802 this._renderer.scale.set(this._scale, this._scale);
5803 }
5804
5805 }
5806
5807 return this.flagReset();
5808
5809 }
5810
5811 },
5812
5813 getTriangles: function (rect, triangles) {
5814
5815 var top = rect.top,
5816 left = rect.left,
5817 right = rect.right,
5818 bottom = rect.bottom;
5819
5820 // First Triangle
5821
5822 triangles[0] = left;
5823 triangles[1] = top;
5824
5825 triangles[2] = right;
5826 triangles[3] = top;
5827
5828 triangles[4] = left;
5829 triangles[5] = bottom;
5830
5831 // Second Triangle
5832
5833 triangles[6] = left;
5834 triangles[7] = bottom;
5835
5836 triangles[8] = right;
5837 triangles[9] = top;
5838
5839 triangles[10] = right;
5840 triangles[11] = bottom;
5841
5842 },
5843
5844 updateTexture: function (gl, elem) {
5845
5846 this[elem._renderer.type].updateCanvas.call(webgl, elem);
5847
5848 if (elem._renderer.texture) {
5849 gl.deleteTexture(elem._renderer.texture);
5850 }
5851
5852 gl.bindBuffer(gl.ARRAY_BUFFER, elem._renderer.textureCoordsBuffer);
5853
5854 // TODO: Is this necessary every time or can we do once?
5855 // TODO: Create a registry for textures
5856 elem._renderer.texture = gl.createTexture();
5857 gl.bindTexture(gl.TEXTURE_2D, elem._renderer.texture);
5858
5859 // Set the parameters so we can render any size image.
5860 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
5861 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
5862 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
5863 // gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
5864 // gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
5865 // gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
5866
5867 if (this.canvas.width <= 0 || this.canvas.height <= 0) {
5868 return;
5869 }
5870
5871 // Upload the image into the texture.
5872 gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, this.canvas);
5873
5874 },
5875
5876 updateBuffer: function (gl, elem, program) {
5877
5878 if (_.isObject(elem._renderer.buffer)) {
5879 gl.deleteBuffer(elem._renderer.buffer);
5880 }
5881
5882 elem._renderer.buffer = gl.createBuffer();
5883
5884 gl.bindBuffer(gl.ARRAY_BUFFER, elem._renderer.buffer);
5885 gl.enableVertexAttribArray(program.position);
5886
5887 gl.bufferData(gl.ARRAY_BUFFER, elem._renderer.triangles, gl.STATIC_DRAW);
5888
5889 if (_.isObject(elem._renderer.textureCoordsBuffer)) {
5890 gl.deleteBuffer(elem._renderer.textureCoordsBuffer);
5891 }
5892
5893 elem._renderer.textureCoordsBuffer = gl.createBuffer();
5894
5895 gl.bindBuffer(gl.ARRAY_BUFFER, elem._renderer.textureCoordsBuffer);
5896 gl.enableVertexAttribArray(program.textureCoords);
5897
5898 gl.bufferData(gl.ARRAY_BUFFER, this.uv, gl.STATIC_DRAW);
5899
5900 },
5901
5902 program: {
5903
5904 create: function (gl, shaders) {
5905 var program, linked, error;
5906 program = gl.createProgram();
5907 _.each(shaders, function (s) {
5908 gl.attachShader(program, s);
5909 });
5910
5911 gl.linkProgram(program);
5912 linked = gl.getProgramParameter(program, gl.LINK_STATUS);
5913 if (!linked) {
5914 error = gl.getProgramInfoLog(program);
5915 gl.deleteProgram(program);
5916 throw new Two.Utils.Error('unable to link program: ' + error);
5917 }
5918
5919 return program;
5920
5921 }
5922
5923 },
5924
5925 shaders: {
5926
5927 create: function (gl, source, type) {
5928 var shader, compiled, error;
5929 shader = gl.createShader(gl[type]);
5930 gl.shaderSource(shader, source);
5931 gl.compileShader(shader);
5932
5933 compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
5934 if (!compiled) {
5935 error = gl.getShaderInfoLog(shader);
5936 gl.deleteShader(shader);
5937 throw new Two.Utils.Error('unable to compile shader ' + shader + ': ' + error);
5938 }
5939
5940 return shader;
5941
5942 },
5943
5944 types: {
5945 vertex: 'VERTEX_SHADER',
5946 fragment: 'FRAGMENT_SHADER'
5947 },
5948
5949 vertex: [
5950 'attribute vec2 a_position;',
5951 'attribute vec2 a_textureCoords;',
5952 '',
5953 'uniform mat3 u_matrix;',
5954 'uniform vec2 u_resolution;',
5955 '',
5956 'varying vec2 v_textureCoords;',
5957 '',
5958 'void main() {',
5959 ' vec2 projected = (u_matrix * vec3(a_position, 1.0)).xy;',
5960 ' vec2 normal = projected / u_resolution;',
5961 ' vec2 clipspace = (normal * 2.0) - 1.0;',
5962 '',
5963 ' gl_Position = vec4(clipspace * vec2(1.0, -1.0), 0.0, 1.0);',
5964 ' v_textureCoords = a_textureCoords;',
5965 '}'
5966 ].join('\n'),
5967
5968 fragment: [
5969 'precision mediump float;',
5970 '',
5971 'uniform sampler2D u_image;',
5972 'varying vec2 v_textureCoords;',
5973 '',
5974 'void main() {',
5975 ' gl_FragColor = texture2D(u_image, v_textureCoords);',
5976 '}'
5977 ].join('\n')
5978
5979 },
5980
5981 TextureRegistry: new Two.Registry()
5982
5983 };
5984
5985 webgl.ctx = webgl.canvas.getContext('2d');
5986
5987 var Renderer = Two[Two.Types.webgl] = function (options) {
5988
5989 var params, gl, vs, fs;
5990 this.domElement = options.domElement || document.createElement('canvas');
5991
5992 // Everything drawn on the canvas needs to come from the stage.
5993 this.scene = new Two.Group();
5994 this.scene.parent = this;
5995
5996 this._renderer = {
5997 matrix: new Two.Array(identity),
5998 scale: 1,
5999 opacity: 1
6000 };
6001 this._flagMatrix = true;
6002
6003 // http://games.greggman.com/game/webgl-and-alpha/
6004 // http://www.khronos.org/registry/webgl/specs/latest/#5.2
6005 params = _.defaults(options || {}, {
6006 antialias: false,
6007 alpha: true,
6008 premultipliedAlpha: true,
6009 stencil: true,
6010 preserveDrawingBuffer: true,
6011 overdraw: false
6012 });
6013
6014 this.overdraw = params.overdraw;
6015
6016 gl = this.ctx = this.domElement.getContext('webgl', params) ||
6017 this.domElement.getContext('experimental-webgl', params);
6018
6019 if (!this.ctx) {
6020 throw new Two.Utils.Error(
6021 'unable to create a webgl context. Try using another renderer.');
6022 }
6023
6024 // Compile Base Shaders to draw in pixel space.
6025 vs = webgl.shaders.create(
6026 gl, webgl.shaders.vertex, webgl.shaders.types.vertex);
6027 fs = webgl.shaders.create(
6028 gl, webgl.shaders.fragment, webgl.shaders.types.fragment);
6029
6030 this.program = webgl.program.create(gl, [vs, fs]);
6031 gl.useProgram(this.program);
6032
6033 // Create and bind the drawing buffer
6034
6035 // look up where the vertex data needs to go.
6036 this.program.position = gl.getAttribLocation(this.program, 'a_position');
6037 this.program.matrix = gl.getUniformLocation(this.program, 'u_matrix');
6038 this.program.textureCoords = gl.getAttribLocation(this.program, 'a_textureCoords');
6039
6040 // Copied from Three.js WebGLRenderer
6041 gl.disable(gl.DEPTH_TEST);
6042
6043 // Setup some initial statements of the gl context
6044 gl.enable(gl.BLEND);
6045
6046 // https://code.google.com/p/chromium/issues/detail?id=316393
6047 // gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, gl.TRUE);
6048
6049 gl.blendEquationSeparate(gl.FUNC_ADD, gl.FUNC_ADD);
6050 gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA,
6051 gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
6052
6053 };
6054
6055 _.extend(Renderer, {
6056
6057 Utils: webgl
6058
6059 });
6060
6061 _.extend(Renderer.prototype, Two.Utils.Events, {
6062
6063 setSize: function (width, height, ratio) {
6064
6065 this.width = width;
6066 this.height = height;
6067
6068 this.ratio = _.isUndefined(ratio) ? getRatio(this.ctx) : ratio;
6069
6070 this.domElement.width = width * this.ratio;
6071 this.domElement.height = height * this.ratio;
6072
6073 _.extend(this.domElement.style, {
6074 width: width + 'px',
6075 height: height + 'px'
6076 });
6077
6078 width *= this.ratio;
6079 height *= this.ratio;
6080
6081 // Set for this.stage parent scaling to account for HDPI
6082 this._renderer.matrix[0] = this._renderer.matrix[4] = this._renderer.scale = this.ratio;
6083
6084 this._flagMatrix = true;
6085
6086 this.ctx.viewport(0, 0, width, height);
6087
6088 var resolutionLocation = this.ctx.getUniformLocation(
6089 this.program, 'u_resolution');
6090 this.ctx.uniform2f(resolutionLocation, width, height);
6091
6092 return this;
6093
6094 },
6095
6096 render: function () {
6097
6098 var gl = this.ctx;
6099
6100 if (!this.overdraw) {
6101 gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
6102 }
6103
6104 webgl.group.render.call(this.scene, gl, this.program);
6105 this._flagMatrix = false;
6106
6107 return this;
6108
6109 }
6110
6111 });
6112
6113})((typeof global !== 'undefined' ? global : this).Two);
6114
6115(function (Two) {
6116
6117 var _ = Two.Utils;
6118
6119 var Shape = Two.Shape = function () {
6120
6121 // Private object for renderer specific variables.
6122 this._renderer = {};
6123 this._renderer.flagMatrix = _.bind(Shape.FlagMatrix, this);
6124 this.isShape = true;
6125
6126 this.id = Two.Identifier + Two.uniqueId();
6127 this.classList = [];
6128
6129 // Define matrix properties which all inherited
6130 // objects of Shape have.
6131
6132 this._matrix = new Two.Matrix();
6133
6134 this.translation = new Two.Vector();
6135 this.rotation = 0;
6136 this.scale = 1;
6137
6138 };
6139
6140 _.extend(Shape, {
6141
6142 FlagMatrix: function () {
6143 this._flagMatrix = true;
6144 },
6145
6146 MakeObservable: function (object) {
6147
6148 Object.defineProperty(object, 'translation', {
6149 enumerable: true,
6150 get: function () {
6151 return this._translation;
6152 },
6153 set: function (v) {
6154 if (this._translation) {
6155 this._translation.unbind(Two.Events.change, this._renderer.flagMatrix);
6156 }
6157 this._translation = v;
6158 this._translation.bind(Two.Events.change, this._renderer.flagMatrix);
6159 Shape.FlagMatrix.call(this);
6160 }
6161 });
6162
6163 Object.defineProperty(object, 'rotation', {
6164 enumerable: true,
6165 get: function () {
6166 return this._rotation;
6167 },
6168 set: function (v) {
6169 this._rotation = v;
6170 this._flagMatrix = true;
6171 }
6172 });
6173
6174 Object.defineProperty(object, 'scale', {
6175 enumerable: true,
6176 get: function () {
6177 return this._scale;
6178 },
6179 set: function (v) {
6180
6181 if (this._scale instanceof Two.Vector) {
6182 this._scale.unbind(Two.Events.change, this._renderer.flagMatrix);
6183 }
6184
6185 this._scale = v;
6186
6187 if (this._scale instanceof Two.Vector) {
6188 this._scale.bind(Two.Events.change, this._renderer.flagMatrix);
6189 }
6190
6191 this._flagMatrix = true;
6192 this._flagScale = true;
6193
6194 }
6195 });
6196
6197 }
6198
6199 });
6200
6201 _.extend(Shape.prototype, Two.Utils.Events, {
6202
6203 // Flags
6204
6205 _flagMatrix: true,
6206 _flagScale: false,
6207
6208 // _flagMask: false,
6209 // _flagClip: false,
6210
6211 // Underlying Properties
6212
6213 _rotation: 0,
6214 _scale: 1,
6215 _translation: null,
6216
6217 // _mask: null,
6218 // _clip: false,
6219
6220 addTo: function (group) {
6221 group.add(this);
6222 return this;
6223 },
6224
6225 clone: function () {
6226 var clone = new Shape();
6227 clone.translation.copy(this.translation);
6228 clone.rotation = this.rotation;
6229 clone.scale = this.scale;
6230 _.each(Shape.Properties, function (k) {
6231 clone[k] = this[k];
6232 }, this);
6233 return clone._update();
6234 },
6235
6236 /**
6237 * To be called before render that calculates and collates all information
6238 * to be as up-to-date as possible for the render. Called once a frame.
6239 */
6240 _update: function (deep) {
6241
6242 if (!this._matrix.manual && this._flagMatrix) {
6243
6244 this._matrix
6245 .identity()
6246 .translate(this.translation.x, this.translation.y);
6247
6248 if (this._scale instanceof Two.Vector) {
6249 this._matrix.scale(this._scale.x, this._scale.y);
6250 } else {
6251 this._matrix.scale(this._scale);
6252 }
6253
6254 this._matrix.rotate(this.rotation);
6255
6256 }
6257
6258 if (deep) {
6259 // Bubble up to parents mainly for `getBoundingClientRect` method.
6260 if (this.parent && this.parent._update) {
6261 this.parent._update();
6262 }
6263 }
6264
6265 return this;
6266
6267 },
6268
6269 flagReset: function () {
6270
6271 this._flagMatrix = this._flagScale = false;
6272
6273 return this;
6274
6275 }
6276
6277 });
6278
6279 Shape.MakeObservable(Shape.prototype);
6280
6281})((typeof global !== 'undefined' ? global : this).Two);
6282
6283(function (Two) {
6284
6285 /**
6286 * Constants
6287 */
6288
6289 var min = Math.min, max = Math.max, round = Math.round,
6290 getComputedMatrix = Two.Utils.getComputedMatrix;
6291
6292 var commands = {};
6293 var _ = Two.Utils;
6294
6295 _.each(Two.Commands, function (v, k) {
6296 commands[k] = new RegExp(v);
6297 });
6298
6299 var Path = Two.Path = function (vertices, closed, curved, manual) {
6300
6301 Two.Shape.call(this);
6302
6303 this._renderer.type = 'path';
6304 this._renderer.flagVertices = _.bind(Path.FlagVertices, this);
6305 this._renderer.bindVertices = _.bind(Path.BindVertices, this);
6306 this._renderer.unbindVertices = _.bind(Path.UnbindVertices, this);
6307
6308 this._renderer.flagFill = _.bind(Path.FlagFill, this);
6309 this._renderer.flagStroke = _.bind(Path.FlagStroke, this);
6310
6311 this._closed = !!closed;
6312 this._curved = !!curved;
6313
6314 this.beginning = 0;
6315 this.ending = 1;
6316
6317 // Style properties
6318
6319 this.fill = '#fff';
6320 this.stroke = '#000';
6321 this.linewidth = 1.0;
6322 this.opacity = 1.0;
6323 this.visible = true;
6324
6325 this.cap = 'butt'; // Default of Adobe Illustrator
6326 this.join = 'miter'; // Default of Adobe Illustrator
6327 this.miter = 4; // Default of Adobe Illustrator
6328
6329 this._vertices = [];
6330 this.vertices = vertices;
6331
6332 // Determines whether or not two.js should calculate curves, lines, and
6333 // commands automatically for you or to let the developer manipulate them
6334 // for themselves.
6335 this.automatic = !manual;
6336
6337 };
6338
6339 _.extend(Path, {
6340
6341 Properties: [
6342 'fill',
6343 'stroke',
6344 'linewidth',
6345 'opacity',
6346 'visible',
6347 'cap',
6348 'join',
6349 'miter',
6350
6351 'closed',
6352 'curved',
6353 'automatic',
6354 'beginning',
6355 'ending'
6356 ],
6357
6358 FlagVertices: function () {
6359 this._flagVertices = true;
6360 this._flagLength = true;
6361 },
6362
6363 BindVertices: function (items) {
6364
6365 // This function is called a lot
6366 // when importing a large SVG
6367 var i = items.length;
6368 while (i--) {
6369 items[i].bind(Two.Events.change, this._renderer.flagVertices);
6370 }
6371
6372 this._renderer.flagVertices();
6373
6374 },
6375
6376 UnbindVertices: function (items) {
6377
6378 var i = items.length;
6379 while (i--) {
6380 items[i].unbind(Two.Events.change, this._renderer.flagVertices);
6381 }
6382
6383 this._renderer.flagVertices();
6384
6385 },
6386
6387 FlagFill: function () {
6388 this._flagFill = true;
6389 },
6390
6391 FlagStroke: function () {
6392 this._flagStroke = true;
6393 },
6394
6395 MakeObservable: function (object) {
6396
6397 Two.Shape.MakeObservable(object);
6398
6399 // Only the 6 defined properties are flagged like this. The subsequent
6400 // properties behave differently and need to be hand written.
6401 _.each(Path.Properties.slice(2, 8), Two.Utils.defineProperty, object);
6402
6403 Object.defineProperty(object, 'fill', {
6404 enumerable: true,
6405 get: function () {
6406 return this._fill;
6407 },
6408 set: function (f) {
6409
6410 if (this._fill instanceof Two.Gradient
6411 || this._fill instanceof Two.LinearGradient
6412 || this._fill instanceof Two.RadialGradient
6413 || this._fill instanceof Two.Texture) {
6414 this._fill.unbind(Two.Events.change, this._renderer.flagFill);
6415 }
6416
6417 this._fill = f;
6418 this._flagFill = true;
6419
6420 if (this._fill instanceof Two.Gradient
6421 || this._fill instanceof Two.LinearGradient
6422 || this._fill instanceof Two.RadialGradient
6423 || this._fill instanceof Two.Texture) {
6424 this._fill.bind(Two.Events.change, this._renderer.flagFill);
6425 }
6426
6427 }
6428 });
6429
6430 Object.defineProperty(object, 'stroke', {
6431 enumerable: true,
6432 get: function () {
6433 return this._stroke;
6434 },
6435 set: function (f) {
6436
6437 if (this._stroke instanceof Two.Gradient
6438 || this._stroke instanceof Two.LinearGradient
6439 || this._stroke instanceof Two.RadialGradient
6440 || this._stroke instanceof Two.Texture) {
6441 this._stroke.unbind(Two.Events.change, this._renderer.flagStroke);
6442 }
6443
6444 this._stroke = f;
6445 this._flagStroke = true;
6446
6447 if (this._stroke instanceof Two.Gradient
6448 || this._stroke instanceof Two.LinearGradient
6449 || this._stroke instanceof Two.RadialGradient
6450 || this._stroke instanceof Two.Texture) {
6451 this._stroke.bind(Two.Events.change, this._renderer.flagStroke);
6452 }
6453
6454 }
6455 });
6456
6457 Object.defineProperty(object, 'length', {
6458 get: function () {
6459 if (this._flagLength) {
6460 this._updateLength();
6461 }
6462 return this._length;
6463 }
6464 });
6465
6466 Object.defineProperty(object, 'closed', {
6467 enumerable: true,
6468 get: function () {
6469 return this._closed;
6470 },
6471 set: function (v) {
6472 this._closed = !!v;
6473 this._flagVertices = true;
6474 }
6475 });
6476
6477 Object.defineProperty(object, 'curved', {
6478 enumerable: true,
6479 get: function () {
6480 return this._curved;
6481 },
6482 set: function (v) {
6483 this._curved = !!v;
6484 this._flagVertices = true;
6485 }
6486 });
6487
6488 Object.defineProperty(object, 'automatic', {
6489 enumerable: true,
6490 get: function () {
6491 return this._automatic;
6492 },
6493 set: function (v) {
6494 if (v === this._automatic) {
6495 return;
6496 }
6497 this._automatic = !!v;
6498 var method = this._automatic ? 'ignore' : 'listen';
6499 _.each(this.vertices, function (v) {
6500 v[method]();
6501 });
6502 }
6503 });
6504
6505 Object.defineProperty(object, 'beginning', {
6506 enumerable: true,
6507 get: function () {
6508 return this._beginning;
6509 },
6510 set: function (v) {
6511 this._beginning = v;
6512 this._flagVertices = true;
6513 }
6514 });
6515
6516 Object.defineProperty(object, 'ending', {
6517 enumerable: true,
6518 get: function () {
6519 return this._ending;
6520 },
6521 set: function (v) {
6522 this._ending = v;
6523 this._flagVertices = true;
6524 }
6525 });
6526
6527 Object.defineProperty(object, 'vertices', {
6528
6529 enumerable: true,
6530
6531 get: function () {
6532 return this._collection;
6533 },
6534
6535 set: function (vertices) {
6536
6537 var updateVertices = this._renderer.flagVertices;
6538 var bindVertices = this._renderer.bindVertices;
6539 var unbindVertices = this._renderer.unbindVertices;
6540
6541 // Remove previous listeners
6542 if (this._collection) {
6543 this._collection
6544 .unbind(Two.Events.insert, bindVertices)
6545 .unbind(Two.Events.remove, unbindVertices);
6546 }
6547
6548 // Create new Collection with copy of vertices
6549 this._collection = new Two.Utils.Collection((vertices || []).slice(0));
6550
6551 // Listen for Collection changes and bind / unbind
6552 this._collection
6553 .bind(Two.Events.insert, bindVertices)
6554 .bind(Two.Events.remove, unbindVertices);
6555
6556 // Bind Initial Vertices
6557 bindVertices(this._collection);
6558
6559 }
6560
6561 });
6562
6563 Object.defineProperty(object, 'clip', {
6564 enumerable: true,
6565 get: function () {
6566 return this._clip;
6567 },
6568 set: function (v) {
6569 this._clip = v;
6570 this._flagClip = true;
6571 }
6572 });
6573
6574 }
6575
6576 });
6577
6578 _.extend(Path.prototype, Two.Shape.prototype, {
6579
6580 // Flags
6581 // http://en.wikipedia.org/wiki/Flag
6582
6583 _flagVertices: true,
6584 _flagLength: true,
6585
6586 _flagFill: true,
6587 _flagStroke: true,
6588 _flagLinewidth: true,
6589 _flagOpacity: true,
6590 _flagVisible: true,
6591
6592 _flagCap: true,
6593 _flagJoin: true,
6594 _flagMiter: true,
6595
6596 _flagClip: false,
6597
6598 // Underlying Properties
6599
6600 _length: 0,
6601
6602 _fill: '#fff',
6603 _stroke: '#000',
6604 _linewidth: 1.0,
6605 _opacity: 1.0,
6606 _visible: true,
6607
6608 _cap: 'round',
6609 _join: 'round',
6610 _miter: 4,
6611
6612 _closed: true,
6613 _curved: false,
6614 _automatic: true,
6615 _beginning: 0,
6616 _ending: 1.0,
6617
6618 _clip: false,
6619
6620 clone: function (parent) {
6621
6622 parent = parent || this.parent;
6623
6624 var points = _.map(this.vertices, function (v) {
6625 return v.clone();
6626 });
6627
6628 var clone = new Path(points, this.closed, this.curved, !this.automatic);
6629
6630 _.each(Two.Path.Properties, function (k) {
6631 clone[k] = this[k];
6632 }, this);
6633
6634 clone.translation.copy(this.translation);
6635 clone.rotation = this.rotation;
6636 clone.scale = this.scale;
6637
6638 if (parent) {
6639 parent.add(clone);
6640 }
6641
6642 return clone;
6643
6644 },
6645
6646 toObject: function () {
6647
6648 var result = {
6649 vertices: _.map(this.vertices, function (v) {
6650 return v.toObject();
6651 })
6652 };
6653
6654 _.each(Two.Shape.Properties, function (k) {
6655 result[k] = this[k];
6656 }, this);
6657
6658 result.translation = this.translation.toObject;
6659 result.rotation = this.rotation;
6660 result.scale = this.scale;
6661
6662 return result;
6663
6664 },
6665
6666 noFill: function () {
6667 this.fill = 'transparent';
6668 return this;
6669 },
6670
6671 noStroke: function () {
6672 this.stroke = 'transparent';
6673 return this;
6674 },
6675
6676 /**
6677 * Orient the vertices of the shape to the upper lefthand
6678 * corner of the path.
6679 */
6680 corner: function () {
6681
6682 var rect = this.getBoundingClientRect(true);
6683
6684 rect.centroid = {
6685 x: rect.left + rect.width / 2,
6686 y: rect.top + rect.height / 2
6687 };
6688
6689 _.each(this.vertices, function (v) {
6690 v.addSelf(rect.centroid);
6691 });
6692
6693 return this;
6694
6695 },
6696
6697 /**
6698 * Orient the vertices of the shape to the center of the
6699 * path.
6700 */
6701 center: function () {
6702
6703 var rect = this.getBoundingClientRect(true);
6704
6705 rect.centroid = {
6706 x: rect.left + rect.width / 2,
6707 y: rect.top + rect.height / 2
6708 };
6709
6710 _.each(this.vertices, function (v) {
6711 v.subSelf(rect.centroid);
6712 });
6713
6714 // this.translation.addSelf(rect.centroid);
6715
6716 return this;
6717
6718 },
6719
6720 /**
6721 * Remove self from the scene / parent.
6722 */
6723 remove: function () {
6724
6725 if (!this.parent) {
6726 return this;
6727 }
6728
6729 this.parent.remove(this);
6730
6731 return this;
6732
6733 },
6734
6735 /**
6736 * Return an object with top, left, right, bottom, width, and height
6737 * parameters of the group.
6738 */
6739 getBoundingClientRect: function (shallow) {
6740 var matrix, border, l, x, y, i, v;
6741
6742 var left = Infinity, right = -Infinity,
6743 top = Infinity, bottom = -Infinity;
6744
6745 // TODO: Update this to not __always__ update. Just when it needs to.
6746 this._update(true);
6747
6748 matrix = !!shallow ? this._matrix : getComputedMatrix(this);
6749
6750 border = this.linewidth / 2;
6751 l = this._vertices.length;
6752
6753 if (l <= 0) {
6754 v = matrix.multiply(0, 0, 1);
6755 return {
6756 top: v.y,
6757 left: v.x,
6758 right: v.x,
6759 bottom: v.y,
6760 width: 0,
6761 height: 0
6762 };
6763 }
6764
6765 for (i = 0; i < l; i++) {
6766 v = this._vertices[i];
6767
6768 x = v.x;
6769 y = v.y;
6770
6771 v = matrix.multiply(x, y, 1);
6772 top = min(v.y - border, top);
6773 left = min(v.x - border, left);
6774 right = max(v.x + border, right);
6775 bottom = max(v.y + border, bottom);
6776 }
6777
6778 return {
6779 top: top,
6780 left: left,
6781 right: right,
6782 bottom: bottom,
6783 width: right - left,
6784 height: bottom - top
6785 };
6786
6787 },
6788
6789 /**
6790 * Given a float `t` from 0 to 1, return a point or assign a passed `obj`'s
6791 * coordinates to that percentage on this Two.Path's curve.
6792 */
6793 getPointAt: function (t, obj) {
6794 var ia, ib;
6795 var x, x1, x2, x3, x4, y, y1, y2, y3, y4, left, right;
6796 var target = this.length * Math.min(Math.max(t, 0), 1);
6797 var length = this.vertices.length;
6798 var last = length - 1;
6799
6800 var a = null;
6801 var b = null;
6802
6803 for (var i = 0, l = this._lengths.length, sum = 0; i < l; i++) {
6804
6805 if (sum + this._lengths[i] >= target) {
6806
6807 if (this._closed) {
6808 ia = Two.Utils.mod(i, length);
6809 ib = Two.Utils.mod(i - 1, length);
6810 if (i === 0) {
6811 ia = ib;
6812 ib = i;
6813 }
6814 } else {
6815 ia = i;
6816 ib = Math.min(Math.max(i - 1, 0), last);
6817 }
6818
6819 a = this.vertices[ia];
6820 b = this.vertices[ib];
6821 target -= sum;
6822 if (this._lengths[i] !== 0) {
6823 t = target / this._lengths[i];
6824 }
6825
6826 break;
6827
6828 }
6829
6830 sum += this._lengths[i];
6831
6832 }
6833
6834 // console.log(sum, a.command, b.command);
6835
6836 if (_.isNull(a) || _.isNull(b)) {
6837 return null;
6838 }
6839
6840 right = b.controls && b.controls.right;
6841 left = a.controls && a.controls.left;
6842
6843 x1 = b.x;
6844 y1 = b.y;
6845 x2 = (right || b).x;
6846 y2 = (right || b).y;
6847 x3 = (left || a).x;
6848 y3 = (left || a).y;
6849 x4 = a.x;
6850 y4 = a.y;
6851
6852 if (right && b._relative) {
6853 x2 += b.x;
6854 y2 += b.y;
6855 }
6856
6857 if (left && a._relative) {
6858 x3 += a.x;
6859 y3 += a.y;
6860 }
6861
6862 x = Two.Utils.getPointOnCubicBezier(t, x1, x2, x3, x4);
6863 y = Two.Utils.getPointOnCubicBezier(t, y1, y2, y3, y4);
6864
6865 if (_.isObject(obj)) {
6866 obj.x = x;
6867 obj.y = y;
6868 return obj;
6869 }
6870
6871 return new Two.Vector(x, y);
6872
6873 },
6874
6875 /**
6876 * Based on closed / curved and sorting of vertices plot where all points
6877 * should be and where the respective handles should be too.
6878 */
6879 plot: function () {
6880
6881 if (this.curved) {
6882 Two.Utils.getCurveFromPoints(this._vertices, this.closed);
6883 return this;
6884 }
6885
6886 for (var i = 0; i < this._vertices.length; i++) {
6887 this._vertices[i]._command = i === 0 ? Two.Commands.move : Two.Commands.line;
6888 }
6889
6890 return this;
6891
6892 },
6893
6894 subdivide: function (limit) {
6895 //TODO: DRYness (function below)
6896 this._update();
6897
6898 var last = this.vertices.length - 1;
6899 var b = this.vertices[last];
6900 var closed = this._closed || this.vertices[last]._command === Two.Commands.close;
6901 var points = [];
6902 _.each(this.vertices, function (a, i) {
6903
6904 if (i <= 0 && !closed) {
6905 b = a;
6906 return;
6907 }
6908
6909 if (a.command === Two.Commands.move) {
6910 points.push(new Two.Anchor(b.x, b.y));
6911 if (i > 0) {
6912 points[points.length - 1].command = Two.Commands.line;
6913 }
6914 b = a;
6915 return;
6916 }
6917
6918 var verts = getSubdivisions(a, b, limit);
6919 points = points.concat(verts);
6920
6921 // Assign commands to all the verts
6922 _.each(verts, function (v, i) {
6923 if (i <= 0 && b.command === Two.Commands.move) {
6924 v.command = Two.Commands.move;
6925 } else {
6926 v.command = Two.Commands.line;
6927 }
6928 });
6929
6930 if (i >= last) {
6931
6932 // TODO: Add check if the two vectors in question are the same values.
6933 if (this._closed && this._automatic) {
6934
6935 b = a;
6936
6937 verts = getSubdivisions(a, b, limit);
6938 points = points.concat(verts);
6939
6940 // Assign commands to all the verts
6941 _.each(verts, function (v, i) {
6942 if (i <= 0 && b.command === Two.Commands.move) {
6943 v.command = Two.Commands.move;
6944 } else {
6945 v.command = Two.Commands.line;
6946 }
6947 });
6948
6949 } else if (closed) {
6950 points.push(new Two.Anchor(a.x, a.y));
6951 }
6952
6953 points[points.length - 1].command = closed ? Two.Commands.close : Two.Commands.line;
6954
6955 }
6956
6957 b = a;
6958
6959 }, this);
6960
6961 this._automatic = false;
6962 this._curved = false;
6963 this.vertices = points;
6964
6965 return this;
6966
6967 },
6968
6969 _updateLength: function (limit) {
6970 //TODO: DRYness (function above)
6971 this._update();
6972
6973 var length = this.vertices.length;
6974 var last = length - 1;
6975 var b = this.vertices[last];
6976 var closed = this._closed || this.vertices[last]._command === Two.Commands.close;
6977 var sum = 0;
6978
6979 if (_.isUndefined(this._lengths)) {
6980 this._lengths = [];
6981 }
6982
6983 _.each(this.vertices, function (a, i) {
6984
6985 if ((i <= 0 && !closed) || a.command === Two.Commands.move) {
6986 b = a;
6987 this._lengths[i] = 0;
6988 return;
6989 }
6990
6991 this._lengths[i] = getCurveLength(a, b, limit);
6992 sum += this._lengths[i];
6993
6994 if (i >= last && closed) {
6995
6996 b = this.vertices[(i + 1) % length];
6997
6998 this._lengths[i + 1] = getCurveLength(a, b, limit);
6999 sum += this._lengths[i + 1];
7000
7001 }
7002
7003 b = a;
7004
7005 }, this);
7006
7007 this._length = sum;
7008
7009 return this;
7010
7011 },
7012
7013 _update: function () {
7014
7015 if (this._flagVertices) {
7016
7017 var l = this.vertices.length;
7018 var last = l - 1, v;
7019
7020 // TODO: Should clamp this so that `ia` and `ib`
7021 // cannot select non-verts.
7022 var ia = round((this._beginning) * last);
7023 var ib = round((this._ending) * last);
7024
7025 this._vertices.length = 0;
7026
7027 for (var i = ia; i < ib + 1; i++) {
7028 v = this.vertices[i];
7029 this._vertices.push(v);
7030 }
7031
7032 if (this._automatic) {
7033 this.plot();
7034 }
7035
7036 }
7037
7038 Two.Shape.prototype._update.apply(this, arguments);
7039
7040 return this;
7041
7042 },
7043
7044 flagReset: function () {
7045
7046 this._flagVertices = this._flagFill = this._flagStroke =
7047 this._flagLinewidth = this._flagOpacity = this._flagVisible =
7048 this._flagCap = this._flagJoin = this._flagMiter =
7049 this._flagClip = false;
7050
7051 Two.Shape.prototype.flagReset.call(this);
7052
7053 return this;
7054
7055 }
7056
7057 });
7058
7059 Path.MakeObservable(Path.prototype);
7060
7061 /**
7062 * Utility functions
7063 */
7064
7065 function getCurveLength(a, b, limit) {
7066 // TODO: DRYness
7067 var x1, x2, x3, x4, y1, y2, y3, y4;
7068
7069 var right = b.controls && b.controls.right;
7070 var left = a.controls && a.controls.left;
7071
7072 x1 = b.x;
7073 y1 = b.y;
7074 x2 = (right || b).x;
7075 y2 = (right || b).y;
7076 x3 = (left || a).x;
7077 y3 = (left || a).y;
7078 x4 = a.x;
7079 y4 = a.y;
7080
7081 if (right && b._relative) {
7082 x2 += b.x;
7083 y2 += b.y;
7084 }
7085
7086 if (left && a._relative) {
7087 x3 += a.x;
7088 y3 += a.y;
7089 }
7090
7091 return Two.Utils.getCurveLength(x1, y1, x2, y2, x3, y3, x4, y4, limit);
7092
7093 }
7094
7095 function getSubdivisions(a, b, limit) {
7096 // TODO: DRYness
7097 var x1, x2, x3, x4, y1, y2, y3, y4;
7098
7099 var right = b.controls && b.controls.right;
7100 var left = a.controls && a.controls.left;
7101
7102 x1 = b.x;
7103 y1 = b.y;
7104 x2 = (right || b).x;
7105 y2 = (right || b).y;
7106 x3 = (left || a).x;
7107 y3 = (left || a).y;
7108 x4 = a.x;
7109 y4 = a.y;
7110
7111 if (right && b._relative) {
7112 x2 += b.x;
7113 y2 += b.y;
7114 }
7115
7116 if (left && a._relative) {
7117 x3 += a.x;
7118 y3 += a.y;
7119 }
7120
7121 return Two.Utils.subdivide(x1, y1, x2, y2, x3, y3, x4, y4, limit);
7122
7123 }
7124
7125})((typeof global !== 'undefined' ? global : this).Two);
7126
7127(function (Two) {
7128
7129 var Path = Two.Path;
7130 var _ = Two.Utils;
7131
7132 var Line = Two.Line = function (x1, y1, x2, y2) {
7133
7134 var width = x2 - x1;
7135 var height = y2 - y1;
7136
7137 var w2 = width / 2;
7138 var h2 = height / 2;
7139
7140 Path.call(this, [
7141 new Two.Anchor(- w2, - h2),
7142 new Two.Anchor(w2, h2)
7143 ]);
7144
7145 this.translation.set(x1 + w2, y1 + h2);
7146
7147 };
7148
7149 _.extend(Line.prototype, Path.prototype);
7150
7151 Path.MakeObservable(Line.prototype);
7152
7153})((typeof global !== 'undefined' ? global : this).Two);
7154
7155(function (Two) {
7156
7157 var Path = Two.Path;
7158 var _ = Two.Utils;
7159
7160 var Rectangle = Two.Rectangle = function (x, y, width, height) {
7161
7162 Path.call(this, [
7163 new Two.Anchor(),
7164 new Two.Anchor(),
7165 new Two.Anchor(),
7166 new Two.Anchor()
7167 ], true);
7168
7169 this.width = width;
7170 this.height = height;
7171 this._update();
7172
7173 this.translation.set(x, y);
7174
7175 };
7176
7177 _.extend(Rectangle, {
7178
7179 Properties: ['width', 'height'],
7180
7181 MakeObservable: function (obj) {
7182 Path.MakeObservable(obj);
7183 _.each(Rectangle.Properties, Two.Utils.defineProperty, obj);
7184 }
7185
7186 });
7187
7188 _.extend(Rectangle.prototype, Path.prototype, {
7189
7190 _width: 0,
7191 _height: 0,
7192
7193 _flagWidth: 0,
7194 _flagHeight: 0,
7195
7196 _update: function () {
7197
7198 if (this._flagWidth || this._flagHeight) {
7199
7200 var xr = this._width / 2;
7201 var yr = this._height / 2;
7202
7203 this.vertices[0].set(-xr, -yr);
7204 this.vertices[1].set(xr, -yr);
7205 this.vertices[2].set(xr, yr);
7206 this.vertices[3].set(-xr, yr);
7207
7208 }
7209
7210 Path.prototype._update.call(this);
7211
7212 return this;
7213
7214 },
7215
7216 flagReset: function () {
7217
7218 this._flagWidth = this._flagHeight = false;
7219 Path.prototype.flagReset.call(this);
7220
7221 return this;
7222
7223 }
7224
7225 });
7226
7227 Rectangle.MakeObservable(Rectangle.prototype);
7228
7229})((typeof global !== 'undefined' ? global : this).Two);
7230
7231(function (Two) {
7232
7233 var Path = Two.Path, TWO_PI = Math.PI * 2, cos = Math.cos, sin = Math.sin;
7234 var _ = Two.Utils;
7235
7236 var Ellipse = Two.Ellipse = function (ox, oy, rx, ry) {
7237
7238 if (!_.isNumber(ry)) {
7239 ry = rx;
7240 }
7241
7242 var amount = Two.Resolution;
7243
7244 var points = _.map(_.range(amount), function (i) {
7245 return new Two.Anchor();
7246 }, this);
7247
7248 Path.call(this, points, true, true);
7249
7250 this.width = rx * 2;
7251 this.height = ry * 2;
7252
7253 this._update();
7254 this.translation.set(ox, oy);
7255
7256 };
7257
7258 _.extend(Ellipse, {
7259
7260 Properties: ['width', 'height'],
7261
7262 MakeObservable: function (obj) {
7263
7264 Path.MakeObservable(obj);
7265 _.each(Ellipse.Properties, Two.Utils.defineProperty, obj);
7266
7267 }
7268
7269 });
7270
7271 _.extend(Ellipse.prototype, Path.prototype, {
7272
7273 _width: 0,
7274 _height: 0,
7275
7276 _flagWidth: false,
7277 _flagHeight: false,
7278
7279 _update: function () {
7280
7281 if (this._flagWidth || this._flagHeight) {
7282 for (var i = 0, l = this.vertices.length; i < l; i++) {
7283 var pct = i / l;
7284 var theta = pct * TWO_PI;
7285 var x = this._width * cos(theta) / 2;
7286 var y = this._height * sin(theta) / 2;
7287 this.vertices[i].set(x, y);
7288 }
7289 }
7290
7291 Path.prototype._update.call(this);
7292 return this;
7293
7294 },
7295
7296 flagReset: function () {
7297
7298 this._flagWidth = this._flagHeight = false;
7299
7300 Path.prototype.flagReset.call(this);
7301 return this;
7302
7303 }
7304
7305 });
7306
7307 Ellipse.MakeObservable(Ellipse.prototype);
7308
7309})((typeof global !== 'undefined' ? global : this).Two);
7310
7311(function (Two) {
7312
7313 var Path = Two.Path, TWO_PI = Math.PI * 2, cos = Math.cos, sin = Math.sin;
7314 var _ = Two.Utils;
7315
7316 var Circle = Two.Circle = function (ox, oy, r) {
7317
7318 var amount = Two.Resolution;
7319
7320 var points = _.map(_.range(amount), function (i) {
7321 return new Two.Anchor();
7322 }, this);
7323
7324 Path.call(this, points, true, true);
7325
7326 this.radius = r;
7327
7328 this._update();
7329 this.translation.set(ox, oy);
7330
7331 };
7332
7333 _.extend(Circle, {
7334
7335 Properties: ['radius'],
7336
7337 MakeObservable: function (obj) {
7338
7339 Path.MakeObservable(obj);
7340 _.each(Circle.Properties, Two.Utils.defineProperty, obj);
7341
7342 }
7343
7344 });
7345
7346 _.extend(Circle.prototype, Path.prototype, {
7347
7348 _radius: 0,
7349 _flagRadius: false,
7350
7351 _update: function () {
7352
7353 if (this._flagRadius) {
7354 for (var i = 0, l = this.vertices.length; i < l; i++) {
7355 var pct = i / l;
7356 var theta = pct * TWO_PI;
7357 var x = this._radius * cos(theta);
7358 var y = this._radius * sin(theta);
7359 this.vertices[i].set(x, y);
7360 }
7361 }
7362
7363 Path.prototype._update.call(this);
7364 return this;
7365
7366 },
7367
7368 flagReset: function () {
7369
7370 this._flagRadius = false;
7371
7372 Path.prototype.flagReset.call(this);
7373 return this;
7374
7375 }
7376
7377 });
7378
7379 Circle.MakeObservable(Circle.prototype);
7380
7381})((typeof global !== 'undefined' ? global : this).Two);
7382
7383(function (Two) {
7384
7385 var Path = Two.Path, TWO_PI = Math.PI * 2, cos = Math.cos, sin = Math.sin;
7386 var _ = Two.Utils;
7387
7388 var Polygon = Two.Polygon = function (ox, oy, r, sides) {
7389
7390 sides = Math.max(sides || 0, 3);
7391
7392 var points = _.map(_.range(sides), function (i) {
7393 return new Two.Anchor();
7394 });
7395
7396 Path.call(this, points, true);
7397
7398 this.width = r * 2;
7399 this.height = r * 2;
7400 this.sides = sides;
7401
7402 this._update();
7403 this.translation.set(ox, oy);
7404
7405 };
7406
7407 _.extend(Polygon, {
7408
7409 Properties: ['width', 'height', 'sides'],
7410
7411 MakeObservable: function (obj) {
7412
7413 Path.MakeObservable(obj);
7414 _.each(Polygon.Properties, Two.Utils.defineProperty, obj);
7415
7416 }
7417
7418 });
7419
7420 _.extend(Polygon.prototype, Path.prototype, {
7421
7422 _width: 0,
7423 _height: 0,
7424 _sides: 0,
7425
7426 _flagWidth: false,
7427 _flagHeight: false,
7428 _flagSides: false,
7429
7430 _update: function () {
7431
7432 if (this._flagWidth || this._flagHeight || this._flagSides) {
7433
7434 var sides = this._sides;
7435 var amount = this.vertices.length;
7436
7437 if (amount > sides) {
7438 this.vertices.splice(sides - 1, amount - sides);
7439 }
7440
7441 for (var i = 0; i < sides; i++) {
7442
7443 var pct = (i + 0.5) / sides;
7444 var theta = TWO_PI * pct + Math.PI / 2;
7445 var x = this._width * cos(theta);
7446 var y = this._height * sin(theta);
7447
7448 if (i >= amount) {
7449 this.vertices.push(new Two.Anchor(x, y));
7450 } else {
7451 this.vertices[i].set(x, y);
7452 }
7453
7454 }
7455
7456 }
7457
7458 Path.prototype._update.call(this);
7459 return this;
7460
7461 },
7462
7463 flagReset: function () {
7464
7465 this._flagWidth = this._flagHeight = this._flagSides = false;
7466 Path.prototype.flagReset.call(this);
7467
7468 return this;
7469
7470 }
7471
7472 });
7473
7474 Polygon.MakeObservable(Polygon.prototype);
7475
7476})((typeof global !== 'undefined' ? global : this).Two);
7477
7478(function (Two) {
7479
7480 var Path = Two.Path, PI = Math.PI, TWO_PI = Math.PI * 2, HALF_PI = Math.PI / 2,
7481 cos = Math.cos, sin = Math.sin, abs = Math.abs, _ = Two.Utils;
7482
7483 var ArcSegment = Two.ArcSegment = function (ox, oy, ir, or, sa, ea, res) {
7484
7485 var points = _.map(_.range(res || (Two.Resolution * 3)), function () {
7486 return new Two.Anchor();
7487 });
7488
7489 Path.call(this, points, false, false, true);
7490
7491 this.innerRadius = ir;
7492 this.outerRadius = or;
7493
7494 this.startAngle = sa;
7495 this.endAngle = ea;
7496
7497 this._update();
7498 this.translation.set(ox, oy);
7499
7500 }
7501
7502 _.extend(ArcSegment, {
7503
7504 Properties: ['startAngle', 'endAngle', 'innerRadius', 'outerRadius'],
7505
7506 MakeObservable: function (obj) {
7507
7508 Path.MakeObservable(obj);
7509 _.each(ArcSegment.Properties, Two.Utils.defineProperty, obj);
7510
7511 }
7512
7513 });
7514
7515 _.extend(ArcSegment.prototype, Path.prototype, {
7516
7517 _flagStartAngle: false,
7518 _flagEndAngle: false,
7519 _flagInnerRadius: false,
7520 _flagOuterRadius: false,
7521
7522 _startAngle: 0,
7523 _endAngle: TWO_PI,
7524 _innerRadius: 0,
7525 _outerRadius: 0,
7526
7527 _update: function () {
7528
7529 if (this._flagStartAngle || this._flagEndAngle || this._flagInnerRadius
7530 || this._flagOuterRadius) {
7531
7532 var sa = this._startAngle;
7533 var ea = this._endAngle;
7534
7535 var ir = this._innerRadius;
7536 var or = this._outerRadius;
7537
7538 var connected = mod(sa, TWO_PI) === mod(ea, TWO_PI);
7539 var punctured = ir > 0;
7540
7541 var vertices = this.vertices;
7542 var length = (punctured ? vertices.length / 2 : vertices.length);
7543 var command, id = 0;
7544
7545 if (connected) {
7546 length--;
7547 } else if (!punctured) {
7548 length -= 2;
7549 }
7550
7551 /**
7552 * Outer Circle
7553 */
7554 for (var i = 0, last = length - 1; i < length; i++) {
7555
7556 var pct = i / last;
7557 var v = vertices[id];
7558 var theta = pct * (ea - sa) + sa;
7559 var step = (ea - sa) / length;
7560
7561 var x = or * Math.cos(theta);
7562 var y = or * Math.sin(theta);
7563
7564 switch (i) {
7565 case 0:
7566 command = Two.Commands.move;
7567 break;
7568 default:
7569 command = Two.Commands.curve;
7570 }
7571
7572 v.command = command;
7573 v.x = x;
7574 v.y = y;
7575 v.controls.left.clear();
7576 v.controls.right.clear();
7577
7578 if (v.command === Two.Commands.curve) {
7579 var amp = or * step / Math.PI;
7580 v.controls.left.x = amp * Math.cos(theta - HALF_PI);
7581 v.controls.left.y = amp * Math.sin(theta - HALF_PI);
7582 v.controls.right.x = amp * Math.cos(theta + HALF_PI);
7583 v.controls.right.y = amp * Math.sin(theta + HALF_PI);
7584 if (i === 1) {
7585 v.controls.left.multiplyScalar(2);
7586 }
7587 if (i === last) {
7588 v.controls.right.multiplyScalar(2);
7589 }
7590 }
7591
7592 id++;
7593
7594 }
7595
7596 if (punctured) {
7597
7598 if (connected) {
7599 vertices[id].command = Two.Commands.close;
7600 id++;
7601 } else {
7602 length--;
7603 last = length - 1;
7604 }
7605
7606 /**
7607 * Inner Circle
7608 */
7609 for (i = 0; i < length; i++) {
7610
7611 pct = i / last;
7612 v = vertices[id];
7613 theta = (1 - pct) * (ea - sa) + sa;
7614 step = (ea - sa) / length;
7615
7616 x = ir * Math.cos(theta);
7617 y = ir * Math.sin(theta);
7618 command = Two.Commands.curve;
7619 if (i <= 0) {
7620 command = connected ? Two.Commands.move : Two.Commands.line;
7621 }
7622
7623 v.command = command;
7624 v.x = x;
7625 v.y = y;
7626 v.controls.left.clear();
7627 v.controls.right.clear();
7628
7629 if (v.command === Two.Commands.curve) {
7630 amp = ir * step / Math.PI;
7631 v.controls.left.x = amp * Math.cos(theta + HALF_PI);
7632 v.controls.left.y = amp * Math.sin(theta + HALF_PI);
7633 v.controls.right.x = amp * Math.cos(theta - HALF_PI);
7634 v.controls.right.y = amp * Math.sin(theta - HALF_PI);
7635 if (i === 1) {
7636 v.controls.left.multiplyScalar(2);
7637 }
7638 if (i === last) {
7639 v.controls.right.multiplyScalar(2);
7640 }
7641 }
7642
7643 id++;
7644
7645 }
7646
7647 } else if (!connected) {
7648
7649 vertices[id].command = Two.Commands.line;
7650 vertices[id].x = 0;
7651 vertices[id].y = 0;
7652 id++;
7653
7654 }
7655
7656 /**
7657 * Final Point
7658 */
7659 vertices[id].command = Two.Commands.close;
7660
7661 }
7662
7663 Path.prototype._update.call(this);
7664
7665 return this;
7666
7667 },
7668
7669 flagReset: function () {
7670
7671 Path.prototype.flagReset.call(this);
7672
7673 this._flagStartAngle = this._flagEndAngle
7674 = this._flagInnerRadius = this._flagOuterRadius = false;
7675
7676 return this;
7677
7678 }
7679
7680 });
7681
7682 ArcSegment.MakeObservable(ArcSegment.prototype);
7683
7684 function mod(v, l) {
7685 while (v < 0) {
7686 v += l;
7687 }
7688 return v % l;
7689 }
7690
7691})((typeof global !== 'undefined' ? global : this).Two);
7692
7693(function (Two) {
7694
7695 var Path = Two.Path, TWO_PI = Math.PI * 2, cos = Math.cos, sin = Math.sin;
7696 var _ = Two.Utils;
7697
7698 var Star = Two.Star = function (ox, oy, or, ir, sides) {
7699
7700 if (!_.isNumber(ir)) {
7701 ir = or / 2;
7702 }
7703
7704 if (!_.isNumber(sides) || sides <= 0) {
7705 sides = 5;
7706 }
7707
7708 var length = sides * 2;
7709
7710 var points = _.map(_.range(length), function (i) {
7711 return new Two.Anchor();
7712 });
7713
7714 Path.call(this, points, true);
7715
7716 this.innerRadius = ir;
7717 this.outerRadius = or;
7718 this.sides = sides;
7719
7720 this._update();
7721 this.translation.set(ox, oy);
7722
7723 };
7724
7725 _.extend(Star, {
7726
7727 Properties: ['innerRadius', 'outerRadius', 'sides'],
7728
7729 MakeObservable: function (obj) {
7730
7731 Path.MakeObservable(obj);
7732 _.each(Star.Properties, Two.Utils.defineProperty, obj);
7733
7734 }
7735
7736 });
7737
7738 _.extend(Star.prototype, Path.prototype, {
7739
7740 _innerRadius: 0,
7741 _outerRadius: 0,
7742 _sides: 0,
7743
7744 _flagInnerRadius: false,
7745 _flagOuterRadius: false,
7746 _flagSides: false,
7747
7748 _update: function () {
7749
7750 if (this._flagInnerRadius || this._flagOuterRadius || this._flagSides) {
7751
7752 var sides = this._sides * 2;
7753 var amount = this.vertices.length;
7754
7755 if (amount > sides) {
7756 this.vertices.splice(sides - 1, amount - sides);
7757 }
7758
7759 for (var i = 0; i < sides; i++) {
7760
7761 var pct = (i + 0.5) / sides;
7762 var theta = TWO_PI * pct;
7763 var r = (i % 2 ? this._innerRadius : this._outerRadius);
7764 var x = r * cos(theta);
7765 var y = r * sin(theta);
7766
7767 if (i >= amount) {
7768 this.vertices.push(new Two.Anchor(x, y));
7769 } else {
7770 this.vertices[i].set(x, y);
7771 }
7772
7773 }
7774
7775 }
7776
7777 Path.prototype._update.call(this);
7778
7779 return this;
7780
7781 },
7782
7783 flagReset: function () {
7784
7785 this._flagInnerRadius = this._flagOuterRadius = this._flagSides = false;
7786 Path.prototype.flagReset.call(this);
7787
7788 return this;
7789
7790 }
7791
7792 });
7793
7794 Star.MakeObservable(Star.prototype);
7795
7796})((typeof global !== 'undefined' ? global : this).Two);
7797
7798(function (Two) {
7799
7800 var Path = Two.Path;
7801 var _ = Two.Utils;
7802
7803 var RoundedRectangle = Two.RoundedRectangle = function (ox, oy, width, height, radius) {
7804
7805 if (!_.isNumber(radius)) {
7806 radius = Math.floor(Math.min(width, height) / 12);
7807 }
7808
7809 var amount = 10;
7810
7811 var points = _.map(_.range(amount), function (i) {
7812 return new Two.Anchor(0, 0, 0, 0, 0, 0,
7813 i === 0 ? Two.Commands.move : Two.Commands.curve);
7814 });
7815
7816 points[points.length - 1].command = Two.Commands.close;
7817
7818 Path.call(this, points, false, false, true);
7819
7820 this.width = width;
7821 this.height = height;
7822 this.radius = radius;
7823
7824 this._update();
7825 this.translation.set(ox, oy);
7826
7827 };
7828
7829 _.extend(RoundedRectangle, {
7830
7831 Properties: ['width', 'height', 'radius'],
7832
7833 MakeObservable: function (obj) {
7834
7835 Path.MakeObservable(obj);
7836 _.each(RoundedRectangle.Properties, Two.Utils.defineProperty, obj);
7837
7838 }
7839
7840 });
7841
7842 _.extend(RoundedRectangle.prototype, Path.prototype, {
7843
7844 _width: 0,
7845 _height: 0,
7846 _radius: 0,
7847
7848 _flagWidth: false,
7849 _flagHeight: false,
7850 _flagRadius: false,
7851
7852 _update: function () {
7853
7854 if (this._flagWidth || this._flagHeight || this._flagRadius) {
7855
7856 var width = this._width;
7857 var height = this._height;
7858 var radius = Math.min(Math.max(this._radius, 0),
7859 Math.min(width, height));
7860
7861 var v;
7862 var w = width / 2;
7863 var h = height / 2;
7864
7865 v = this.vertices[0];
7866 v.x = - (w - radius);
7867 v.y = - h;
7868
7869 // Upper Right Corner
7870
7871 v = this.vertices[1];
7872 v.x = (w - radius);
7873 v.y = - h;
7874 v.controls.left.clear();
7875 v.controls.right.x = radius;
7876 v.controls.right.y = 0;
7877
7878 v = this.vertices[2];
7879 v.x = w;
7880 v.y = - (h - radius);
7881 v.controls.right.clear();
7882 v.controls.left.clear();
7883
7884 // Bottom Right Corner
7885
7886 v = this.vertices[3];
7887 v.x = w;
7888 v.y = (h - radius);
7889 v.controls.left.clear();
7890 v.controls.right.x = 0;
7891 v.controls.right.y = radius;
7892
7893 v = this.vertices[4];
7894 v.x = (w - radius);
7895 v.y = h;
7896 v.controls.right.clear();
7897 v.controls.left.clear();
7898
7899 // Bottom Left Corner
7900
7901 v = this.vertices[5];
7902 v.x = - (w - radius);
7903 v.y = h;
7904 v.controls.left.clear();
7905 v.controls.right.x = - radius;
7906 v.controls.right.y = 0;
7907
7908 v = this.vertices[6];
7909 v.x = - w;
7910 v.y = (h - radius);
7911 v.controls.left.clear();
7912 v.controls.right.clear();
7913
7914 // Upper Left Corner
7915
7916 v = this.vertices[7];
7917 v.x = - w;
7918 v.y = - (h - radius);
7919 v.controls.left.clear();
7920 v.controls.right.x = 0;
7921 v.controls.right.y = - radius;
7922
7923 v = this.vertices[8];
7924 v.x = - (w - radius);
7925 v.y = - h;
7926 v.controls.left.clear();
7927 v.controls.right.clear();
7928
7929 v = this.vertices[9];
7930 v.copy(this.vertices[8]);
7931
7932 }
7933
7934 Path.prototype._update.call(this);
7935
7936 return this;
7937
7938 },
7939
7940 flagReset: function () {
7941
7942 this._flagWidth = this._flagHeight = this._flagRadius = false;
7943 Path.prototype.flagReset.call(this);
7944
7945 return this;
7946
7947 }
7948
7949 });
7950
7951 RoundedRectangle.MakeObservable(RoundedRectangle.prototype);
7952
7953})((typeof global !== 'undefined' ? global : this).Two);
7954
7955(function (Two) {
7956
7957 var root = Two.root;
7958 var getComputedMatrix = Two.Utils.getComputedMatrix;
7959 var _ = Two.Utils;
7960
7961 var canvas = (root.document ? root.document.createElement('canvas') : { getContext: _.identity });
7962 var ctx = canvas.getContext('2d');
7963
7964 var Text = Two.Text = function (message, x, y, styles) {
7965
7966 Two.Shape.call(this);
7967
7968 this._renderer.type = 'text';
7969 this._renderer.flagFill = _.bind(Text.FlagFill, this);
7970 this._renderer.flagStroke = _.bind(Text.FlagStroke, this);
7971
7972 this.value = message;
7973
7974 if (_.isNumber(x)) {
7975 this.translation.x = x;
7976 }
7977 if (_.isNumber(y)) {
7978 this.translation.y = y;
7979 }
7980
7981 if (!_.isObject(styles)) {
7982 return this;
7983 }
7984
7985 _.each(Two.Text.Properties, function (property) {
7986
7987 if (property in styles) {
7988 this[property] = styles[property];
7989 }
7990
7991 }, this);
7992
7993 };
7994
7995 _.extend(Two.Text, {
7996
7997 Properties: [
7998 'value', 'family', 'size', 'leading', 'alignment', 'linewidth', 'style',
7999 'weight', 'decoration', 'baseline', 'opacity', 'visible', 'fill', 'stroke'
8000 ],
8001
8002 FlagFill: function () {
8003 this._flagFill = true;
8004 },
8005
8006 FlagStroke: function () {
8007 this._flagStroke = true;
8008 },
8009
8010 MakeObservable: function (object) {
8011
8012 Two.Shape.MakeObservable(object);
8013
8014 _.each(Two.Text.Properties.slice(0, 12), Two.Utils.defineProperty, object);
8015
8016 Object.defineProperty(object, 'fill', {
8017 enumerable: true,
8018 get: function () {
8019 return this._fill;
8020 },
8021 set: function (f) {
8022
8023 if (this._fill instanceof Two.Gradient
8024 || this._fill instanceof Two.LinearGradient
8025 || this._fill instanceof Two.RadialGradient
8026 || this._fill instanceof Two.Texture) {
8027 this._fill.unbind(Two.Events.change, this._renderer.flagFill);
8028 }
8029
8030 this._fill = f;
8031 this._flagFill = true;
8032
8033 if (this._fill instanceof Two.Gradient
8034 || this._fill instanceof Two.LinearGradient
8035 || this._fill instanceof Two.RadialGradient
8036 || this._fill instanceof Two.Texture) {
8037 this._fill.bind(Two.Events.change, this._renderer.flagFill);
8038 }
8039
8040 }
8041 });
8042
8043 Object.defineProperty(object, 'stroke', {
8044 enumerable: true,
8045 get: function () {
8046 return this._stroke;
8047 },
8048 set: function (f) {
8049
8050 if (this._stroke instanceof Two.Gradient
8051 || this._stroke instanceof Two.LinearGradient
8052 || this._stroke instanceof Two.RadialGradient
8053 || this._stroke instanceof Two.Texture) {
8054 this._stroke.unbind(Two.Events.change, this._renderer.flagStroke);
8055 }
8056
8057 this._stroke = f;
8058 this._flagStroke = true;
8059
8060 if (this._stroke instanceof Two.Gradient
8061 || this._stroke instanceof Two.LinearGradient
8062 || this._stroke instanceof Two.RadialGradient
8063 || this._stroke instanceof Two.Texture) {
8064 this._stroke.bind(Two.Events.change, this._renderer.flagStroke);
8065 }
8066
8067 }
8068 });
8069
8070 Object.defineProperty(object, 'clip', {
8071 enumerable: true,
8072 get: function () {
8073 return this._clip;
8074 },
8075 set: function (v) {
8076 this._clip = v;
8077 this._flagClip = true;
8078 }
8079 });
8080
8081 }
8082
8083 });
8084
8085 _.extend(Two.Text.prototype, Two.Shape.prototype, {
8086
8087 // Flags
8088 // http://en.wikipedia.org/wiki/Flag
8089
8090 _flagValue: true,
8091 _flagFamily: true,
8092 _flagSize: true,
8093 _flagLeading: true,
8094 _flagAlignment: true,
8095 _flagBaseline: true,
8096 _flagStyle: true,
8097 _flagWeight: true,
8098 _flagDecoration: true,
8099
8100 _flagFill: true,
8101 _flagStroke: true,
8102 _flagLinewidth: true,
8103 _flagOpacity: true,
8104 _flagVisible: true,
8105
8106 _flagClip: false,
8107
8108 // Underlying Properties
8109
8110 _value: '',
8111 _family: 'sans-serif',
8112 _size: 13,
8113 _leading: 17,
8114 _alignment: 'center',
8115 _baseline: 'middle',
8116 _style: 'normal',
8117 _weight: 500,
8118 _decoration: 'none',
8119
8120 _fill: '#000',
8121 _stroke: 'transparent',
8122 _linewidth: 1,
8123 _opacity: 1,
8124 _visible: true,
8125
8126 _clip: false,
8127
8128 remove: function () {
8129
8130 if (!this.parent) {
8131 return this;
8132 }
8133
8134 this.parent.remove(this);
8135
8136 return this;
8137
8138 },
8139
8140 clone: function (parent) {
8141
8142 var parent = parent || this.parent;
8143
8144 var clone = new Two.Text(this.value);
8145 clone.translation.copy(this.translation);
8146 clone.rotation = this.rotation;
8147 clone.scale = this.scale;
8148
8149 _.each(Two.Text.Properties, function (property) {
8150 clone[property] = this[property];
8151 }, this);
8152
8153 if (parent) {
8154 parent.add(clone);
8155 }
8156
8157 return clone;
8158
8159 },
8160
8161 toObject: function () {
8162
8163 var result = {
8164 translation: this.translation.toObject(),
8165 rotation: this.rotation,
8166 scale: this.scale
8167 };
8168
8169 _.each(Two.Text.Properties, function (property) {
8170 result[property] = this[property];
8171 }, this);
8172
8173 return result;
8174
8175 },
8176
8177 noStroke: function () {
8178 this.stroke = 'transparent';
8179 return this;
8180 },
8181
8182 noFill: function () {
8183 this.fill = 'transparent';
8184 return this;
8185 },
8186
8187 /**
8188 * A shim to not break `getBoundingClientRect` calls. TODO: Implement a
8189 * way to calculate proper bounding boxes of `Two.Text`.
8190 */
8191 getBoundingClientRect: function (shallow) {
8192
8193 var matrix, border, l, x, y, i, v;
8194
8195 var left = Infinity, right = -Infinity,
8196 top = Infinity, bottom = -Infinity;
8197
8198 // TODO: Update this to not __always__ update. Just when it needs to.
8199 this._update(true);
8200
8201 matrix = !!shallow ? this._matrix : getComputedMatrix(this);
8202
8203 v = matrix.multiply(0, 0, 1);
8204
8205 return {
8206 top: v.x,
8207 left: v.y,
8208 right: v.x,
8209 bottom: v.y,
8210 width: 0,
8211 height: 0
8212 };
8213
8214 },
8215
8216 flagReset: function () {
8217
8218 this._flagValue = this._flagFamily = this._flagSize =
8219 this._flagLeading = this._flagAlignment = this._flagFill =
8220 this._flagStroke = this._flagLinewidth = this._flagOpaicty =
8221 this._flagVisible = this._flagClip = this._flagDecoration =
8222 this._flagBaseline = false;
8223
8224 Two.Shape.prototype.flagReset.call(this);
8225
8226 return this;
8227
8228 }
8229
8230 });
8231
8232 Two.Text.MakeObservable(Two.Text.prototype);
8233
8234})((typeof global !== 'undefined' ? global : this).Two);
8235
8236(function (Two) {
8237
8238 var _ = Two.Utils;
8239
8240 var Stop = Two.Stop = function (offset, color, opacity) {
8241
8242 this._renderer = {};
8243 this._renderer.type = 'stop';
8244
8245 this.offset = _.isNumber(offset) ? offset
8246 : Stop.Index <= 0 ? 0 : 1;
8247
8248 this.opacity = _.isNumber(opacity) ? opacity : 1;
8249
8250 this.color = _.isString(color) ? color
8251 : Stop.Index <= 0 ? '#fff' : '#000';
8252
8253 Stop.Index = (Stop.Index + 1) % 2;
8254
8255 };
8256
8257 _.extend(Stop, {
8258
8259 Index: 0,
8260
8261 Properties: [
8262 'offset',
8263 'opacity',
8264 'color'
8265 ],
8266
8267 MakeObservable: function (object) {
8268
8269 _.each(Stop.Properties, function (property) {
8270
8271 var object = this;
8272 var secret = '_' + property;
8273 var flag = '_flag' + property.charAt(0).toUpperCase() + property.slice(1);
8274
8275 Object.defineProperty(object, property, {
8276 enumerable: true,
8277 get: function () {
8278 return this[secret];
8279 },
8280 set: function (v) {
8281 this[secret] = v;
8282 this[flag] = true;
8283 if (this.parent) {
8284 this.parent._flagStops = true;
8285 }
8286 }
8287 });
8288
8289 }, object);
8290
8291 }
8292
8293 });
8294
8295 _.extend(Stop.prototype, Two.Utils.Events, {
8296
8297 clone: function () {
8298
8299 var clone = new Stop();
8300
8301 _.each(Stop.Properties, function (property) {
8302 clone[property] = this[property];
8303 }, this);
8304
8305 return clone;
8306
8307 },
8308
8309 toObject: function () {
8310
8311 var result = {};
8312
8313 _.each(Stop.Properties, function (k) {
8314 result[k] = this[k];
8315 }, this);
8316
8317 return result;
8318
8319 },
8320
8321 flagReset: function () {
8322
8323 this._flagOffset = this._flagColor = this._flagOpacity = false;
8324
8325 return this;
8326
8327 }
8328
8329 });
8330
8331 Stop.MakeObservable(Stop.prototype);
8332
8333 var Gradient = Two.Gradient = function (stops) {
8334
8335 this._renderer = {};
8336 this._renderer.type = 'gradient';
8337
8338 this.id = Two.Identifier + Two.uniqueId();
8339 this.classList = [];
8340
8341 this._renderer.flagStops = _.bind(Gradient.FlagStops, this);
8342 this._renderer.bindStops = _.bind(Gradient.BindStops, this);
8343 this._renderer.unbindStops = _.bind(Gradient.UnbindStops, this);
8344
8345 this.spread = 'pad';
8346
8347 this.stops = stops;
8348
8349 };
8350
8351 _.extend(Gradient, {
8352
8353 Stop: Stop,
8354
8355 Properties: [
8356 'spread'
8357 ],
8358
8359 MakeObservable: function (object) {
8360
8361 _.each(Gradient.Properties, Two.Utils.defineProperty, object);
8362
8363 Object.defineProperty(object, 'stops', {
8364
8365 enumerable: true,
8366
8367 get: function () {
8368 return this._stops;
8369 },
8370
8371 set: function (stops) {
8372
8373 var updateStops = this._renderer.flagStops;
8374 var bindStops = this._renderer.bindStops;
8375 var unbindStops = this._renderer.unbindStops;
8376
8377 // Remove previous listeners
8378 if (this._stops) {
8379 this._stops
8380 .unbind(Two.Events.insert, bindStops)
8381 .unbind(Two.Events.remove, unbindStops);
8382 }
8383
8384 // Create new Collection with copy of Stops
8385 this._stops = new Two.Utils.Collection((stops || []).slice(0));
8386
8387 // Listen for Collection changes and bind / unbind
8388 this._stops
8389 .bind(Two.Events.insert, bindStops)
8390 .bind(Two.Events.remove, unbindStops);
8391
8392 // Bind Initial Stops
8393 bindStops(this._stops);
8394
8395 }
8396
8397 });
8398
8399 },
8400
8401 FlagStops: function () {
8402 this._flagStops = true;
8403 },
8404
8405 BindStops: function (items) {
8406
8407 // This function is called a lot
8408 // when importing a large SVG
8409 var i = items.length;
8410 while (i--) {
8411 items[i].bind(Two.Events.change, this._renderer.flagStops);
8412 items[i].parent = this;
8413 }
8414
8415 this._renderer.flagStops();
8416
8417 },
8418
8419 UnbindStops: function (items) {
8420
8421 var i = items.length;
8422 while (i--) {
8423 items[i].unbind(Two.Events.change, this._renderer.flagStops);
8424 delete items[i].parent;
8425 }
8426
8427 this._renderer.flagStops();
8428
8429 }
8430
8431 });
8432
8433 _.extend(Gradient.prototype, Two.Utils.Events, {
8434
8435 _flagStops: false,
8436 _flagSpread: false,
8437
8438 clone: function (parent) {
8439
8440 parent = parent || this.parent;
8441
8442 var stops = _.map(this.stops, function (s) {
8443 return s.clone();
8444 });
8445
8446 var clone = new Gradient(stops);
8447
8448 _.each(Two.Gradient.Properties, function (k) {
8449 clone[k] = this[k];
8450 }, this);
8451
8452 if (parent) {
8453 parent.add(clone);
8454 }
8455
8456 return clone;
8457
8458 },
8459
8460 toObject: function () {
8461
8462 var result = {
8463 stops: _.map(this.stops, function (s) {
8464 return s.toObject();
8465 })
8466 };
8467
8468 _.each(Gradient.Properties, function (k) {
8469 result[k] = this[k];
8470 }, this);
8471
8472 return result;
8473
8474 },
8475
8476 _update: function () {
8477
8478 if (this._flagSpread || this._flagStops) {
8479 this.trigger(Two.Events.change);
8480 }
8481
8482 return this;
8483
8484 },
8485
8486 flagReset: function () {
8487
8488 this._flagSpread = this._flagStops = false;
8489
8490 return this;
8491
8492 }
8493
8494 });
8495
8496 Gradient.MakeObservable(Gradient.prototype);
8497
8498})((typeof global !== 'undefined' ? global : this).Two);
8499
8500(function (Two) {
8501
8502 var _ = Two.Utils;
8503
8504 var LinearGradient = Two.LinearGradient = function (x1, y1, x2, y2, stops) {
8505
8506 Two.Gradient.call(this, stops);
8507
8508 this._renderer.type = 'linear-gradient';
8509
8510 var flagEndPoints = _.bind(LinearGradient.FlagEndPoints, this);
8511 this.left = new Two.Vector().bind(Two.Events.change, flagEndPoints);
8512 this.right = new Two.Vector().bind(Two.Events.change, flagEndPoints);
8513
8514 if (_.isNumber(x1)) {
8515 this.left.x = x1;
8516 }
8517 if (_.isNumber(y1)) {
8518 this.left.y = y1;
8519 }
8520 if (_.isNumber(x2)) {
8521 this.right.x = x2;
8522 }
8523 if (_.isNumber(y2)) {
8524 this.right.y = y2;
8525 }
8526
8527 };
8528
8529 _.extend(LinearGradient, {
8530
8531 Stop: Two.Gradient.Stop,
8532
8533 MakeObservable: function (object) {
8534 Two.Gradient.MakeObservable(object);
8535 },
8536
8537 FlagEndPoints: function () {
8538 this._flagEndPoints = true;
8539 }
8540
8541 });
8542
8543 _.extend(LinearGradient.prototype, Two.Gradient.prototype, {
8544
8545 _flagEndPoints: false,
8546
8547 clone: function (parent) {
8548
8549 parent = parent || this.parent;
8550
8551 var stops = _.map(this.stops, function (stop) {
8552 return stop.clone();
8553 });
8554
8555 var clone = new LinearGradient(this.left._x, this.left._y,
8556 this.right._x, this.right._y, stops);
8557
8558 _.each(Two.Gradient.Properties, function (k) {
8559 clone[k] = this[k];
8560 }, this);
8561
8562 if (parent) {
8563 parent.add(clone);
8564 }
8565
8566 return clone;
8567
8568 },
8569
8570 toObject: function () {
8571
8572 var result = Two.Gradient.prototype.toObject.call(this);
8573
8574 result.left = this.left.toObject();
8575 result.right = this.right.toObject();
8576
8577 return result;
8578
8579 },
8580
8581 _update: function () {
8582
8583 if (this._flagEndPoints || this._flagSpread || this._flagStops) {
8584 this.trigger(Two.Events.change);
8585 }
8586
8587 return this;
8588
8589 },
8590
8591 flagReset: function () {
8592
8593 this._flagEndPoints = false;
8594
8595 Two.Gradient.prototype.flagReset.call(this);
8596
8597 return this;
8598
8599 }
8600
8601 });
8602
8603 LinearGradient.MakeObservable(LinearGradient.prototype);
8604
8605})((typeof global !== 'undefined' ? global : this).Two);
8606
8607(function (Two) {
8608
8609 var _ = Two.Utils;
8610
8611 var RadialGradient = Two.RadialGradient = function (cx, cy, r, stops, fx, fy) {
8612
8613 Two.Gradient.call(this, stops);
8614
8615 this._renderer.type = 'radial-gradient';
8616
8617 this.center = new Two.Vector()
8618 .bind(Two.Events.change, _.bind(function () {
8619 this._flagCenter = true;
8620 }, this));
8621
8622 this.radius = _.isNumber(r) ? r : 20;
8623
8624 this.focal = new Two.Vector()
8625 .bind(Two.Events.change, _.bind(function () {
8626 this._flagFocal = true;
8627 }, this));
8628
8629 if (_.isNumber(cx)) {
8630 this.center.x = cx;
8631 }
8632 if (_.isNumber(cy)) {
8633 this.center.y = cy;
8634 }
8635
8636 this.focal.copy(this.center);
8637
8638 if (_.isNumber(fx)) {
8639 this.focal.x = fx;
8640 }
8641 if (_.isNumber(fy)) {
8642 this.focal.y = fy;
8643 }
8644
8645 };
8646
8647 _.extend(RadialGradient, {
8648
8649 Stop: Two.Gradient.Stop,
8650
8651 Properties: [
8652 'radius'
8653 ],
8654
8655 MakeObservable: function (object) {
8656
8657 Two.Gradient.MakeObservable(object);
8658
8659 _.each(RadialGradient.Properties, Two.Utils.defineProperty, object);
8660
8661 }
8662
8663 });
8664
8665 _.extend(RadialGradient.prototype, Two.Gradient.prototype, {
8666
8667 _flagRadius: false,
8668 _flagCenter: false,
8669 _flagFocal: false,
8670
8671 clone: function (parent) {
8672
8673 parent = parent || this.parent;
8674
8675 var stops = _.map(this.stops, function (stop) {
8676 return stop.clone();
8677 });
8678
8679 var clone = new RadialGradient(this.center._x, this.center._y,
8680 this._radius, stops, this.focal._x, this.focal._y);
8681
8682 _.each(Two.Gradient.Properties.concat(RadialGradient.Properties), function (k) {
8683 clone[k] = this[k];
8684 }, this);
8685
8686 if (parent) {
8687 parent.add(clone);
8688 }
8689
8690 return clone;
8691
8692 },
8693
8694 toObject: function () {
8695
8696 var result = Two.Gradient.prototype.toObject.call(this);
8697
8698 _.each(RadialGradient.Properties, function (k) {
8699 result[k] = this[k];
8700 }, this);
8701
8702 result.center = this.center.toObject();
8703 result.focal = this.focal.toObject();
8704
8705 return result;
8706
8707 },
8708
8709 _update: function () {
8710
8711 if (this._flagRadius || this._flatCenter || this._flagFocal
8712 || this._flagSpread || this._flagStops) {
8713 this.trigger(Two.Events.change);
8714 }
8715
8716 return this;
8717
8718 },
8719
8720 flagReset: function () {
8721
8722 this._flagRadius = this._flagCenter = this._flagFocal = false;
8723
8724 Two.Gradient.prototype.flagReset.call(this);
8725
8726 return this;
8727
8728 }
8729
8730 });
8731
8732 RadialGradient.MakeObservable(RadialGradient.prototype);
8733
8734})((typeof global !== 'undefined' ? global : this).Two);
8735
8736(function (Two) {
8737
8738 var _ = Two.Utils;
8739 var anchor;
8740 var regex = {
8741 video: /\.(mp4|webm)$/i,
8742 image: /\.(jpe?g|png|gif|tiff)$/i
8743 };
8744
8745 if (this.document) {
8746 anchor = document.createElement('a');
8747 }
8748
8749 var Texture = Two.Texture = function (src, callback) {
8750
8751 this._renderer = {};
8752 this._renderer.type = 'texture';
8753 this._renderer.flagOffset = _.bind(Texture.FlagOffset, this);
8754 this._renderer.flagScale = _.bind(Texture.FlagScale, this);
8755
8756 this.id = Two.Identifier + Two.uniqueId();
8757 this.classList = [];
8758
8759 this.offset = new Two.Vector();
8760
8761 if (_.isFunction(callback)) {
8762 var loaded = _.bind(function () {
8763 this.unbind(Two.Events.load, loaded);
8764 if (_.isFunction(callback)) {
8765 callback();
8766 }
8767 }, this);
8768 this.bind(Two.Events.load, loaded);
8769 }
8770
8771 if (_.isString(src)) {
8772 this.src = src;
8773 } else if (_.isElement(src)) {
8774 this.image = src;
8775 }
8776
8777 this._update();
8778
8779 };
8780
8781 _.extend(Texture, {
8782
8783 Properties: [
8784 'src',
8785 'loaded',
8786 'repeat'
8787 ],
8788
8789 ImageRegistry: new Two.Registry(),
8790
8791 getAbsoluteURL: function (path) {
8792 if (!anchor) {
8793 // TODO: Fix for headless environment
8794 return path;
8795 }
8796 anchor.href = path;
8797 return anchor.href;
8798 },
8799
8800 getImage: function (src) {
8801
8802 var absoluteSrc = Texture.getAbsoluteURL(src);
8803
8804 if (Texture.ImageRegistry.contains(absoluteSrc)) {
8805 return Texture.ImageRegistry.get(absoluteSrc);
8806 }
8807
8808 var image;
8809
8810 if (regex.video.test(absoluteSrc)) {
8811 image = document.createElement('video');
8812 } else {
8813 image = document.createElement('img');
8814 }
8815
8816 image.crossOrigin = 'anonymous';
8817
8818 return image;
8819
8820 },
8821
8822 Register: {
8823 canvas: function (texture, callback) {
8824 texture._src = '#' + texture.id;
8825 Texture.ImageRegistry.add(texture.src, texture.image);
8826 if (_.isFunction(callback)) {
8827 callback();
8828 }
8829 },
8830 img: function (texture, callback) {
8831
8832 var loaded = function (e) {
8833 texture.image.removeEventListener('load', loaded, false);
8834 texture.image.removeEventListener('error', error, false);
8835 if (_.isFunction(callback)) {
8836 callback();
8837 }
8838 };
8839 var error = function (e) {
8840 texture.image.removeEventListener('load', loaded, false);
8841 texture.image.removeEventListener('error', error, false);
8842 throw new Two.Utils.Error('unable to load ' + texture.src);
8843 };
8844
8845 if (_.isNumber(texture.image.width) && texture.image.width > 0
8846 && _.isNumber(texture.image.height) && texture.image.height > 0) {
8847 loaded();
8848 } else {
8849 texture.image.addEventListener('load', loaded, false);
8850 texture.image.addEventListener('error', error, false);
8851 }
8852
8853 texture._src = Texture.getAbsoluteURL(texture._src);
8854
8855 if (texture.image && texture.image.getAttribute('two-src')) {
8856 return;
8857 }
8858
8859 texture.image.setAttribute('two-src', texture.src);
8860 Texture.ImageRegistry.add(texture.src, texture.image);
8861 texture.image.src = texture.src;
8862
8863 },
8864 video: function (texture, callback) {
8865
8866 var loaded = function (e) {
8867 texture.image.removeEventListener('load', loaded, false);
8868 texture.image.removeEventListener('error', error, false);
8869 texture.image.width = texture.image.videoWidth;
8870 texture.image.height = texture.image.videoHeight;
8871 texture.image.play();
8872 if (_.isFunction(callback)) {
8873 callback();
8874 }
8875 };
8876 var error = function (e) {
8877 texture.image.removeEventListener('load', loaded, false);
8878 texture.image.removeEventListener('error', error, false);
8879 throw new Two.Utils.Error('unable to load ' + texture.src);
8880 };
8881
8882 texture._src = Texture.getAbsoluteURL(texture._src);
8883 texture.image.addEventListener('canplaythrough', loaded, false);
8884 texture.image.addEventListener('error', error, false);
8885
8886 if (texture.image && texture.image.getAttribute('two-src')) {
8887 return;
8888 }
8889
8890 texture.image.setAttribute('two-src', texture.src);
8891 Texture.ImageRegistry.add(texture.src, texture.image);
8892 texture.image.src = texture.src;
8893 texture.image.loop = true;
8894 texture.image.load();
8895
8896 }
8897 },
8898
8899 load: function (texture, callback) {
8900
8901 var src = texture.src;
8902 var image = texture.image;
8903 var tag = image && image.nodeName.toLowerCase();
8904
8905 if (texture._flagImage) {
8906 if (/canvas/i.test(tag)) {
8907 Texture.Register.canvas(texture, callback);
8908 } else {
8909 texture._src = image.getAttribute('two-src') || image.src;
8910 Texture.Register[tag](texture, callback);
8911 }
8912 }
8913
8914 if (texture._flagSrc) {
8915 if (!image) {
8916 texture.image = Texture.getImage(texture.src);
8917 }
8918 tag = texture.image.nodeName.toLowerCase();
8919 Texture.Register[tag](texture, callback);
8920 }
8921
8922 },
8923
8924 FlagOffset: function () {
8925 this._flagOffset = true;
8926 },
8927
8928 FlagScale: function () {
8929 this._flagScale = true;
8930 },
8931
8932 MakeObservable: function (object) {
8933
8934 _.each(Texture.Properties, Two.Utils.defineProperty, object);
8935
8936 Object.defineProperty(object, 'image', {
8937 enumerable: true,
8938 get: function () {
8939 return this._image;
8940 },
8941 set: function (image) {
8942
8943 var tag = image && image.nodeName.toLowerCase();
8944 var index;
8945
8946 switch (tag) {
8947 case 'canvas':
8948 index = '#' + image.id;
8949 break;
8950 default:
8951 index = image.src;
8952 }
8953
8954 if (Texture.ImageRegistry.contains(index)) {
8955 this._image = Texture.ImageRegistry.get(image.src);
8956 } else {
8957 this._image = image;
8958 }
8959
8960 this._flagImage = true;
8961
8962 }
8963
8964 });
8965
8966 Object.defineProperty(object, 'offset', {
8967 enumerable: true,
8968 get: function () {
8969 return this._offset;
8970 },
8971 set: function (v) {
8972 if (this._offset) {
8973 this._offset.unbind(Two.Events.change, this._renderer.flagOffset);
8974 }
8975 this._offset = v;
8976 this._offset.bind(Two.Events.change, this._renderer.flagOffset);
8977 this._flagOffset = true;
8978 }
8979 });
8980
8981 Object.defineProperty(object, 'scale', {
8982 enumerable: true,
8983 get: function () {
8984 return this._scale;
8985 },
8986 set: function (v) {
8987
8988 if (this._scale instanceof Two.Vector) {
8989 this._scale.unbind(Two.Events.change, this._renderer.flagScale);
8990 }
8991
8992 this._scale = v;
8993
8994 if (this._scale instanceof Two.Vector) {
8995 this._scale.bind(Two.Events.change, this._renderer.flagScale);
8996 }
8997
8998 this._flagScale = true;
8999
9000 }
9001 });
9002
9003 }
9004
9005 });
9006
9007 _.extend(Texture.prototype, Two.Utils.Events, Two.Shape.prototype, {
9008
9009 _flagSrc: false,
9010 _flagImage: false,
9011 _flagVideo: false,
9012 _flagLoaded: false,
9013 _flagRepeat: false,
9014
9015 _flagOffset: false,
9016 _flagScale: false,
9017
9018 _src: '',
9019 _image: null,
9020 _loaded: false,
9021 _repeat: 'no-repeat',
9022
9023 _scale: 1,
9024 _offset: null,
9025
9026 clone: function () {
9027 return new Texture(this.src);
9028 },
9029
9030 toObject: function () {
9031 return {
9032 src: this.src,
9033 image: this.image
9034 }
9035 },
9036
9037 _update: function () {
9038
9039 if (this._flagSrc || this._flagImage || this._flagVideo) {
9040
9041 this.trigger(Two.Events.change);
9042
9043 if (this._flagSrc || this._flagImage) {
9044 this.loaded = false;
9045 Texture.load(this, _.bind(function () {
9046 this.loaded = true;
9047 this
9048 .trigger(Two.Events.change)
9049 .trigger(Two.Events.load);
9050 }, this));
9051 }
9052
9053 }
9054
9055 if (this._image && this._image.readyState >= 4) {
9056 this._flagVideo = true;
9057 }
9058
9059 return this;
9060
9061 },
9062
9063 flagReset: function () {
9064
9065 this._flagSrc = this._flagImage = this._flagLoaded
9066 = this._flagVideo = this._flagScale = this._flagOffset = false;
9067
9068 return this;
9069
9070 }
9071
9072 });
9073
9074 Texture.MakeObservable(Texture.prototype);
9075
9076})((typeof global !== 'undefined' ? global : this).Two);
9077
9078(function (Two) {
9079
9080 var _ = Two.Utils;
9081 var Path = Two.Path;
9082 var Rectangle = Two.Rectangle;
9083
9084 var Sprite = Two.Sprite = function (path, ox, oy, cols, rows, frameRate) {
9085
9086 Path.call(this, [
9087 new Two.Anchor(),
9088 new Two.Anchor(),
9089 new Two.Anchor(),
9090 new Two.Anchor()
9091 ], true);
9092
9093 this.noStroke();
9094 this.noFill();
9095
9096 if (path instanceof Two.Texture) {
9097 this.texture = path;
9098 } else if (_.isString(path)) {
9099 this.texture = new Two.Texture(path);
9100 }
9101
9102 this._update();
9103 this.translation.set(ox || 0, oy || 0);
9104
9105 if (_.isNumber(cols)) {
9106 this.columns = cols;
9107 }
9108 if (_.isNumber(rows)) {
9109 this.rows = rows;
9110 }
9111 if (_.isNumber(frameRate)) {
9112 this.frameRate = frameRate;
9113 }
9114
9115 };
9116
9117 _.extend(Sprite, {
9118
9119 Properties: [
9120 'texture', 'columns', 'rows', 'frameRate', 'index'
9121 ],
9122
9123 MakeObservable: function (obj) {
9124
9125 Rectangle.MakeObservable(obj);
9126 _.each(Sprite.Properties, Two.Utils.defineProperty, obj);
9127
9128 }
9129
9130 })
9131
9132 _.extend(Sprite.prototype, Rectangle.prototype, {
9133
9134 _flagTexture: false,
9135 _flagColumns: false,
9136 _flagRows: false,
9137 _flagFrameRate: false,
9138 flagIndex: false,
9139
9140 // Private variables
9141 _amount: 1,
9142 _duration: 0,
9143 _startTime: 0,
9144 _playing: false,
9145 _firstFrame: 0,
9146 _lastFrame: 0,
9147 _loop: true,
9148
9149 // Exposed through getter-setter
9150 _texture: null,
9151 _columns: 1,
9152 _rows: 1,
9153 _frameRate: 0,
9154 _index: 0,
9155
9156 play: function (firstFrame, lastFrame, onLastFrame) {
9157
9158 this._playing = true;
9159 this._firstFrame = 0;
9160 this._lastFrame = this.amount - 1;
9161 this._startTime = _.performance.now();
9162
9163 if (_.isNumber(firstFrame)) {
9164 this._firstFrame = firstFrame;
9165 }
9166 if (_.isNumber(lastFrame)) {
9167 this._lastFrame = lastFrame;
9168 }
9169 if (_.isFunction(onLastFrame)) {
9170 this._onLastFrame = onLastFrame;
9171 } else {
9172 delete this._onLastFrame;
9173 }
9174
9175 if (this._index !== this._firstFrame) {
9176 this._startTime -= 1000 * Math.abs(this._index - this._firstFrame)
9177 / this._frameRate;
9178 }
9179
9180 return this;
9181
9182 },
9183
9184 pause: function () {
9185
9186 this._playing = false;
9187 return this;
9188
9189 },
9190
9191 stop: function () {
9192
9193 this._playing = false;
9194 this._index = 0;
9195
9196 return this;
9197
9198 },
9199
9200 clone: function (parent) {
9201
9202 parent = parent || this.parent;
9203
9204 var clone = new Sprite(
9205 this.texture, this.translation.x, this.translation.y,
9206 this.columns, this.rows, this.frameRate
9207 );
9208
9209 if (this.playing) {
9210 clone.play(this._firstFrame, this._lastFrame);
9211 clone._loop = this._loop;
9212 }
9213
9214 if (parent) {
9215 parent.add(clone);
9216 }
9217
9218 return clone;
9219
9220 },
9221
9222 _update: function () {
9223
9224 var effect = this._texture;
9225 var cols = this._columns;
9226 var rows = this._rows;
9227
9228 var width, height, elapsed, amount, duration;
9229 var index, iw, ih, isRange, frames;
9230
9231 if (this._flagColumns || this._flagRows) {
9232 this._amount = this._columns * this._rows;
9233 }
9234
9235 if (this._flagFrameRate) {
9236 this._duration = 1000 * this._amount / this._frameRate;
9237 }
9238
9239 if (this._flagTexture) {
9240 this.fill = this._texture;
9241 }
9242
9243 if (this._texture.loaded) {
9244
9245 iw = effect.image.width;
9246 ih = effect.image.height;
9247
9248 width = iw / cols;
9249 height = ih / rows;
9250 amount = this._amount;
9251
9252 if (this.width !== width) {
9253 this.width = width;
9254 }
9255 if (this.height !== height) {
9256 this.height = height;
9257 }
9258
9259 if (this._playing && this._frameRate > 0) {
9260
9261 if (_.isNaN(this._lastFrame)) {
9262 this._lastFrame = amount - 1;
9263 }
9264
9265 // TODO: Offload perf logic to instance of `Two`.
9266 elapsed = _.performance.now() - this._startTime;
9267 frames = this._lastFrame + 1;
9268 duration = 1000 * (frames - this._firstFrame) / this._frameRate;
9269
9270 if (this._loop) {
9271 elapsed = elapsed % duration;
9272 } else {
9273 elapsed = Math.min(elapsed, duration);
9274 }
9275
9276 index = _.lerp(this._firstFrame, frames, elapsed / duration);
9277 index = Math.floor(index);
9278
9279 if (index !== this._index) {
9280 this._index = index;
9281 if (index >= this._lastFrame - 1 && this._onLastFrame) {
9282 this._onLastFrame(); // Shortcut for chainable sprite animations
9283 }
9284 }
9285
9286 }
9287
9288 var col = this._index % cols;
9289 var row = Math.floor(this._index / cols);
9290
9291 var ox = - width * col + (iw - width) / 2;
9292 var oy = - height * row + (ih - height) / 2;
9293
9294 // TODO: Improve performance
9295 if (ox !== effect.offset.x) {
9296 effect.offset.x = ox;
9297 }
9298 if (oy !== effect.offset.y) {
9299 effect.offset.y = oy;
9300 }
9301
9302 }
9303
9304 Rectangle.prototype._update.call(this);
9305
9306 return this;
9307
9308 },
9309
9310 flagReset: function () {
9311
9312 this._flagTexture = this._flagColumns = this._flagRows
9313 = this._flagFrameRate = false;
9314
9315 Rectangle.prototype.flagReset.call(this);
9316
9317 return this;
9318 }
9319
9320
9321 });
9322
9323 Sprite.MakeObservable(Sprite.prototype);
9324
9325})((typeof global !== 'undefined' ? global : this).Two);
9326
9327(function (Two) {
9328
9329 var _ = Two.Utils;
9330 var Path = Two.Path;
9331 var Rectangle = Two.Rectangle;
9332
9333 var ImageSequence = Two.ImageSequence = function (paths, ox, oy, frameRate) {
9334
9335 Path.call(this, [
9336 new Two.Anchor(),
9337 new Two.Anchor(),
9338 new Two.Anchor(),
9339 new Two.Anchor()
9340 ], true);
9341
9342 this._renderer.flagTextures = _.bind(ImageSequence.FlagTextures, this);
9343 this._renderer.bindTextures = _.bind(ImageSequence.BindTextures, this);
9344 this._renderer.unbindTextures = _.bind(ImageSequence.UnbindTextures, this);
9345
9346 this.noStroke();
9347 this.noFill();
9348
9349 this.textures = _.map(paths, ImageSequence.GenerateTexture, this);
9350
9351 this._update();
9352 this.translation.set(ox || 0, oy || 0);
9353
9354 if (_.isNumber(frameRate)) {
9355 this.frameRate = frameRate;
9356 } else {
9357 this.frameRate = ImageSequence.DefaultFrameRate;
9358 }
9359
9360 };
9361
9362 _.extend(ImageSequence, {
9363
9364 Properties: [
9365 'frameRate',
9366 'index'
9367 ],
9368
9369 DefaultFrameRate: 30,
9370
9371 FlagTextures: function () {
9372 this._flagTextures = true;
9373 },
9374
9375 BindTextures: function (items) {
9376
9377 var i = items.length;
9378 while (i--) {
9379 items[i].bind(Two.Events.change, this._renderer.flagTextures);
9380 }
9381
9382 this._renderer.flagTextures();
9383
9384 },
9385
9386 UnbindTextures: function (items) {
9387
9388 var i = items.length;
9389 while (i--) {
9390 items[i].unbind(Two.Events.change, this._renderer.flagTextures);
9391 }
9392
9393 this._renderer.flagTextures();
9394
9395 },
9396
9397 MakeObservable: function (obj) {
9398
9399 Rectangle.MakeObservable(obj);
9400 _.each(ImageSequence.Properties, Two.Utils.defineProperty, obj);
9401
9402 Object.defineProperty(obj, 'textures', {
9403
9404 enumerable: true,
9405
9406 get: function () {
9407 return this._textures;
9408 },
9409
9410 set: function (textures) {
9411
9412 var updateTextures = this._renderer.flagTextures;
9413 var bindTextures = this._renderer.bindTextures;
9414 var unbindTextures = this._renderer.unbindTextures;
9415
9416 // Remove previous listeners
9417 if (this._textures) {
9418 this._textures
9419 .unbind(Two.Events.insert, bindTextures)
9420 .unbind(Two.Events.remove, unbindTextures);
9421 }
9422
9423 // Create new Collection with copy of vertices
9424 this._textures = new Two.Utils.Collection((textures || []).slice(0));
9425
9426 // Listen for Collection changes and bind / unbind
9427 this._textures
9428 .bind(Two.Events.insert, bindTextures)
9429 .bind(Two.Events.remove, unbindTextures);
9430
9431 // Bind Initial Textures
9432 bindTextures(this._textures);
9433
9434 }
9435
9436 });
9437
9438 },
9439
9440 GenerateTexture: function (obj) {
9441 if (obj instanceof Two.Texture) {
9442 return obj;
9443 } else if (_.isString(obj)) {
9444 return new Two.Texture(obj);
9445 }
9446 }
9447
9448 });
9449
9450 _.extend(ImageSequence.prototype, Rectangle.prototype, {
9451
9452 _flagTextures: false,
9453 _flagFrameRate: false,
9454 _flagIndex: false,
9455
9456 // Private variables
9457 _amount: 1,
9458 _duration: 0,
9459 _index: 0,
9460 _startTime: 0,
9461 _playing: false,
9462 _firstFrame: 0,
9463 _lastFrame: 0,
9464 _loop: true,
9465
9466 // Exposed through getter-setter
9467 _textures: null,
9468 _frameRate: 0,
9469
9470 play: function (firstFrame, lastFrame, onLastFrame) {
9471
9472 this._playing = true;
9473 this._firstFrame = 0;
9474 this._lastFrame = this.amount - 1;
9475 this._startTime = _.performance.now();
9476
9477 if (_.isNumber(firstFrame)) {
9478 this._firstFrame = firstFrame;
9479 }
9480 if (_.isNumber(lastFrame)) {
9481 this._lastFrame = lastFrame;
9482 }
9483 if (_.isFunction(onLastFrame)) {
9484 this._onLastFrame = onLastFrame;
9485 } else {
9486 delete this._onLastFrame;
9487 }
9488
9489 if (this._index !== this._firstFrame) {
9490 this._startTime -= 1000 * Math.abs(this._index - this._firstFrame)
9491 / this._frameRate;
9492 }
9493
9494 return this;
9495
9496 },
9497
9498 pause: function () {
9499
9500 this._playing = false;
9501 return this;
9502
9503 },
9504
9505 stop: function () {
9506
9507 this._playing = false;
9508 this._index = 0;
9509
9510 return this;
9511
9512 },
9513
9514 clone: function (parent) {
9515
9516 parent = parent || this.parent;
9517
9518 var clone = new ImageSequence(this.textures, this.translation.x,
9519 this.translation.y, this.frameRate)
9520
9521 clone._loop = this._loop;
9522
9523 if (this._playing) {
9524 clone.play();
9525 }
9526
9527 if (parent) {
9528 parent.add(clone);
9529 }
9530
9531 return clone;
9532
9533 },
9534
9535 _update: function () {
9536
9537 var effects = this._textures;
9538 var width, height, elapsed, amount, duration, texture;
9539 var index, frames;
9540
9541 if (this._flagTextures) {
9542 this._amount = effects.length;
9543 }
9544
9545 if (this._flagFrameRate) {
9546 this._duration = 1000 * this._amount / this._frameRate;
9547 }
9548
9549 if (this._playing && this._frameRate > 0) {
9550
9551 amount = this._amount;
9552
9553 if (_.isNaN(this._lastFrame)) {
9554 this._lastFrame = amount - 1;
9555 }
9556
9557 // TODO: Offload perf logic to instance of `Two`.
9558 elapsed = _.performance.now() - this._startTime;
9559 frames = this._lastFrame + 1;
9560 duration = 1000 * (frames - this._firstFrame) / this._frameRate;
9561
9562 if (this._loop) {
9563 elapsed = elapsed % duration;
9564 } else {
9565 elapsed = Math.min(elapsed, duration);
9566 }
9567
9568 index = _.lerp(this._firstFrame, frames, elapsed / duration);
9569 index = Math.floor(index);
9570
9571 if (index !== this._index) {
9572
9573 this._index = index;
9574 texture = effects[this._index];
9575
9576 if (texture.loaded) {
9577
9578 width = texture.image.width;
9579 height = texture.image.height;
9580
9581 if (this.width !== width) {
9582 this.width = width;
9583 }
9584 if (this.height !== height) {
9585 this.height = height;
9586 }
9587
9588 this.fill = texture;
9589
9590 if (index >= this._lastFrame - 1 && this._onLastFrame) {
9591 this._onLastFrame(); // Shortcut for chainable sprite animations
9592 }
9593
9594 }
9595
9596 }
9597
9598 } else if (this._flagIndex || !(this.fill instanceof Two.Texture)) {
9599
9600 texture = effects[this._index];
9601
9602 if (texture.loaded) {
9603
9604 width = texture.image.width;
9605 height = texture.image.height;
9606
9607 if (this.width !== width) {
9608 this.width = width;
9609 }
9610 if (this.height !== height) {
9611 this.height = height;
9612 }
9613
9614 }
9615
9616 this.fill = texture;
9617
9618 }
9619
9620 Rectangle.prototype._update.call(this);
9621
9622 return this;
9623
9624 },
9625
9626 flagReset: function () {
9627
9628 this._flagTextures = this._flagFrameRate = false;
9629 Rectangle.prototype.flagReset.call(this);
9630
9631 return this;
9632
9633 }
9634
9635 });
9636
9637 ImageSequence.MakeObservable(ImageSequence.prototype);
9638
9639})((typeof global !== 'undefined' ? global : this).Two);
9640
9641(function (Two) {
9642
9643 /**
9644 * Constants
9645 */
9646 var min = Math.min, max = Math.max;
9647 var _ = Two.Utils;
9648
9649 /**
9650 * A children collection which is accesible both by index and by object id
9651 * @constructor
9652 */
9653 var Children = function () {
9654
9655 Two.Utils.Collection.apply(this, arguments);
9656
9657 Object.defineProperty(this, '_events', {
9658 value: {},
9659 enumerable: false
9660 });
9661
9662 this.ids = {};
9663
9664 this.on(Two.Events.insert, this.attach);
9665 this.on(Two.Events.remove, this.detach);
9666 Children.prototype.attach.apply(this, arguments);
9667
9668 };
9669
9670 Children.prototype = new Two.Utils.Collection();
9671 Children.prototype.constructor = Children;
9672
9673 _.extend(Children.prototype, {
9674
9675 attach: function (children) {
9676 for (var i = 0; i < children.length; i++) {
9677 this.ids[children[i].id] = children[i];
9678 }
9679 return this;
9680 },
9681
9682 detach: function (children) {
9683 for (var i = 0; i < children.length; i++) {
9684 delete this.ids[children[i].id];
9685 }
9686 return this;
9687 }
9688
9689 });
9690
9691 var Group = Two.Group = function () {
9692
9693 Two.Shape.call(this, true);
9694
9695 this._renderer.type = 'group';
9696
9697 this.additions = [];
9698 this.subtractions = [];
9699
9700 this.children = arguments;
9701
9702 };
9703
9704 _.extend(Group, {
9705
9706 Children: Children,
9707
9708 InsertChildren: function (children) {
9709 for (var i = 0; i < children.length; i++) {
9710 replaceParent.call(this, children[i], this);
9711 }
9712 },
9713
9714 RemoveChildren: function (children) {
9715 for (var i = 0; i < children.length; i++) {
9716 replaceParent.call(this, children[i]);
9717 }
9718 },
9719
9720 OrderChildren: function (children) {
9721 this._flagOrder = true;
9722 },
9723
9724 MakeObservable: function (object) {
9725
9726 var properties = Two.Path.Properties.slice(0);
9727 var oi = _.indexOf(properties, 'opacity');
9728
9729 if (oi >= 0) {
9730
9731 properties.splice(oi, 1);
9732
9733 Object.defineProperty(object, 'opacity', {
9734
9735 enumerable: true,
9736
9737 get: function () {
9738 return this._opacity;
9739 },
9740
9741 set: function (v) {
9742 // Only set flag if there is an actual difference
9743 this._flagOpacity = (this._opacity != v);
9744 this._opacity = v;
9745 }
9746
9747 });
9748
9749 }
9750
9751 Two.Shape.MakeObservable(object);
9752 Group.MakeGetterSetters(object, properties);
9753
9754 Object.defineProperty(object, 'children', {
9755
9756 enumerable: true,
9757
9758 get: function () {
9759 return this._children;
9760 },
9761
9762 set: function (children) {
9763
9764 var insertChildren = _.bind(Group.InsertChildren, this);
9765 var removeChildren = _.bind(Group.RemoveChildren, this);
9766 var orderChildren = _.bind(Group.OrderChildren, this);
9767
9768 if (this._children) {
9769 this._children.unbind();
9770 }
9771
9772 this._children = new Children(children);
9773 this._children.bind(Two.Events.insert, insertChildren);
9774 this._children.bind(Two.Events.remove, removeChildren);
9775 this._children.bind(Two.Events.order, orderChildren);
9776
9777 }
9778
9779 });
9780
9781 Object.defineProperty(object, 'mask', {
9782
9783 enumerable: true,
9784
9785 get: function () {
9786 return this._mask;
9787 },
9788
9789 set: function (v) {
9790 this._mask = v;
9791 this._flagMask = true;
9792 if (!v.clip) {
9793 v.clip = true;
9794 }
9795 }
9796
9797 });
9798
9799 },
9800
9801 MakeGetterSetters: function (group, properties) {
9802
9803 if (!_.isArray(properties)) {
9804 properties = [properties];
9805 }
9806
9807 _.each(properties, function (k) {
9808 Group.MakeGetterSetter(group, k);
9809 });
9810
9811 },
9812
9813 MakeGetterSetter: function (group, k) {
9814
9815 var secret = '_' + k;
9816
9817 Object.defineProperty(group, k, {
9818
9819 enumerable: true,
9820
9821 get: function () {
9822 return this[secret];
9823 },
9824
9825 set: function (v) {
9826 this[secret] = v;
9827 _.each(this.children, function (child) { // Trickle down styles
9828 child[k] = v;
9829 });
9830 }
9831
9832 });
9833
9834 }
9835
9836 });
9837
9838 _.extend(Group.prototype, Two.Shape.prototype, {
9839
9840 // Flags
9841 // http://en.wikipedia.org/wiki/Flag
9842
9843 _flagAdditions: false,
9844 _flagSubtractions: false,
9845 _flagOrder: false,
9846 _flagOpacity: true,
9847
9848 _flagMask: false,
9849
9850 // Underlying Properties
9851
9852 _fill: '#fff',
9853 _stroke: '#000',
9854 _linewidth: 1.0,
9855 _opacity: 1.0,
9856 _visible: true,
9857
9858 _cap: 'round',
9859 _join: 'round',
9860 _miter: 4,
9861
9862 _closed: true,
9863 _curved: false,
9864 _automatic: true,
9865 _beginning: 0,
9866 _ending: 1.0,
9867
9868 _mask: null,
9869
9870 /**
9871 * TODO: Group has a gotcha in that it's at the moment required to be bound to
9872 * an instance of two in order to add elements correctly. This needs to
9873 * be rethought and fixed.
9874 */
9875 clone: function (parent) {
9876
9877 parent = parent || this.parent;
9878
9879 var group = new Group();
9880 var children = _.map(this.children, function (child) {
9881 return child.clone(group);
9882 });
9883
9884 group.add(children);
9885
9886 group.opacity = this.opacity;
9887
9888 if (this.mask) {
9889 group.mask = this.mask;
9890 }
9891
9892 group.translation.copy(this.translation);
9893 group.rotation = this.rotation;
9894 group.scale = this.scale;
9895
9896 if (parent) {
9897 parent.add(group);
9898 }
9899
9900 return group;
9901
9902 },
9903
9904 /**
9905 * Export the data from the instance of Two.Group into a plain JavaScript
9906 * object. This also makes all children plain JavaScript objects. Great
9907 * for turning into JSON and storing in a database.
9908 */
9909 toObject: function () {
9910
9911 var result = {
9912 children: [],
9913 translation: this.translation.toObject(),
9914 rotation: this.rotation,
9915 scale: this.scale,
9916 opacity: this.opacity,
9917 mask: (this.mask ? this.mask.toObject() : null)
9918 };
9919
9920 _.each(this.children, function (child, i) {
9921 result.children[i] = child.toObject();
9922 }, this);
9923
9924 return result;
9925
9926 },
9927
9928 /**
9929 * Anchor all children to the upper left hand corner
9930 * of the group.
9931 */
9932 corner: function () {
9933
9934 var rect = this.getBoundingClientRect(true),
9935 corner = { x: rect.left, y: rect.top };
9936
9937 this.children.forEach(function (child) {
9938 child.translation.subSelf(corner);
9939 });
9940
9941 return this;
9942
9943 },
9944
9945 /**
9946 * Anchors all children around the center of the group,
9947 * effectively placing the shape around the unit circle.
9948 */
9949 center: function () {
9950
9951 var rect = this.getBoundingClientRect(true);
9952
9953 rect.centroid = {
9954 x: rect.left + rect.width / 2,
9955 y: rect.top + rect.height / 2
9956 };
9957
9958 this.children.forEach(function (child) {
9959 if (child.isShape) {
9960 child.translation.subSelf(rect.centroid);
9961 }
9962 });
9963
9964 // this.translation.copy(rect.centroid);
9965
9966 return this;
9967
9968 },
9969
9970 /**
9971 * Recursively search for id. Returns the first element found.
9972 * Returns null if none found.
9973 */
9974 getById: function (id) {
9975 var search = function (node, id) {
9976 if (node.id === id) {
9977 return node;
9978 } else if (node.children) {
9979 var i = node.children.length;
9980 while (i--) {
9981 var found = search(node.children[i], id);
9982 if (found) return found;
9983 }
9984 }
9985
9986 };
9987 return search(this, id) || null;
9988 },
9989
9990 /**
9991 * Recursively search for classes. Returns an array of matching elements.
9992 * Empty array if none found.
9993 */
9994 getByClassName: function (cl) {
9995 var found = [];
9996 var search = function (node, cl) {
9997 if (node.classList.indexOf(cl) != -1) {
9998 found.push(node);
9999 } else if (node.children) {
10000 node.children.forEach(function (child) {
10001 search(child, cl);
10002 });
10003 }
10004 return found;
10005 };
10006 return search(this, cl);
10007 },
10008
10009 /**
10010 * Recursively search for children of a specific type,
10011 * e.g. Two.Polygon. Pass a reference to this type as the param.
10012 * Returns an empty array if none found.
10013 */
10014 getByType: function (type) {
10015 var found = [];
10016 var search = function (node, type) {
10017 for (var id in node.children) {
10018 if (node.children[id] instanceof type) {
10019 found.push(node.children[id]);
10020 } else if (node.children[id] instanceof Two.Group) {
10021 search(node.children[id], type);
10022 }
10023 }
10024 return found;
10025 };
10026 return search(this, type);
10027 },
10028
10029 /**
10030 * Add objects to the group.
10031 */
10032 add: function (objects) {
10033
10034 // Allow to pass multiple objects either as array or as multiple arguments
10035 // If it's an array also create copy of it in case we're getting passed
10036 // a childrens array directly.
10037 if (!(objects instanceof Array)) {
10038 objects = _.toArray(arguments);
10039 } else {
10040 objects = objects.slice();
10041 }
10042
10043 // Add the objects
10044 for (var i = 0; i < objects.length; i++) {
10045 if (!(objects[i] && objects[i].id)) continue;
10046 this.children.push(objects[i]);
10047 }
10048
10049 return this;
10050
10051 },
10052
10053 /**
10054 * Remove objects from the group.
10055 */
10056 remove: function (objects) {
10057
10058 var l = arguments.length,
10059 grandparent = this.parent;
10060
10061 // Allow to call remove without arguments
10062 // This will detach the object from the scene.
10063 if (l <= 0 && grandparent) {
10064 grandparent.remove(this);
10065 return this;
10066 }
10067
10068 // Allow to pass multiple objects either as array or as multiple arguments
10069 // If it's an array also create copy of it in case we're getting passed
10070 // a childrens array directly.
10071 if (!(objects instanceof Array)) {
10072 objects = _.toArray(arguments);
10073 } else {
10074 objects = objects.slice();
10075 }
10076
10077 // Remove the objects
10078 for (var i = 0; i < objects.length; i++) {
10079 if (!objects[i] || !(this.children.ids[objects[i].id])) continue;
10080 this.children.splice(_.indexOf(this.children, objects[i]), 1);
10081 }
10082
10083 return this;
10084
10085 },
10086
10087 /**
10088 * Return an object with top, left, right, bottom, width, and height
10089 * parameters of the group.
10090 */
10091 getBoundingClientRect: function (shallow) {
10092 var rect;
10093
10094 // TODO: Update this to not __always__ update. Just when it needs to.
10095 this._update(true);
10096
10097 // Variables need to be defined here, because of nested nature of groups.
10098 var left = Infinity, right = -Infinity,
10099 top = Infinity, bottom = -Infinity;
10100
10101 this.children.forEach(function (child) {
10102
10103 if (/(linear-gradient|radial-gradient|gradient)/.test(child._renderer.type)) {
10104 return;
10105 }
10106
10107 rect = child.getBoundingClientRect(shallow);
10108
10109 if (!_.isNumber(rect.top) || !_.isNumber(rect.left) ||
10110 !_.isNumber(rect.right) || !_.isNumber(rect.bottom)) {
10111 return;
10112 }
10113
10114 top = min(rect.top, top);
10115 left = min(rect.left, left);
10116 right = max(rect.right, right);
10117 bottom = max(rect.bottom, bottom);
10118
10119 }, this);
10120
10121 return {
10122 top: top,
10123 left: left,
10124 right: right,
10125 bottom: bottom,
10126 width: right - left,
10127 height: bottom - top
10128 };
10129
10130 },
10131
10132 /**
10133 * Trickle down of noFill
10134 */
10135 noFill: function () {
10136 this.children.forEach(function (child) {
10137 child.noFill();
10138 });
10139 return this;
10140 },
10141
10142 /**
10143 * Trickle down of noStroke
10144 */
10145 noStroke: function () {
10146 this.children.forEach(function (child) {
10147 child.noStroke();
10148 });
10149 return this;
10150 },
10151
10152 /**
10153 * Trickle down subdivide
10154 */
10155 subdivide: function () {
10156 var args = arguments;
10157 this.children.forEach(function (child) {
10158 child.subdivide.apply(child, args);
10159 });
10160 return this;
10161 },
10162
10163 flagReset: function () {
10164
10165 if (this._flagAdditions) {
10166 this.additions.length = 0;
10167 this._flagAdditions = false;
10168 }
10169
10170 if (this._flagSubtractions) {
10171 this.subtractions.length = 0;
10172 this._flagSubtractions = false;
10173 }
10174
10175 this._flagOrder = this._flagMask = this._flagOpacity = false;
10176
10177 Two.Shape.prototype.flagReset.call(this);
10178
10179 return this;
10180
10181 }
10182
10183 });
10184
10185 Group.MakeObservable(Group.prototype);
10186
10187 /**
10188 * Helper function used to sync parent-child relationship within the
10189 * `Two.Group.children` object.
10190 *
10191 * Set the parent of the passed object to another object
10192 * and updates parent-child relationships
10193 * Calling with one arguments will simply remove the parenting
10194 */
10195 function replaceParent(child, newParent) {
10196
10197 var parent = child.parent;
10198 var index;
10199
10200 if (parent === newParent) {
10201 this.additions.push(child);
10202 this._flagAdditions = true;
10203 return;
10204 }
10205
10206 if (parent && parent.children.ids[child.id]) {
10207
10208 index = _.indexOf(parent.children, child);
10209 parent.children.splice(index, 1);
10210
10211 // If we're passing from one parent to another...
10212 index = _.indexOf(parent.additions, child);
10213
10214 if (index >= 0) {
10215 parent.additions.splice(index, 1);
10216 } else {
10217 parent.subtractions.push(child);
10218 parent._flagSubtractions = true;
10219 }
10220
10221 }
10222
10223 if (newParent) {
10224 child.parent = newParent;
10225 this.additions.push(child);
10226 this._flagAdditions = true;
10227 return;
10228 }
10229
10230 // If we're passing from one parent to another...
10231 index = _.indexOf(this.additions, child);
10232
10233 if (index >= 0) {
10234 this.additions.splice(index, 1);
10235 } else {
10236 this.subtractions.push(child);
10237 this._flagSubtractions = true;
10238 }
10239
10240 delete child.parent;
10241
10242 }
10243
10244})((typeof global !== 'undefined' ? global : this).Two);