· 5 years ago · Oct 04, 2020, 03:38 AM
1/*!
2 * Paper.js v0.21
3 *
4 * This file is part of Paper.js, a JavaScript Vector Graphics Library,
5 * based on Scriptographer.org and designed to be largely API compatible.
6 * http:
7 * http:
8 *
9 * Copyright (c) 2011, Juerg Lehni & Jonathan Puckey
10 * http:
11 *
12 * Distributed under the MIT license. See LICENSE file for details.
13 *
14 * All rights reserved.
15 *
16 * Date: Tue Aug 2 10:08:08 2011 +0100
17 *
18 ***
19 *
20 * Bootstrap.js JavaScript Framework.
21 * http:
22 *
23 * Copyright (c) 2006 - 2011 Juerg Lehni
24 * http:
25 *
26 * Distributed under the MIT license.
27 *
28 ***
29 *
30 * Parse-js
31 *
32 * A JavaScript tokenizer / parser / generator, originally written in Lisp.
33 * Copyright (c) Marijn Haverbeke <marijnh@gmail.com>
34 * http:
35 *
36 * Ported by to JavaScript by Mihai Bazon
37 * Copyright (c) 2010, Mihai Bazon <mihai.bazon@gmail.com>
38 * http:
39 *
40 * Modifications and adaptions to browser (c) 2011, Juerg Lehni
41 * http:
42 *
43 * Distributed under the BSD license.
44 */
45
46var paper = new function() {
47
48var Base = new function() {
49 var fix = !this.__proto__,
50 hidden = /^(statics|generics|preserve|enumerable|prototype|__proto__|toString|valueOf)$/,
51 proto = Object.prototype,
52 has = fix
53 ? function(name) {
54 return name !== '__proto__' && this.hasOwnProperty(name);
55 }
56 : proto.hasOwnProperty,
57 toString = proto.toString,
58 proto = Array.prototype,
59 isArray = Array.isArray = Array.isArray || function(obj) {
60 return toString.call(obj) === '[object Array]';
61 },
62 slice = proto.slice,
63 forEach = proto.forEach = proto.forEach || function(iter, bind) {
64 for (var i = 0, l = this.length; i < l; i++)
65 iter.call(bind, this[i], i, this);
66 },
67 forIn = function(iter, bind) {
68 for (var i in this)
69 if (this.hasOwnProperty(i))
70 iter.call(bind, this[i], i, this);
71 },
72 _define = Object.defineProperty,
73 _describe = Object.getOwnPropertyDescriptor;
74
75 function define(obj, name, desc) {
76 if (_define) {
77 try {
78 delete obj[name];
79 return _define(obj, name, desc);
80 } catch (e) {}
81 }
82 if ((desc.get || desc.set) && obj.__defineGetter__) {
83 desc.get && obj.__defineGetter__(name, desc.get);
84 desc.set && obj.__defineSetter__(name, desc.set);
85 } else {
86 obj[name] = desc.value;
87 }
88 return obj;
89 }
90
91 function describe(obj, name) {
92 if (_describe) {
93 try {
94 return _describe(obj, name);
95 } catch (e) {}
96 }
97 var get = obj.__lookupGetter__ && obj.__lookupGetter__(name);
98 return get
99 ? { get: get, set: obj.__lookupSetter__(name), enumerable: true,
100 configurable: true }
101 : has.call(obj, name)
102 ? { value: obj[name], enumerable: true, configurable: true,
103 writable: true }
104 : null;
105 }
106
107 function inject(dest, src, enumerable, base, preserve, generics) {
108 var beans, bean;
109
110 function field(name, val, dontCheck, generics) {
111 var val = val || (val = describe(src, name))
112 && (val.get ? val : val.value),
113 func = typeof val === 'function',
114 res = val,
115 prev = preserve || func
116 ? (val && val.get ? name in dest : dest[name]) : null;
117 if (generics && func && (!preserve || !generics[name])) {
118 generics[name] = function(bind) {
119 return bind && dest[name].apply(bind,
120 slice.call(arguments, 1));
121 }
122 }
123 if ((dontCheck || val !== undefined && has.call(src, name))
124 && (!preserve || !prev)) {
125 if (func) {
126 if (prev && /\bthis\.base\b/.test(val)) {
127 var fromBase = base && base[name] == prev;
128 res = function() {
129 var tmp = describe(this, 'base');
130 define(this, 'base', { value: fromBase
131 ? base[name] : prev, configurable: true });
132 try {
133 return val.apply(this, arguments);
134 } finally {
135 tmp ? define(this, 'base', tmp)
136 : delete this.base;
137 }
138 };
139 res.toString = function() {
140 return val.toString();
141 }
142 res.valueOf = function() {
143 return val.valueOf();
144 }
145 }
146 if (beans && val.length == 0
147 && (bean = name.match(/^(get|is)(([A-Z])(.*))$/)))
148 beans.push([ bean[3].toLowerCase() + bean[4], bean[2] ]);
149 }
150 if (!res || func || !res.get && !res.set)
151 res = { value: res, writable: true };
152 if ((describe(dest, name)
153 || { configurable: true }).configurable) {
154 res.configurable = true;
155 res.enumerable = enumerable;
156 }
157 define(dest, name, res);
158 }
159 }
160 if (src) {
161 beans = [];
162 for (var name in src)
163 if (has.call(src, name) && !hidden.test(name))
164 field(name, null, true, generics);
165 field('toString');
166 field('valueOf');
167 for (var i = 0, l = beans && beans.length; i < l; i++)
168 try {
169 var bean = beans[i], part = bean[1];
170 field(bean[0], {
171 get: dest['get' + part] || dest['is' + part],
172 set: dest['set' + part]
173 }, true);
174 } catch (e) {}
175 }
176 return dest;
177 }
178
179 function extend(obj) {
180 var ctor = function(dont) {
181 if (fix) define(this, '__proto__', { value: obj });
182 if (this.initialize && dont !== ctor.dont)
183 return this.initialize.apply(this, arguments);
184 }
185 ctor.prototype = obj;
186 ctor.toString = function() {
187 return (this.prototype.initialize || function() {}).toString();
188 }
189 return ctor;
190 }
191
192 function iterator(iter) {
193 return !iter
194 ? function(val) { return val }
195 : typeof iter !== 'function'
196 ? function(val) { return val == iter }
197 : iter;
198 }
199
200 function each(obj, iter, bind, asArray) {
201 try {
202 if (obj)
203 (asArray || asArray === undefined && isArray(obj)
204 ? forEach : forIn).call(obj, iterator(iter),
205 bind = bind || obj);
206 } catch (e) {
207 if (e !== Base.stop) throw e;
208 }
209 return bind;
210 }
211
212 function clone(obj) {
213 return each(obj, function(val, i) {
214 this[i] = val;
215 }, new obj.constructor());
216 }
217
218 return inject(function() {}, {
219 inject: function(src) {
220 if (src) {
221 var proto = this.prototype,
222 base = proto.__proto__ && proto.__proto__.constructor,
223 statics = src.statics == true ? src : src.statics;
224 if (statics != src)
225 inject(proto, src, src.enumerable, base && base.prototype,
226 src.preserve, src.generics && this);
227 inject(this, statics, true, base, src.preserve);
228 }
229 for (var i = 1, l = arguments.length; i < l; i++)
230 this.inject(arguments[i]);
231 return this;
232 },
233
234 extend: function(src) {
235 var proto = new this(this.dont),
236 ctor = extend(proto);
237 define(proto, 'constructor',
238 { value: ctor, writable: true, configurable: true });
239 ctor.dont = {};
240 inject(ctor, this, true);
241 return arguments.length ? this.inject.apply(ctor, arguments) : ctor;
242 }
243 }, true).inject({
244 has: has,
245 each: each,
246
247 inject: function() {
248 for (var i = 0, l = arguments.length; i < l; i++)
249 inject(this, arguments[i]);
250 return this;
251 },
252
253 extend: function() {
254 var res = new (extend(this));
255 return res.inject.apply(res, arguments);
256 },
257
258 each: function(iter, bind) {
259 return each(this, iter, bind);
260 },
261
262 clone: function() {
263 return clone(this);
264 },
265
266 statics: {
267 each: each,
268 clone: clone,
269 define: define,
270 describe: describe,
271 iterator: iterator,
272
273 has: function(obj, name) {
274 return has.call(obj, name);
275 },
276
277 type: function(obj) {
278 return (obj || obj === 0) && (obj._type || typeof obj) || null;
279 },
280
281 check: function(obj) {
282 return !!(obj || obj === 0);
283 },
284
285 pick: function() {
286 for (var i = 0, l = arguments.length; i < l; i++)
287 if (arguments[i] !== undefined)
288 return arguments[i];
289 return null;
290 },
291
292 stop: {}
293 }
294 });
295}
296
297this.Base = Base.inject({
298 generics: true,
299
300 clone: function() {
301 return new this.constructor(this);
302 },
303
304 toString: function() {
305 return '{ ' + Base.each(this, function(value, key) {
306 if (key.charAt(0) != '_') {
307 var type = typeof value;
308 this.push(key + ': ' + (type === 'number'
309 ? Base.formatNumber(value)
310 : type === 'string' ? "'" + value + "'" : value));
311 }
312 }, []).join(', ') + ' }';
313 },
314
315 statics: {
316 read: function(list, start, length) {
317 var start = start || 0,
318 length = length || list.length - start;
319 var obj = list[start];
320 if (obj instanceof this
321 || this.prototype._readNull && obj == null && length <= 1)
322 return obj;
323 obj = new this(this.dont);
324 return obj.initialize.apply(obj, start > 0 || length < list.length
325 ? Array.prototype.slice.call(list, start, start + length)
326 : list) || obj;
327 },
328
329 readAll: function(list, start) {
330 var res = [], entry;
331 for (var i = start || 0, l = list.length; i < l; i++) {
332 res.push(Array.isArray(entry = list[i])
333 ? this.read(entry, 0)
334 : this.read(list, i, 1));
335 }
336 return res;
337 },
338
339 splice: function(list, items, index, remove) {
340 var amount = items && items.length,
341 append = index === undefined;
342 index = append ? list.length : index;
343 for (var i = 0; i < amount; i++)
344 items[i]._index = index + i;
345 if (append) {
346 list.push.apply(list, items);
347 return [];
348 } else {
349 var args = [index, remove];
350 if (items)
351 args.push.apply(args, items);
352 var removed = list.splice.apply(list, args);
353 for (var i = 0, l = removed.length; i < l; i++)
354 delete removed[i]._index;
355 for (var i = index + amount, l = list.length; i < l; i++)
356 list[i]._index = i;
357 return removed;
358 }
359 },
360
361 merge: function() {
362 return Base.each(arguments, function(hash) {
363 Base.each(hash, function(value, key) {
364 this[key] = value;
365 }, this);
366 }, new Base(), true);
367 },
368
369 capitalize: function(str) {
370 return str.replace(/\b[a-z]/g, function(match) {
371 return match.toUpperCase();
372 });
373 },
374
375 camelize: function(str) {
376 return str.replace(/-(\w)/g, function(all, chr) {
377 return chr.toUpperCase();
378 });
379 },
380
381 hyphenate: function(str) {
382 return str.replace(/[a-z][A-Z0-9]|[0-9][a-zA-Z]|[A-Z]{2}[a-z]/g,
383 function(match) {
384 return match.charAt(0) + '-' + match.substring(1);
385 }
386 ).toLowerCase();
387 },
388
389 formatNumber: function(num) {
390 return (Math.round(num * 100000) / 100000).toString();
391 }
392 }
393});
394
395var PaperScope = this.PaperScope = Base.extend({
396
397 initialize: function(script) {
398 paper = this;
399 this.view = null;
400 this.views = [];
401 this.project = null;
402 this.projects = [];
403 this.tool = null;
404 this.tools = [];
405 this._id = script && (script.getAttribute('id') || script.src)
406 || ('paperscope-' + (PaperScope._id++));
407 if (script)
408 script.setAttribute('id', this._id);
409 PaperScope._scopes[this._id] = this;
410 },
411
412 version: 0.21,
413
414 evaluate: function(code) {
415 var res = PaperScript.evaluate(code, this);
416 View.updateFocus();
417 return res;
418 },
419
420 install: function(scope) {
421 var that = this;
422 Base.each(['project', 'view', 'tool'], function(key) {
423 Base.define(scope, key, {
424 configurable: true,
425 writable: true,
426 get: function() {
427 return that[key];
428 }
429 });
430 });
431 for (var key in this) {
432 if (!/^(version|_id|load)/.test(key) && !(key in scope))
433 scope[key] = this[key];
434 }
435 },
436
437 setup: function(canvas) {
438 paper = this;
439 this.project = new Project();
440 if (canvas)
441 this.view = new View(canvas);
442 },
443
444 clear: function() {
445 for (var i = this.projects.length - 1; i >= 0; i--)
446 this.projects[i].remove();
447 for (var i = this.views.length - 1; i >= 0; i--)
448 this.views[i].remove();
449 for (var i = this.tools.length - 1; i >= 0; i--)
450 this.tools[i].remove();
451 },
452
453 remove: function() {
454 this.clear();
455 delete PaperScope._scopes[this._id];
456 },
457
458 _needsRedraw: function() {
459 if (!this._redrawNotified) {
460 for (var i = this.views.length - 1; i >= 0; i--)
461 this.views[i]._redrawNeeded = true;
462 this._redrawNotified = true;
463 }
464 },
465
466 statics: {
467 _scopes: {},
468 _id: 0,
469
470 get: function(id) {
471 if (typeof id === 'object')
472 id = id.getAttribute('id');
473 return this._scopes[id] || null;
474 },
475
476 each: function(iter) {
477 Base.each(this._scopes, iter);
478 }
479 }
480});
481
482var PaperScopeItem = Base.extend({
483
484 initialize: function(activate) {
485 this._scope = paper;
486 this._index = this._scope[this._list].push(this) - 1;
487 if (activate || !this._scope[this._reference])
488 this.activate();
489 },
490
491 activate: function() {
492 if (!this._scope)
493 return false;
494 this._scope[this._reference] = this;
495 return true;
496 },
497
498 remove: function() {
499 if (this._index == null)
500 return false;
501 Base.splice(this._scope[this._list], null, this._index, 1);
502 if (this._scope[this._reference] == this)
503 this._scope[this._reference] = null;
504 this._scope = null;
505 return true;
506 }
507});
508
509var Point = this.Point = Base.extend({
510 initialize: function(arg0, arg1) {
511 if (arg1 !== undefined) {
512 this.x = arg0;
513 this.y = arg1;
514 } else if (arg0 !== undefined) {
515 if (arg0 == null) {
516 this.x = this.y = 0;
517 } else if (arg0.x !== undefined) {
518 this.x = arg0.x;
519 this.y = arg0.y;
520 } else if (arg0.width !== undefined) {
521 this.x = arg0.width;
522 this.y = arg0.height;
523 } else if (Array.isArray(arg0)) {
524 this.x = arg0[0];
525 this.y = arg0.length > 1 ? arg0[1] : arg0[0];
526 } else if (arg0.angle !== undefined) {
527 this.x = arg0.length;
528 this.y = 0;
529 this.setAngle(arg0.angle);
530 } else if (typeof arg0 === 'number') {
531 this.x = this.y = arg0;
532 } else {
533 this.x = this.y = 0;
534 }
535 } else {
536 this.x = this.y = 0;
537 }
538 },
539
540 set: function(x, y) {
541 this.x = x;
542 this.y = y;
543 return this;
544 },
545
546 clone: function() {
547 return Point.create(this.x, this.y);
548 },
549
550 toString: function() {
551 var format = Base.formatNumber;
552 return '{ x: ' + format(this.x) + ', y: ' + format(this.y) + ' }';
553 },
554
555 add: function(point) {
556 point = Point.read(arguments);
557 return Point.create(this.x + point.x, this.y + point.y);
558 },
559
560 subtract: function(point) {
561 point = Point.read(arguments);
562 return Point.create(this.x - point.x, this.y - point.y);
563 },
564
565 multiply: function(point) {
566 point = Point.read(arguments);
567 return Point.create(this.x * point.x, this.y * point.y);
568 },
569
570 divide: function(point) {
571 point = Point.read(arguments);
572 return Point.create(this.x / point.x, this.y / point.y);
573 },
574
575 modulo: function(point) {
576 point = Point.read(arguments);
577 return Point.create(this.x % point.x, this.y % point.y);
578 },
579
580 negate: function() {
581 return Point.create(-this.x, -this.y);
582 },
583
584 transform: function(matrix) {
585 return matrix ? matrix._transformPoint(this) : this;
586 },
587
588 getDistance: function(point, squared) {
589 point = Point.read(arguments);
590 var x = point.x - this.x,
591 y = point.y - this.y,
592 d = x * x + y * y;
593 return squared ? d : Math.sqrt(d);
594 },
595
596 getLength: function() {
597 var l = this.x * this.x + this.y * this.y;
598 return arguments[0] ? l : Math.sqrt(l);
599 },
600
601 setLength: function(length) {
602 if (this.isZero()) {
603 var angle = this._angle || 0;
604 this.set(
605 Math.cos(angle) * length,
606 Math.sin(angle) * length
607 );
608 } else {
609 var scale = length / this.getLength();
610 if (scale == 0)
611 this.getAngle();
612 this.set(
613 this.x * scale,
614 this.y * scale
615 );
616 }
617 return this;
618 },
619
620 normalize: function(length) {
621 if (length === undefined)
622 length = 1;
623 var current = this.getLength(),
624 scale = current != 0 ? length / current : 0,
625 point = Point.create(this.x * scale, this.y * scale);
626 point._angle = this._angle;
627 return point;
628 },
629
630 getAngle: function() {
631 return this.getAngleInRadians(arguments[0]) * 180 / Math.PI;
632 },
633
634 setAngle: function(angle) {
635 angle = this._angle = angle * Math.PI / 180;
636 if (!this.isZero()) {
637 var length = this.getLength();
638 this.set(
639 Math.cos(angle) * length,
640 Math.sin(angle) * length
641 );
642 }
643 return this;
644 },
645
646 getAngleInRadians: function() {
647 if (arguments[0] === undefined) {
648 if (this._angle == null)
649 this._angle = Math.atan2(this.y, this.x);
650 return this._angle;
651 } else {
652 var point = Point.read(arguments),
653 div = this.getLength() * point.getLength();
654 if (div == 0) {
655 return NaN;
656 } else {
657 return Math.acos(this.dot(point) / div);
658 }
659 }
660 },
661
662 getAngleInDegrees: function() {
663 return this.getAngle(arguments[0]);
664 },
665
666 getQuadrant: function() {
667 return this.x >= 0 ? this.y >= 0 ? 1 : 4 : this.y >= 0 ? 2 : 3;
668 },
669
670 getDirectedAngle: function(point) {
671 point = Point.read(arguments);
672 return Math.atan2(this.cross(point), this.dot(point)) * 180 / Math.PI;
673 },
674
675 rotate: function(angle, center) {
676 angle = angle * Math.PI / 180;
677 var point = center ? this.subtract(center) : this,
678 s = Math.sin(angle),
679 c = Math.cos(angle);
680 point = Point.create(
681 point.x * c - point.y * s,
682 point.y * c + point.x * s
683 );
684 return center ? point.add(center) : point;
685 },
686
687 equals: function(point) {
688 point = Point.read(arguments);
689 return this.x == point.x && this.y == point.y;
690 },
691
692 isInside: function(rect) {
693 return rect.contains(this);
694 },
695
696 isClose: function(point, tolerance) {
697 return this.getDistance(point) < tolerance;
698 },
699
700 isColinear: function(point) {
701 return this.cross(point) < Numerical.TOLERANCE;
702 },
703
704 isOrthogonal: function(point) {
705 return this.dot(point) < Numerical.TOLERANCE;
706 },
707
708 isZero: function() {
709 return this.x == 0 && this.y == 0;
710 },
711
712 isNaN: function() {
713 return isNaN(this.x) || isNaN(this.y);
714 },
715
716 dot: function(point) {
717 point = Point.read(arguments);
718 return this.x * point.x + this.y * point.y;
719 },
720
721 cross: function(point) {
722 point = Point.read(arguments);
723 return this.x * point.y - this.y * point.x;
724 },
725
726 project: function(point) {
727 point = Point.read(arguments);
728 if (point.isZero()) {
729 return Point.create(0, 0);
730 } else {
731 var scale = this.dot(point) / point.dot(point);
732 return Point.create(
733 point.x * scale,
734 point.y * scale
735 );
736 }
737 },
738
739 statics: {
740 create: function(x, y) {
741 var point = new Point(Point.dont);
742 point.x = x;
743 point.y = y;
744 return point;
745 },
746
747 min: function(point1, point2) {
748 point1 = Point.read(arguments, 0, 1);
749 point2 = Point.read(arguments, 1, 1);
750 return Point.create(
751 Math.min(point1.x, point2.x),
752 Math.min(point1.y, point2.y)
753 );
754 },
755
756 max: function(point1, point2) {
757 point1 = Point.read(arguments, 0, 1);
758 point2 = Point.read(arguments, 1, 1);
759 return Point.create(
760 Math.max(point1.x, point2.x),
761 Math.max(point1.y, point2.y)
762 );
763 },
764
765 random: function() {
766 return Point.create(Math.random(), Math.random());
767 }
768 }
769}, new function() {
770
771 return Base.each(['round', 'ceil', 'floor', 'abs'], function(name) {
772 var op = Math[name];
773 this[name] = function() {
774 return Point.create(op(this.x), op(this.y));
775 };
776 }, {});
777});
778
779var LinkedPoint = Point.extend({
780 set: function(x, y, dontNotify) {
781 this._x = x;
782 this._y = y;
783 if (!dontNotify)
784 this._owner[this._setter](this);
785 return this;
786 },
787
788 getX: function() {
789 return this._x;
790 },
791
792 setX: function(x) {
793 this._x = x;
794 this._owner[this._setter](this);
795 },
796
797 getY: function() {
798 return this._y;
799 },
800
801 setY: function(y) {
802 this._y = y;
803 this._owner[this._setter](this);
804 },
805
806 statics: {
807 create: function(owner, setter, x, y) {
808 var point = new LinkedPoint(LinkedPoint.dont);
809 point._x = x;
810 point._y = y;
811 point._owner = owner;
812 point._setter = setter;
813 return point;
814 }
815 }
816});
817
818var Size = this.Size = Base.extend({
819 initialize: function(arg0, arg1) {
820 if (arg1 !== undefined) {
821 this.width = arg0;
822 this.height = arg1;
823 } else if (arg0 !== undefined) {
824 if (arg0 == null) {
825 this.width = this.height = 0;
826 } else if (arg0.width !== undefined) {
827 this.width = arg0.width;
828 this.height = arg0.height;
829 } else if (arg0.x !== undefined) {
830 this.width = arg0.x;
831 this.height = arg0.y;
832 } else if (Array.isArray(arg0)) {
833 this.width = arg0[0];
834 this.height = arg0.length > 1 ? arg0[1] : arg0[0];
835 } else if (typeof arg0 === 'number') {
836 this.width = this.height = arg0;
837 } else {
838 this.width = this.height = 0;
839 }
840 } else {
841 this.width = this.height = 0;
842 }
843 },
844
845 toString: function() {
846 var format = Base.formatNumber;
847 return '{ width: ' + format(this.width)
848 + ', height: ' + format(this.height) + ' }';
849 },
850
851 set: function(width, height) {
852 this.width = width;
853 this.height = height;
854 return this;
855 },
856
857 clone: function() {
858 return Size.create(this.width, this.height);
859 },
860
861 add: function(size) {
862 size = Size.read(arguments);
863 return Size.create(this.width + size.width, this.height + size.height);
864 },
865
866 subtract: function(size) {
867 size = Size.read(arguments);
868 return Size.create(this.width - size.width, this.height - size.height);
869 },
870
871 multiply: function(size) {
872 size = Size.read(arguments);
873 return Size.create(this.width * size.width, this.height * size.height);
874 },
875
876 divide: function(size) {
877 size = Size.read(arguments);
878 return Size.create(this.width / size.width, this.height / size.height);
879 },
880
881 modulo: function(size) {
882 size = Size.read(arguments);
883 return Size.create(this.width % size.width, this.height % size.height);
884 },
885
886 negate: function() {
887 return Size.create(-this.width, -this.height);
888 },
889
890 equals: function(size) {
891 size = Size.read(arguments);
892 return this.width == size.width && this.height == size.height;
893 },
894
895 isZero: function() {
896 return this.width == 0 && this.width == 0;
897 },
898
899 isNaN: function() {
900 return isNaN(this.width) || isNaN(this.height);
901 },
902
903 statics: {
904 create: function(width, height) {
905 return new Size(Size.dont).set(width, height);
906 },
907
908 min: function(size1, size2) {
909 return Size.create(
910 Math.min(size1.width, size2.width),
911 Math.min(size1.height, size2.height));
912 },
913
914 max: function(size1, size2) {
915 return Size.create(
916 Math.max(size1.width, size2.width),
917 Math.max(size1.height, size2.height));
918 },
919
920 random: function() {
921 return Size.create(Math.random(), Math.random());
922 }
923 }
924}, new function() {
925
926 return Base.each(['round', 'ceil', 'floor', 'abs'], function(name) {
927 var op = Math[name];
928 this[name] = function() {
929 return Size.create(op(this.width), op(this.height));
930 };
931 }, {});
932});
933
934var LinkedSize = Size.extend({
935 set: function(width, height, dontNotify) {
936 this._width = width;
937 this._height = height;
938 if (!dontNotify)
939 this._owner[this._setter](this);
940 return this;
941 },
942
943 getWidth: function() {
944 return this._width;
945 },
946
947 setWidth: function(width) {
948 this._width = width;
949 this._owner[this._setter](this);
950 },
951
952 getHeight: function() {
953 return this._height;
954 },
955
956 setHeight: function(height) {
957 this._height = height;
958 this._owner[this._setter](this);
959 },
960
961 statics: {
962 create: function(owner, setter, width, height) {
963 var point = new LinkedSize(LinkedSize.dont);
964 point._width = width;
965 point._height = height;
966 point._owner = owner;
967 point._setter = setter;
968 return point;
969 }
970 }
971});
972
973var Rectangle = this.Rectangle = Base.extend({
974 initialize: function(arg0, arg1, arg2, arg3) {
975 if (arguments.length == 4) {
976 this.x = arg0;
977 this.y = arg1;
978 this.width = arg2;
979 this.height = arg3;
980 } else if (arguments.length == 2) {
981 if (arg1 && arg1.x !== undefined) {
982 var point1 = Point.read(arguments, 0, 1);
983 var point2 = Point.read(arguments, 1, 1);
984 this.x = point1.x;
985 this.y = point1.y;
986 this.width = point2.x - point1.x;
987 this.height = point2.y - point1.y;
988 if (this.width < 0) {
989 this.x = point2.x;
990 this.width = -this.width;
991 }
992 if (this.height < 0) {
993 this.y = point2.y;
994 this.height = -this.height;
995 }
996 } else {
997 var point = Point.read(arguments, 0, 1);
998 var size = Size.read(arguments, 1, 1);
999 this.x = point.x;
1000 this.y = point.y;
1001 this.width = size.width;
1002 this.height = size.height;
1003 }
1004 } else if (arg0) {
1005 this.x = arg0.x || 0;
1006 this.y = arg0.y || 0;
1007 this.width = arg0.width || 0;
1008 this.height = arg0.height || 0;
1009 } else {
1010 this.x = this.y = this.width = this.height = 0;
1011 }
1012 },
1013
1014 set: function(x, y, width, height) {
1015 this.x = x;
1016 this.y = y;
1017 this.width = width;
1018 this.height = height;
1019 return this;
1020 },
1021
1022 getPoint: function() {
1023 return LinkedPoint.create(this, 'setPoint', this.x, this.y);
1024 },
1025
1026 setPoint: function(point) {
1027 point = Point.read(arguments);
1028 this.x = point.x;
1029 this.y = point.y;
1030 return this;
1031 },
1032
1033 getSize: function() {
1034 return LinkedSize.create(this, 'setSize', this.width, this.height);
1035 },
1036
1037 setSize: function(size) {
1038 size = Size.read(arguments);
1039 this.width = size.width;
1040 this.height = size.height;
1041 return this;
1042 },
1043
1044 getLeft: function() {
1045 return this.x;
1046 },
1047
1048 setLeft: function(left) {
1049 this.width -= left - this.x;
1050 this.x = left;
1051 return this;
1052 },
1053
1054 getTop: function() {
1055 return this.y;
1056 },
1057
1058 setTop: function(top) {
1059 this.height -= top - this.y;
1060 this.y = top;
1061 return this;
1062 },
1063
1064 getRight: function() {
1065 return this.x + this.width;
1066 },
1067
1068 setRight: function(right) {
1069 this.width = right - this.x;
1070 return this;
1071 },
1072
1073 getBottom: function() {
1074 return this.y + this.height;
1075 },
1076
1077 setBottom: function(bottom) {
1078 this.height = bottom - this.y;
1079 return this;
1080 },
1081
1082 getCenterX: function() {
1083 return this.x + this.width * 0.5;
1084 },
1085
1086 setCenterX: function(x) {
1087 this.x = x - this.width * 0.5;
1088 return this;
1089 },
1090
1091 getCenterY: function() {
1092 return this.y + this.height * 0.5;
1093 },
1094
1095 setCenterY: function(y) {
1096 this.y = y - this.height * 0.5;
1097 return this;
1098 },
1099
1100 getCenter: function() {
1101 return LinkedPoint.create(this, 'setCenter',
1102 this.getCenterX(), this.getCenterY());
1103 },
1104
1105 setCenter: function(point) {
1106 point = Point.read(arguments);
1107 return this.setCenterX(point.x).setCenterY(point.y);
1108 },
1109
1110 equals: function(rect) {
1111 rect = Rectangle.read(arguments);
1112 return this.x == rect.x && this.y == rect.y
1113 && this.width == rect.width && this.height == rect.height;
1114 },
1115
1116 isEmpty: function() {
1117 return this.width == 0 || this.height == 0;
1118 },
1119
1120 toString: function() {
1121 var format = Base.formatNumber;
1122 return '{ x: ' + format(this.x)
1123 + ', y: ' + format(this.y)
1124 + ', width: ' + format(this.width)
1125 + ', height: ' + format(this.height)
1126 + ' }';
1127 },
1128
1129 contains: function(arg) {
1130 return arg && arg.width !== undefined
1131 || (Array.isArray(arg) ? arg : arguments).length == 4
1132 ? this._containsRectangle(Rectangle.read(arguments))
1133 : this._containsPoint(Point.read(arguments));
1134 },
1135
1136 _containsPoint: function(point) {
1137 var x = point.x,
1138 y = point.y;
1139 return x >= this.x && y >= this.y
1140 && x <= this.x + this.width
1141 && y <= this.y + this.height;
1142 },
1143
1144 _containsRectangle: function(rect) {
1145 var x = rect.x,
1146 y = rect.y;
1147 return x >= this.x && y >= this.y
1148 && x + rect.width <= this.x + this.width
1149 && y + rect.height <= this.y + this.height;
1150 },
1151
1152 intersects: function(rect) {
1153 rect = Rectangle.read(arguments);
1154 return rect.x + rect.width > this.x
1155 && rect.y + rect.height > this.y
1156 && rect.x < this.x + this.width
1157 && rect.y < this.y + this.height;
1158 },
1159
1160 intersect: function(rect) {
1161 rect = Rectangle.read(arguments);
1162 var x1 = Math.max(this.x, rect.x),
1163 y1 = Math.max(this.y, rect.y),
1164 x2 = Math.min(this.x + this.width, rect.x + rect.width),
1165 y2 = Math.min(this.y + this.height, rect.y + rect.height);
1166 return Rectangle.create(x1, y1, x2 - x1, y2 - y1);
1167 },
1168
1169 unite: function(rect) {
1170 rect = Rectangle.read(arguments);
1171 var x1 = Math.min(this.x, rect.x),
1172 y1 = Math.min(this.y, rect.y),
1173 x2 = Math.max(this.x + this.width, rect.x + rect.width),
1174 y2 = Math.max(this.y + this.height, rect.y + rect.height);
1175 return Rectangle.create(x1, y1, x2 - x1, y2 - y1);
1176 },
1177
1178 include: function(point) {
1179 point = Point.read(arguments);
1180 var x1 = Math.min(this.x, point.x),
1181 y1 = Math.min(this.y, point.y),
1182 x2 = Math.max(this.x + this.width, point.x),
1183 y2 = Math.max(this.y + this.height, point.y);
1184 return Rectangle.create(x1, y1, x2 - x1, y2 - y1);
1185 },
1186
1187 expand: function(hor, ver) {
1188 if (ver === undefined)
1189 ver = hor;
1190 return Rectangle.create(this.x - hor / 2, this.y - ver / 2,
1191 this.width + hor, this.height + ver);
1192 },
1193
1194 scale: function(hor, ver) {
1195 return this.expand(this.width * hor - this.width,
1196 this.height * (ver === undefined ? hor : ver) - this.height);
1197 },
1198
1199 statics: {
1200 create: function(x, y, width, height) {
1201 return new Rectangle(Rectangle.dont).set(x, y, width, height);
1202 }
1203 }
1204}, new function() {
1205 return Base.each([
1206 ['Top', 'Left'], ['Top', 'Right'],
1207 ['Bottom', 'Left'], ['Bottom', 'Right'],
1208 ['Left', 'Center'], ['Top', 'Center'],
1209 ['Right', 'Center'], ['Bottom', 'Center']
1210 ],
1211 function(parts, index) {
1212 var part = parts.join('');
1213 var xFirst = /^[RL]/.test(part);
1214 if (index >= 4)
1215 parts[1] += xFirst ? 'Y' : 'X';
1216 var x = parts[xFirst ? 0 : 1],
1217 y = parts[xFirst ? 1 : 0],
1218 getX = 'get' + x,
1219 getY = 'get' + y,
1220 setX = 'set' + x,
1221 setY = 'set' + y,
1222 get = 'get' + part,
1223 set = 'set' + part;
1224 this[get] = function() {
1225 return LinkedPoint.create(this, set,
1226 this[getX](), this[getY]());
1227 };
1228 this[set] = function(point) {
1229 point = Point.read(arguments);
1230 return this[setX](point.x)[setY](point.y);
1231 };
1232 }, {});
1233});
1234
1235var LinkedRectangle = Rectangle.extend({
1236 set: function(x, y, width, height, dontNotify) {
1237 this._x = x;
1238 this._y = y;
1239 this._width = width;
1240 this._height = height;
1241 if (!dontNotify)
1242 this._owner[this._setter](this);
1243 return this;
1244 },
1245
1246 statics: {
1247 create: function(owner, setter, x, y, width, height) {
1248 var rect = new LinkedRectangle(LinkedRectangle.dont).set(
1249 x, y, width, height, true);
1250 rect._owner = owner;
1251 rect._setter = setter;
1252 return rect;
1253 }
1254 }
1255}, new function() {
1256 var proto = Rectangle.prototype;
1257
1258 return Base.each(['x', 'y', 'width', 'height'], function(key) {
1259 var part = Base.capitalize(key);
1260 var internal = '_' + key;
1261 this['get' + part] = function() {
1262 return this[internal];
1263 };
1264
1265 this['set' + part] = function(value) {
1266 this[internal] = value;
1267 if (!this._dontNotify)
1268 this._owner[this._setter](this);
1269 };
1270 }, Base.each(['Point', 'Size', 'Center',
1271 'Left', 'Top', 'Right', 'Bottom', 'CenterX', 'CenterY',
1272 'TopLeft', 'TopRight', 'BottomLeft', 'BottomRight',
1273 'LeftCenter', 'TopCenter', 'RightCenter', 'BottomCenter'],
1274 function(key) {
1275 var name = 'set' + key;
1276 this[name] = function(value) {
1277 this._dontNotify = true;
1278 proto[name].apply(this, arguments);
1279 delete this._dontNotify;
1280 this._owner[this._setter](this);
1281 return this;
1282 };
1283 }, {})
1284 );
1285});
1286
1287var Matrix = this.Matrix = Base.extend({
1288 initialize: function(arg) {
1289 var count = arguments.length,
1290 ok = true;
1291 if (count == 6) {
1292 this.set.apply(this, arguments);
1293 } else if (count == 1) {
1294 if (arg instanceof Matrix) {
1295 this.set(arg._a, arg._c, arg._b, arg._d, arg._tx, arg._ty);
1296 } else if (Array.isArray(arg)) {
1297 this.set.apply(this, arg);
1298 } else {
1299 ok = false;
1300 }
1301 } else if (count == 0) {
1302 this._a = this._d = 1;
1303 this._c = this._b = this._tx = this._ty = 0;
1304 } else {
1305 ok = false;
1306 }
1307 if (!ok)
1308 throw new Error('Unsupported matrix parameters');
1309 },
1310
1311 clone: function() {
1312 return Matrix.create(this._a, this._c, this._b, this._d,
1313 this._tx, this._ty);
1314 },
1315
1316 set: function(a, c, b, d, tx, ty) {
1317 this._a = a;
1318 this._c = c;
1319 this._b = b;
1320 this._d = d;
1321 this._tx = tx;
1322 this._ty = ty;
1323 return this;
1324 },
1325
1326 scale: function(hor, ver , center) {
1327 if (arguments.length < 2 || typeof ver === 'object') {
1328 center = Point.read(arguments, 1);
1329 ver = hor;
1330 } else {
1331 center = Point.read(arguments, 2);
1332 }
1333 if (center)
1334 this.translate(center);
1335 this._a *= hor;
1336 this._c *= hor;
1337 this._b *= ver;
1338 this._d *= ver;
1339 if (center)
1340 this.translate(center.negate());
1341 return this;
1342 },
1343
1344 translate: function(point) {
1345 point = Point.read(arguments);
1346 var x = point.x, y = point.y;
1347 this._tx += x * this._a + y * this._b;
1348 this._ty += x * this._c + y * this._d;
1349 return this;
1350 },
1351
1352 rotate: function(angle, center) {
1353 return this.concatenate(
1354 Matrix.getRotateInstance.apply(Matrix, arguments));
1355 },
1356
1357 shear: function(hor, ver, center) {
1358 if (arguments.length < 2 || typeof ver === 'object') {
1359 center = Point.read(arguments, 1);
1360 ver = hor;
1361 } else {
1362 center = Point.read(arguments, 2);
1363 }
1364 if (center)
1365 this.translate(center);
1366 var a = this._a,
1367 c = this._c;
1368 this._a += ver * this._b;
1369 this._c += ver * this._d;
1370 this._b += hor * a;
1371 this._d += hor * c;
1372 if (center)
1373 this.translate(center.negate());
1374 return this;
1375 },
1376
1377 toString: function() {
1378 var format = Base.formatNumber;
1379 return '[[' + [format(this._a), format(this._b),
1380 format(this._tx)].join(', ') + '], ['
1381 + [format(this._c), format(this._d),
1382 format(this._ty)].join(', ') + ']]';
1383 },
1384
1385 getValues: function() {
1386 return [ this._a, this._c, this._b, this._d, this._tx, this._ty ];
1387 },
1388
1389 concatenate: function(mx) {
1390 var a = this._a,
1391 b = this._b,
1392 c = this._c,
1393 d = this._d;
1394 this._a = mx._a * a + mx._c * b;
1395 this._b = mx._b * a + mx._d * b;
1396 this._tx += mx._tx * a + mx._ty * b;
1397 this._c = mx._a * c + mx._c * d;
1398 this._d = mx._b * c + mx._d * d;
1399 this._ty += mx._tx * c + mx._ty * d;
1400 return this;
1401 },
1402
1403 preConcatenate: function(mx) {
1404 var a = this._a,
1405 b = this._b,
1406 c = this._c,
1407 d = this._d,
1408 tx = this._tx,
1409 ty = this._ty;
1410 this._a = mx._a * a + mx._b * c;
1411 this._c = mx._c * a + mx._d * c;
1412 this._b = mx._a * b + mx._b * d;
1413 this._d = mx._c * b + mx._d * d;
1414 this._tx = mx._a * tx + mx._b * ty + mx._tx;
1415 this._ty = mx._c * tx + mx._d * ty + mx._ty;
1416 return this;
1417 },
1418
1419 transform: function( src, srcOff, dst, dstOff, numPts) {
1420 return arguments.length < 5
1421 ? this._transformPoint(Point.read(arguments))
1422 : this._transformCoordinates(src, srcOff, dst, dstOff, numPts);
1423 },
1424
1425 _transformPoint: function(point, dest, dontNotify) {
1426 var x = point.x,
1427 y = point.y;
1428 if (!dest)
1429 dest = new Point(Point.dont);
1430 return dest.set(
1431 x * this._a + y * this._b + this._tx,
1432 x * this._c + y * this._d + this._ty,
1433 dontNotify
1434 );
1435 },
1436
1437 _transformCoordinates: function(src, srcOff, dst, dstOff, numPts) {
1438 var i = srcOff, j = dstOff,
1439 srcEnd = srcOff + 2 * numPts;
1440 while (i < srcEnd) {
1441 var x = src[i++];
1442 var y = src[i++];
1443 dst[j++] = x * this._a + y * this._b + this._tx;
1444 dst[j++] = x * this._c + y * this._d + this._ty;
1445 }
1446 return dst;
1447 },
1448
1449 _transformCorners: function(rect) {
1450 var x1 = rect.x,
1451 y1 = rect.y,
1452 x2 = x1 + rect.width,
1453 y2 = y1 + rect.height,
1454 coords = [ x1, y1, x2, y1, x2, y2, x1, y2 ];
1455 return this._transformCoordinates(coords, 0, coords, 0, 4);
1456 },
1457
1458 _transformBounds: function(bounds) {
1459 var coords = this._transformCorners(bounds),
1460 min = coords.slice(0, 2),
1461 max = coords.slice(0);
1462 for (var i = 2; i < 8; i++) {
1463 var val = coords[i],
1464 j = i & 1;
1465 if (val < min[j])
1466 min[j] = val;
1467 else if (val > max[j])
1468 max[j] = val;
1469 }
1470 return Rectangle.create(min[0], min[1],
1471 max[0] - min[0], max[1] - min[1]);
1472 },
1473
1474 getDeterminant: function() {
1475 return this._a * this._d - this._b * this._c;
1476 },
1477
1478 getTranslation: function() {
1479 return new Point(this._tx, this._ty);
1480 },
1481
1482 getScaling: function() {
1483 var hor = Math.sqrt(this._a * this._a + this._c * this._c),
1484 ver = Math.sqrt(this._b * this._b + this._d * this._d);
1485 return new Point(this._a < 0 ? -hor : hor, this._b < 0 ? -ver : ver);
1486 },
1487
1488 getRotation: function() {
1489 var angle1 = -Math.atan2(this._b, this._d),
1490 angle2 = Math.atan2(this._c, this._a);
1491 return Math.abs(angle1 - angle2) < Numerical.TOLERANCE
1492 ? angle1 * 180 / Math.PI : undefined;
1493 },
1494
1495 isIdentity: function() {
1496 return this._a == 1 && this._c == 0 && this._b == 0 && this._d == 1
1497 && this._tx == 0 && this._ty == 0;
1498 },
1499
1500 isInvertible: function() {
1501 var det = this.getDeterminant();
1502 return isFinite(det) && det != 0 && isFinite(this._tx)
1503 && isFinite(this._ty);
1504 },
1505
1506 isSingular: function() {
1507 return !this.isInvertible();
1508 },
1509
1510 createInverse: function() {
1511 var det = this.getDeterminant();
1512 if (isFinite(det) && det != 0 && isFinite(this._tx)
1513 && isFinite(this._ty)) {
1514 return Matrix.create(
1515 this._d / det,
1516 -this._c / det,
1517 -this._b / det,
1518 this._a / det,
1519 (this._b * this._ty - this._d * this._tx) / det,
1520 (this._c * this._tx - this._a * this._ty) / det);
1521 }
1522 return null;
1523 },
1524
1525 createShiftless: function() {
1526 return Matrix.create(this._a, this._c, this._b, this._d, 0, 0);
1527 },
1528
1529 setToScale: function(hor, ver) {
1530 return this.set(hor, 0, 0, ver, 0, 0);
1531 },
1532
1533 setToTranslation: function(delta) {
1534 delta = Point.read(arguments);
1535 return this.set(1, 0, 0, 1, delta.x, delta.y);
1536 },
1537
1538 setToShear: function(hor, ver) {
1539 return this.set(1, ver, hor, 1, 0, 0);
1540 },
1541
1542 setToRotation: function(angle, center) {
1543 center = Point.read(arguments, 1);
1544 angle = angle * Math.PI / 180;
1545 var x = center.x,
1546 y = center.y,
1547 cos = Math.cos(angle),
1548 sin = Math.sin(angle);
1549 return this.set(cos, sin, -sin, cos,
1550 x - x * cos + y * sin,
1551 y - x * sin - y * cos);
1552 },
1553
1554 applyToContext: function(ctx, reset) {
1555 ctx[reset ? 'setTransform' : 'transform'](
1556 this._a, this._c, this._b, this._d, this._tx, this._ty);
1557 return this;
1558 },
1559
1560 statics: {
1561 create: function(a, c, b, d, tx, ty) {
1562 return new Matrix(Matrix.dont).set(a, c, b, d, tx, ty);
1563 },
1564
1565 getScaleInstance: function(hor, ver) {
1566 var mx = new Matrix();
1567 return mx.setToScale.apply(mx, arguments);
1568 },
1569
1570 getTranslateInstance: function(delta) {
1571 var mx = new Matrix();
1572 return mx.setToTranslation.apply(mx, arguments);
1573 },
1574
1575 getShearInstance: function(hor, ver, center) {
1576 var mx = new Matrix();
1577 return mx.setToShear.apply(mx, arguments);
1578 },
1579
1580 getRotateInstance: function(angle, center) {
1581 var mx = new Matrix();
1582 return mx.setToRotation.apply(mx, arguments);
1583 }
1584 }
1585}, new function() {
1586 return Base.each({
1587 scaleX: '_a',
1588 scaleY: '_d',
1589 translateX: '_tx',
1590 translateY: '_ty',
1591 shearX: '_b',
1592 shearY: '_c'
1593 }, function(prop, name) {
1594 name = Base.capitalize(name);
1595 this['get' + name] = function() {
1596 return this[prop];
1597 };
1598 this['set' + name] = function(value) {
1599 this[prop] = value;
1600 };
1601 }, {});
1602});
1603
1604var Line = this.Line = Base.extend({
1605 initialize: function(point1, point2, infinite) {
1606 point1 = Point.read(arguments, 0, 1);
1607 point2 = Point.read(arguments, 1, 1);
1608 if (arguments.length == 3) {
1609 this.point = point1;
1610 this.vector = point2.subtract(point1);
1611 this.infinite = infinite;
1612 } else {
1613 this.point = point1;
1614 this.vector = point2;
1615 this.infinite = true;
1616 }
1617 },
1618
1619 intersect: function(line) {
1620 var cross = this.vector.cross(line.vector);
1621 if (Math.abs(cross) <= Numerical.EPSILON)
1622 return null;
1623 var v = line.point.subtract(this.point),
1624 t1 = v.cross(line.vector) / cross,
1625 t2 = v.cross(this.vector) / cross;
1626 return (this.infinite || 0 <= t1 && t1 <= 1)
1627 && (line.infinite || 0 <= t2 && t2 <= 1)
1628 ? this.point.add(this.vector.multiply(t1)) : null;
1629 },
1630
1631 getSide: function(point) {
1632 var v1 = this.vector,
1633 v2 = point.subtract(this.point),
1634 ccw = v2.cross(v1);
1635 if (ccw == 0) {
1636 ccw = v2.dot(v1);
1637 if (ccw > 0) {
1638 ccw = v2.subtract(v1).dot(v1);
1639 if (ccw < 0)
1640 ccw = 0;
1641 }
1642 }
1643 return ccw < 0 ? -1 : ccw > 0 ? 1 : 0;
1644 },
1645
1646 getDistance: function(point) {
1647 var m = this.vector.y / this.vector.x,
1648 b = this.point.y - (m * this.point.x);
1649 var dist = Math.abs(point.y - (m * point.x) - b) / Math.sqrt(m * m + 1);
1650 return this.infinite ? dist : Math.min(dist,
1651 point.getDistance(this.point),
1652 point.getDistance(this.point.add(this.vector)));
1653 }
1654});
1655
1656var Project = this.Project = PaperScopeItem.extend({
1657 _list: 'projects',
1658 _reference: 'project',
1659
1660 initialize: function() {
1661 this.base(true);
1662 this._currentStyle = new PathStyle();
1663 this._selectedItems = {};
1664 this._selectedItemCount = 0;
1665 this.layers = [];
1666 this.symbols = [];
1667 this.activeLayer = new Layer();
1668 },
1669
1670 _needsRedraw: function() {
1671 if (this._scope)
1672 this._scope._needsRedraw();
1673 },
1674
1675 getCurrentStyle: function() {
1676 return this._currentStyle;
1677 },
1678
1679 setCurrentStyle: function(style) {
1680 this._currentStyle.initialize(style);
1681 },
1682
1683 getIndex: function() {
1684 return this._index;
1685 },
1686
1687 getSelectedItems: function() {
1688 var items = [];
1689 Base.each(this._selectedItems, function(item) {
1690 items.push(item);
1691 });
1692 return items;
1693 },
1694
1695 _updateSelection: function(item) {
1696 if (item._selected) {
1697 this._selectedItemCount++;
1698 this._selectedItems[item.getId()] = item;
1699 } else {
1700 this._selectedItemCount--;
1701 delete this._selectedItems[item.getId()];
1702 }
1703 },
1704
1705 selectAll: function() {
1706 for (var i = 0, l = this.layers.length; i < l; i++)
1707 this.layers[i].setSelected(true);
1708 },
1709
1710 deselectAll: function() {
1711 for (var i in this._selectedItems)
1712 this._selectedItems[i].setSelected(false);
1713 },
1714
1715 hitTest: function(point, options) {
1716 options = HitResult.getOptions(point, options);
1717 point = options.point;
1718 for (var i = this.layers.length - 1; i >= 0; i--) {
1719 var res = this.layers[i].hitTest(point, options);
1720 if (res) return res;
1721 }
1722 return null;
1723 },
1724
1725 draw: function(ctx) {
1726 ctx.save();
1727 var param = { offset: new Point(0, 0) };
1728 for (var i = 0, l = this.layers.length; i < l; i++)
1729 Item.draw(this.layers[i], ctx, param);
1730 ctx.restore();
1731
1732 if (this._selectedItemCount > 0) {
1733 ctx.save();
1734 ctx.strokeWidth = 1;
1735 ctx.strokeStyle = ctx.fillStyle = '#009dec';
1736 param = { selection: true };
1737 Base.each(this._selectedItems, function(item) {
1738 item.draw(ctx, param);
1739 });
1740 ctx.restore();
1741 }
1742 }
1743});
1744
1745var Symbol = this.Symbol = Base.extend({
1746 initialize: function(item) {
1747 this.project = paper.project;
1748 this.project.symbols.push(this);
1749 this.setDefinition(item);
1750 this._instances = {};
1751 },
1752
1753 _changed: function(flags) {
1754 Base.each(this._instances, function(item) {
1755 item._changed(flags);
1756 });
1757 },
1758
1759 getDefinition: function() {
1760 return this._definition;
1761 },
1762
1763 setDefinition: function(item) {
1764 if (item._parentSymbol)
1765 item = item.clone();
1766 if (this._definition)
1767 delete this._definition._parentSymbol;
1768 this._definition = item;
1769 item.remove();
1770 item.setPosition(new Point());
1771 item._parentSymbol = this;
1772 this._changed(Change.GEOMETRY);
1773 },
1774
1775 place: function(position) {
1776 return new PlacedSymbol(this, position);
1777 },
1778
1779 clone: function() {
1780 return new Symbol(this._definition.clone());
1781 }
1782});
1783
1784var ChangeFlag = {
1785 APPEARANCE: 1,
1786 HIERARCHY: 2,
1787 GEOMETRY: 4,
1788 STROKE: 8,
1789 STYLE: 16,
1790 ATTRIBUTE: 32,
1791 CONTENT: 64,
1792 PIXELS: 128,
1793 CLIPPING: 256
1794};
1795
1796var Change = {
1797 HIERARCHY: ChangeFlag.HIERARCHY | ChangeFlag.APPEARANCE,
1798 GEOMETRY: ChangeFlag.GEOMETRY | ChangeFlag.APPEARANCE,
1799 STROKE: ChangeFlag.STROKE | ChangeFlag.STYLE | ChangeFlag.APPEARANCE,
1800 STYLE: ChangeFlag.STYLE | ChangeFlag.APPEARANCE,
1801 ATTRIBUTE: ChangeFlag.ATTRIBUTE | ChangeFlag.APPEARANCE,
1802 CONTENT: ChangeFlag.CONTENT | ChangeFlag.APPEARANCE,
1803 PIXELS: ChangeFlag.PIXELS | ChangeFlag.APPEARANCE
1804};
1805
1806var Item = this.Item = Base.extend({
1807 initialize: function() {
1808 this._id = ++Item._id;
1809 if (!this._project)
1810 paper.project.activeLayer.addChild(this);
1811 this._style = PathStyle.create(this);
1812 this.setStyle(this._project.getCurrentStyle());
1813 },
1814
1815 _changed: function(flags) {
1816 if (flags & ChangeFlag.GEOMETRY) {
1817 delete this._bounds;
1818 delete this._position;
1819 }
1820 if (flags & ChangeFlag.APPEARANCE) {
1821 if (this._project)
1822 this._project._needsRedraw();
1823 }
1824 if (this._parentSymbol)
1825 this._parentSymbol._changed(flags);
1826 },
1827
1828 getId: function() {
1829 return this._id;
1830 },
1831
1832 getName: function() {
1833 return this._name;
1834 },
1835
1836 setName: function(name) {
1837
1838 if (this._name)
1839 this._removeFromNamed();
1840 this._name = name || undefined;
1841 if (name) {
1842 var children = this._parent._children,
1843 namedChildren = this._parent._namedChildren;
1844 (namedChildren[name] = namedChildren[name] || []).push(this);
1845 children[name] = this;
1846 }
1847 this._changed(ChangeFlag.ATTRIBUTE);
1848 },
1849
1850 getPosition: function() {
1851 var pos = this._position
1852 || (this._position = this.getBounds().getCenter());
1853 return LinkedPoint.create(this, 'setPosition', pos._x, pos._y);
1854 },
1855
1856 setPosition: function(point) {
1857 this.translate(Point.read(arguments).subtract(this.getPosition()));
1858 },
1859
1860 getStyle: function() {
1861 return this._style;
1862 },
1863
1864 setStyle: function(style) {
1865 this._style.initialize(style);
1866 },
1867
1868 statics: {
1869 _id: 0
1870 }
1871}, new function() {
1872 return Base.each(['locked', 'visible', 'blendMode', 'opacity', 'guide'],
1873 function(name) {
1874 var part = Base.capitalize(name),
1875 name = '_' + name;
1876 this['get' + part] = function() {
1877 return this[name];
1878 };
1879 this['set' + part] = function(value) {
1880 if (value != this[name]) {
1881 this[name] = value;
1882 this._changed(name === '_locked'
1883 ? ChangeFlag.ATTRIBUTE : Change.ATTRIBUTE);
1884 }
1885 };
1886 }, {});
1887}, {
1888
1889 _locked: false,
1890
1891 _visible: true,
1892
1893 _blendMode: 'normal',
1894
1895 _opacity: 1,
1896
1897 _guide: false,
1898
1899 isSelected: function() {
1900 if (this._children) {
1901 for (var i = 0, l = this._children.length; i < l; i++)
1902 if (this._children[i].isSelected())
1903 return true;
1904 }
1905 return this._selected;
1906 },
1907
1908 setSelected: function(selected) {
1909 if (this._children) {
1910 for (var i = 0, l = this._children.length; i < l; i++) {
1911 this._children[i].setSelected(selected);
1912 }
1913 } else if ((selected = !!selected) != this._selected) {
1914 this._selected = selected;
1915 this._project._updateSelection(this);
1916 this._changed(Change.ATTRIBUTE);
1917 }
1918 },
1919
1920 _selected: false,
1921
1922 isFullySelected: function() {
1923 if (this._children && this._selected) {
1924 for (var i = 0, l = this._children.length; i < l; i++)
1925 if (!this._children[i].isFullySelected())
1926 return false;
1927 return true;
1928 }
1929 return this._selected;
1930 },
1931
1932 setFullySelected: function(selected) {
1933 if (this._children) {
1934 for (var i = 0, l = this._children.length; i < l; i++) {
1935 this._children[i].setFullySelected(selected);
1936 }
1937 }
1938 this.setSelected(selected);
1939 },
1940
1941 isClipMask: function() {
1942 return this._clipMask;
1943 },
1944
1945 setClipMask: function(clipMask) {
1946 if (this._clipMask != (clipMask = !!clipMask)) {
1947 this._clipMask = clipMask;
1948 if (clipMask) {
1949 this.setFillColor(null);
1950 this.setStrokeColor(null);
1951 }
1952 this._changed(Change.ATTRIBUTE);
1953 if (this._parent)
1954 this._parent._changed(ChangeFlag.CLIPPING);
1955 }
1956 },
1957
1958 _clipMask: false,
1959
1960 getProject: function() {
1961 return this._project;
1962 },
1963
1964 _setProject: function(project) {
1965 if (this._project != project) {
1966 this._project = project;
1967 if (this._children) {
1968 for (var i = 0, l = this._children.length; i < l; i++) {
1969 this._children[i]._setProject(project);
1970 }
1971 }
1972 }
1973 },
1974
1975 getParent: function() {
1976 return this._parent;
1977 },
1978
1979 getChildren: function() {
1980 return this._children;
1981 },
1982
1983 setChildren: function(items) {
1984 this.removeChildren();
1985 this.addChildren(items);
1986 },
1987
1988 getFirstChild: function() {
1989 return this._children && this._children[0] || null;
1990 },
1991
1992 getLastChild: function() {
1993 return this._children && this._children[this._children.length - 1]
1994 || null;
1995 },
1996
1997 getNextSibling: function() {
1998 return this._parent && this._parent._children[this._index + 1] || null;
1999 },
2000
2001 getPreviousSibling: function() {
2002 return this._parent && this._parent._children[this._index - 1] || null;
2003 },
2004
2005 getIndex: function() {
2006 return this._index;
2007 },
2008
2009 clone: function() {
2010 return this._clone(new this.constructor());
2011 },
2012
2013 _clone: function(copy) {
2014 copy.setStyle(this._style);
2015 if (this._children) {
2016 for (var i = 0, l = this._children.length; i < l; i++)
2017 copy.addChild(this._children[i].clone());
2018 }
2019 var keys = ['_locked', '_visible', '_blendMode', '_opacity',
2020 '_clipMask', '_guide'];
2021 for (var i = 0, l = keys.length; i < l; i++) {
2022 var key = keys[i];
2023 if (this.hasOwnProperty(key))
2024 copy[key] = this[key];
2025 }
2026 copy.setSelected(this._selected);
2027 if (this._name)
2028 copy.setName(this._name);
2029 return copy;
2030 },
2031
2032 copyTo: function(itemOrProject) {
2033 var copy = this.clone();
2034 if (itemOrProject.layers) {
2035 itemOrProject.activeLayer.addChild(copy);
2036 } else {
2037 itemOrProject.addChild(copy);
2038 }
2039 return copy;
2040 },
2041
2042 rasterize: function(resolution) {
2043 var bounds = this.getStrokeBounds(),
2044 scale = (resolution || 72) / 72,
2045 canvas = CanvasProvider.getCanvas(bounds.getSize().multiply(scale)),
2046 ctx = canvas.getContext('2d'),
2047 matrix = new Matrix().scale(scale).translate(-bounds.x, -bounds.y);
2048 matrix.applyToContext(ctx);
2049 this.draw(ctx, {});
2050 var raster = new Raster(canvas);
2051 raster.setPosition(this.getPosition());
2052 raster.scale(1 / scale);
2053 return raster;
2054 },
2055
2056 hitTest: function(point, options, matrix) {
2057 options = HitResult.getOptions(point, options);
2058 point = options.point;
2059 if (!this._children && !this.getRoughBounds(matrix)
2060 .expand(options.tolerance)._containsPoint(point))
2061 return null;
2062 if ((options.center || options.bounds) &&
2063 !(this instanceof Layer && !this._parent)) {
2064 var bounds = this.getBounds(),
2065 that = this,
2066 points = ['TopLeft', 'TopRight', 'BottomLeft', 'BottomRight',
2067 'LeftCenter', 'TopCenter', 'RightCenter', 'BottomCenter'],
2068 res;
2069 function checkBounds(type, part) {
2070 var pt = bounds['get' + part]().transform(matrix);
2071 if (point.getDistance(pt) < options.tolerance)
2072 return new HitResult(type, that,
2073 { name: Base.hyphenate(part), point: pt });
2074 }
2075 if (options.center && (res = checkBounds('center', 'Center')))
2076 return res;
2077 if (options.bounds) {
2078 for (var i = 0; i < 8; i++)
2079 if (res = checkBounds('bounds', points[i]))
2080 return res;
2081 }
2082 }
2083
2084 return this._children || !(options.guides && !this._guide
2085 || options.selected && !this._selected)
2086 ? this._hitTest(point, options, matrix) : null;
2087 },
2088
2089 _hitTest: function(point, options, matrix) {
2090 if (this._children) {
2091 for (var i = this._children.length - 1; i >= 0; i--) {
2092 var res = this._children[i].hitTest(point, options, matrix);
2093 if (res) return res;
2094 }
2095 }
2096 },
2097
2098 addChild: function(item) {
2099 return this.insertChild(undefined, item);
2100 },
2101
2102 insertChild: function(index, item) {
2103 if (this._children) {
2104 item._remove(false, true);
2105 Base.splice(this._children, [item], index, 0);
2106 item._parent = this;
2107 item._setProject(this._project);
2108 if (item._name)
2109 item.setName(item._name);
2110 this._changed(Change.HIERARCHY);
2111 return true;
2112 }
2113 return false;
2114 },
2115
2116 addChildren: function(items) {
2117 for (var i = 0, l = items && items.length; i < l; i++)
2118 this.insertChild(undefined, items[i]);
2119 },
2120
2121 insertChildren: function(index, items) {
2122 for (var i = 0, l = items && items.length; i < l; i++) {
2123 if (this.insertChild(index, items[i]))
2124 index++;
2125 }
2126 },
2127
2128 insertAbove: function(item) {
2129 return item._parent && item._parent.insertChild(
2130 item._index + 1, this);
2131 },
2132
2133 insertBelow: function(item) {
2134 return item._parent && item._parent.insertChild(
2135 item._index - 1, this);
2136 },
2137
2138 appendTop: function(item) {
2139 return this.addChild(item);
2140 },
2141
2142 appendBottom: function(item) {
2143 return this.insertChild(0, item);
2144 },
2145
2146 moveAbove: function(item) {
2147 return this.insertAbove(item);
2148 },
2149
2150 moveBelow: function(item) {
2151 return this.insertBelow(item);
2152 },
2153
2154 _removeFromNamed: function() {
2155 var children = this._parent._children,
2156 namedChildren = this._parent._namedChildren,
2157 name = this._name,
2158 namedArray = namedChildren[name],
2159 index = namedArray ? namedArray.indexOf(this) : -1;
2160 if (index == -1)
2161 return;
2162 if (children[name] == this)
2163 delete children[name];
2164 namedArray.splice(index, 1);
2165 if (namedArray.length) {
2166 children[name] = namedArray[namedArray.length - 1];
2167 } else {
2168 delete namedChildren[name];
2169 }
2170 },
2171
2172 _remove: function(deselect, notify) {
2173 if (this._parent) {
2174 if (deselect)
2175 this.setSelected(false);
2176 if (this._name)
2177 this._removeFromNamed();
2178 Base.splice(this._parent._children, null, this._index, 1);
2179 if (notify)
2180 this._parent._changed(Change.HIERARCHY);
2181 this._parent = null;
2182 return true;
2183 }
2184 return false;
2185 },
2186
2187 remove: function() {
2188 return this._remove(true, true);
2189 },
2190
2191 removeChildren: function(from, to) {
2192 if (!this._children)
2193 return null;
2194 from = from || 0;
2195 to = Base.pick(to, this._children.length);
2196 var removed = this._children.splice(from, to - from);
2197 for (var i = removed.length - 1; i >= 0; i--)
2198 removed[i]._remove(true, false);
2199 if (removed.length > 0)
2200 this._changed(Change.HIERARCHY);
2201 return removed;
2202 },
2203
2204 reverseChildren: function() {
2205 if (this._children) {
2206 this._children.reverse();
2207 for (var i = 0, l = this._children.length; i < l; i++)
2208 this._children[i]._index = i;
2209 this._changed(Change.HIERARCHY);
2210 }
2211 },
2212
2213 isEditable: function() {
2214 var item = this;
2215 while (item) {
2216 if (!item._visible || item._locked)
2217 return false;
2218 item = item._parent;
2219 }
2220 return true;
2221 },
2222
2223 _getOrder: function(item) {
2224 function getList(item) {
2225 var list = [];
2226 do {
2227 list.unshift(item);
2228 } while (item = item._parent)
2229 return list;
2230 }
2231 var list1 = getList(this),
2232 list2 = getList(item);
2233 for (var i = 0, l = Math.min(list1.length, list2.length); i < l; i++) {
2234 if (list1[i] != list2[i]) {
2235 return list1[i]._index < list2[i]._index ? 1 : -1;
2236 }
2237 }
2238 return 0;
2239 },
2240
2241 hasChildren: function() {
2242 return this._children && this._children.length > 0;
2243 },
2244
2245 isAbove: function(item) {
2246 return this._getOrder(item) == -1;
2247 },
2248
2249 isBelow: function(item) {
2250 return this._getOrder(item) == 1;
2251 },
2252
2253 isParent: function(item) {
2254 return this._parent == item;
2255 },
2256
2257 isChild: function(item) {
2258 return item && item._parent == this;
2259 },
2260
2261 isDescendant: function(item) {
2262 var parent = this;
2263 while (parent = parent._parent) {
2264 if (parent == item)
2265 return true;
2266 }
2267 return false;
2268 },
2269
2270 isAncestor: function(item) {
2271 return item ? item.isDescendant(this) : false;
2272 },
2273
2274 isGroupedWith: function(item) {
2275 var parent = this._parent;
2276 while (parent) {
2277 if (parent._parent
2278 && (parent instanceof Group || parent instanceof CompoundPath)
2279 && item.isDescendant(parent))
2280 return true;
2281 parent = parent._parent;
2282 }
2283 return false;
2284 },
2285
2286 _getBounds: function(getter, cacheName, args) {
2287 var children = this._children;
2288 if (!children || children.length == 0)
2289 return new Rectangle();
2290 var x1 = Infinity,
2291 x2 = -x1,
2292 y1 = x1,
2293 y2 = x2;
2294 for (var i = 0, l = children.length; i < l; i++) {
2295 var child = children[i];
2296 if (child._visible) {
2297 var rect = child[getter](args[0]);
2298 x1 = Math.min(rect.x, x1);
2299 y1 = Math.min(rect.y, y1);
2300 x2 = Math.max(rect.x + rect.width, x2);
2301 y2 = Math.max(rect.y + rect.height, y2);
2302 }
2303 }
2304 var bounds = Rectangle.create(x1, y1, x2 - x1, y2 - y1);
2305 return getter == 'getBounds' ? this._createBounds(bounds) : bounds;
2306 },
2307
2308 _createBounds: function(rect) {
2309 return LinkedRectangle.create(this, 'setBounds',
2310 rect.x, rect.y, rect.width, rect.height);
2311 },
2312
2313 getBounds: function() {
2314 return this._getBounds('getBounds', '_bounds', arguments);
2315 },
2316
2317 setBounds: function(rect) {
2318 rect = Rectangle.read(arguments);
2319 var bounds = this.getBounds(),
2320 matrix = new Matrix(),
2321 center = rect.getCenter();
2322 matrix.translate(center);
2323 if (rect.width != bounds.width || rect.height != bounds.height) {
2324 matrix.scale(
2325 bounds.width != 0 ? rect.width / bounds.width : 1,
2326 bounds.height != 0 ? rect.height / bounds.height : 1);
2327 }
2328 center = bounds.getCenter();
2329 matrix.translate(-center.x, -center.y);
2330 this.transform(matrix);
2331 },
2332
2333 getStrokeBounds: function() {
2334 return this._getBounds('getStrokeBounds', '_strokeBounds', arguments);
2335 },
2336
2337 getHandleBounds: function() {
2338 return this._getBounds('getHandleBounds', '_handleBounds', arguments);
2339 },
2340
2341 getRoughBounds: function() {
2342 return this._getBounds('getRoughBounds', '_roughBounds', arguments);
2343 },
2344
2345 scale: function(hor, ver , center) {
2346 if (arguments.length < 2 || typeof ver === 'object') {
2347 center = ver;
2348 ver = hor;
2349 }
2350 return this.transform(new Matrix().scale(hor, ver,
2351 center || this.getPosition()));
2352 },
2353
2354 translate: function(delta) {
2355 var mx = new Matrix();
2356 return this.transform(mx.translate.apply(mx, arguments));
2357 },
2358
2359 rotate: function(angle, center) {
2360 return this.transform(new Matrix().rotate(angle,
2361 center || this.getPosition()));
2362 },
2363
2364 shear: function(hor, ver, center) {
2365 if (arguments.length < 2 || typeof ver === 'object') {
2366 center = ver;
2367 ver = hor;
2368 }
2369 return this.transform(new Matrix().shear(hor, ver,
2370 center || this.getPosition()));
2371 },
2372
2373 transform: function(matrix, flags) {
2374 var bounds = this._bounds,
2375 position = this._position,
2376 children = this._children;
2377 if (this._transform) {
2378 this._transform(matrix, flags);
2379 this._changed(Change.GEOMETRY);
2380 }
2381 if (bounds && matrix.getRotation() % 90 === 0) {
2382 this._bounds = this._createBounds(
2383 matrix._transformBounds(bounds));
2384 this._position = this._bounds.getCenter();
2385 } else if (position) {
2386 this._position = matrix._transformPoint(position, position, true);
2387 }
2388 for (var i = 0, l = children && children.length; i < l; i++)
2389 children[i].transform(matrix, flags);
2390 return this;
2391 },
2392
2393 fitBounds: function(rectangle, fill) {
2394 rectangle = Rectangle.read(arguments);
2395 var bounds = this.getBounds(),
2396 itemRatio = bounds.height / bounds.width,
2397 rectRatio = rectangle.height / rectangle.width,
2398 scale = (fill ? itemRatio > rectRatio : itemRatio < rectRatio)
2399 ? rectangle.width / bounds.width
2400 : rectangle.height / bounds.height,
2401 delta = rectangle.getCenter().subtract(bounds.getCenter()),
2402 newBounds = new Rectangle(new Point(),
2403 new Size(bounds.width * scale, bounds.height * scale));
2404 newBounds.setCenter(rectangle.getCenter());
2405 this.setBounds(newBounds);
2406 },
2407
2408 toString: function() {
2409 return (this.constructor._name || 'Item') + (this._name
2410 ? " '" + this._name + "'"
2411 : ' @' + this._id);
2412 },
2413
2414 statics: {
2415 drawSelectedBounds: function(bounds, ctx, matrix) {
2416 var coords = matrix._transformCorners(bounds);
2417 ctx.beginPath();
2418 for (var i = 0; i < 8; i++)
2419 ctx[i == 0 ? 'moveTo' : 'lineTo'](coords[i], coords[++i]);
2420 ctx.closePath();
2421 ctx.stroke();
2422 for (var i = 0; i < 8; i++) {
2423 ctx.beginPath();
2424 ctx.rect(coords[i] - 2, coords[++i] - 2, 4, 4);
2425 ctx.fill();
2426 }
2427 },
2428
2429 draw: function(item, ctx, param) {
2430 if (!item._visible || item._opacity == 0)
2431 return;
2432
2433 var tempCanvas, parentCtx;
2434 if (item._blendMode !== 'normal'
2435 || item._opacity < 1
2436 && !(item._segments && (!item.getFillColor()
2437 || !item.getStrokeColor()))) {
2438 var bounds = item.getStrokeBounds() || item.getBounds();
2439 if (!bounds.width || !bounds.height)
2440 return;
2441
2442 var itemOffset = bounds.getTopLeft().floor(),
2443 size = bounds.getSize().ceil().add(new Size(1, 1));
2444 tempCanvas = CanvasProvider.getCanvas(size);
2445
2446 parentCtx = ctx;
2447
2448 ctx = tempCanvas.getContext('2d');
2449 ctx.save();
2450
2451 ctx.translate(-itemOffset.x, -itemOffset.y);
2452 }
2453 var savedOffset;
2454 if (itemOffset) {
2455 savedOffset = param.offset;
2456 param.offset = itemOffset;
2457 }
2458 item.draw(ctx, param);
2459 if (itemOffset)
2460 param.offset = savedOffset;
2461
2462 if (tempCanvas) {
2463
2464 ctx.restore();
2465
2466 if (item._blendMode !== 'normal') {
2467 var pixelOffset = itemOffset.subtract(param.offset);
2468 BlendMode.process(item._blendMode, ctx, parentCtx,
2469 item._opacity, pixelOffset);
2470 } else {
2471 parentCtx.save();
2472 parentCtx.globalAlpha = item._opacity;
2473 parentCtx.drawImage(tempCanvas,
2474 itemOffset.x, itemOffset.y);
2475 parentCtx.restore();
2476 }
2477
2478 CanvasProvider.returnCanvas(tempCanvas);
2479 }
2480 }
2481 }
2482}, new function() {
2483
2484 var sets = {
2485 down: {}, drag: {}, up: {}, move: {}
2486 };
2487
2488 function removeAll(set) {
2489 for (var id in set) {
2490 var item = set[id];
2491 item.remove();
2492 for (var type in sets) {
2493 var other = sets[type];
2494 if (other != set && other[item.getId()])
2495 delete other[item.getId()];
2496 }
2497 }
2498 }
2499
2500 function installHandler(name) {
2501 var handler = 'onMouse' + Base.capitalize(name);
2502 var func = paper.tool[handler];
2503 if (!func || !func._installed) {
2504 var hash = {};
2505 hash[handler] = function(event) {
2506 if (name === 'up')
2507 sets.drag = {};
2508 removeAll(sets[name]);
2509 sets[name] = {};
2510 if (this.base)
2511 this.base(event);
2512 };
2513 paper.tool.inject(hash);
2514 paper.tool[handler]._installed = true;
2515 }
2516 }
2517
2518 return Base.each(['down', 'drag', 'up', 'move'], function(name) {
2519 this['removeOn' + Base.capitalize(name)] = function() {
2520 var hash = {};
2521 hash[name] = true;
2522 return this.removeOn(hash);
2523 };
2524 }, {
2525 removeOn: function(obj) {
2526 for (var name in obj) {
2527 if (obj[name]) {
2528 sets[name][this.getId()] = this;
2529 if (name === 'drag')
2530 installHandler('up');
2531 installHandler(name);
2532 }
2533 }
2534 return this;
2535 }
2536 });
2537});
2538
2539var Group = this.Group = Item.extend({
2540 initialize: function(items) {
2541 this.base();
2542 this._children = [];
2543 this._namedChildren = {};
2544 this.addChildren(!items || !Array.isArray(items)
2545 || typeof items[0] !== 'object' ? arguments : items);
2546 },
2547
2548 _changed: function(flags) {
2549 Item.prototype._changed.call(this, flags);
2550 if (flags & (ChangeFlag.HIERARCHY | ChangeFlag.CLIPPING)) {
2551 delete this._clipItem;
2552 }
2553 },
2554
2555 _getClipItem: function() {
2556 if (this._clipItem !== undefined)
2557 return this._clipItem;
2558 for (var i = 0, l = this._children.length; i < l; i++) {
2559 var child = this._children[i];
2560 if (child._clipMask)
2561 return this._clipItem = child;
2562 }
2563 return this._clipItem = null;
2564 },
2565
2566 isClipped: function() {
2567 return !!this._getClipItem();
2568 },
2569
2570 setClipped: function(clipped) {
2571 var child = this.getFirstChild();
2572 if (child)
2573 child.setClipMask(clipped);
2574 return this;
2575 },
2576
2577 draw: function(ctx, param) {
2578 var clipItem = this._getClipItem();
2579 if (clipItem)
2580 Item.draw(clipItem, ctx, param);
2581 for (var i = 0, l = this._children.length; i < l; i++) {
2582 var item = this._children[i];
2583 if (item != clipItem)
2584 Item.draw(item, ctx, param);
2585 }
2586 }
2587});
2588
2589var Layer = this.Layer = Group.extend({
2590 initialize: function(items) {
2591 this._project = paper.project;
2592 this._index = this._project.layers.push(this) - 1;
2593 this.base.apply(this, arguments);
2594 this.activate();
2595 },
2596
2597 _remove: function(deselect, notify) {
2598 if (this._parent)
2599 return this.base(deselect, notify);
2600 if (this._index != null) {
2601 if (deselect)
2602 this.setSelected(false);
2603 Base.splice(this._project.layers, null, this._index, 1);
2604 this._project._needsRedraw();
2605 return true;
2606 }
2607 return false;
2608 },
2609
2610 getNextSibling: function() {
2611 return this._parent ? this.base()
2612 : this._project.layers[this._index + 1] || null;
2613 },
2614
2615 getPreviousSibling: function() {
2616 return this._parent ? this.base()
2617 : this._project.layers[this._index - 1] || null;
2618 },
2619
2620 activate: function() {
2621 this._project.activeLayer = this;
2622 }
2623}, new function () {
2624 function insert(above) {
2625 return function(item) {
2626 if (item instanceof Layer && !item._parent
2627 && this._remove(false, true)) {
2628 Base.splice(item._project.layers, [this],
2629 item._index + (above ? 1 : -1), 0);
2630 this._setProject(item._project);
2631 return true;
2632 }
2633 return this.base(item);
2634 };
2635 }
2636
2637 return {
2638 insertAbove: insert(true),
2639
2640 insertBelow: insert(false)
2641 };
2642});
2643
2644var PlacedItem = this.PlacedItem = Item.extend({
2645
2646 _transform: function(matrix, flags) {
2647 this._matrix.preConcatenate(matrix);
2648 },
2649
2650 _changed: function(flags) {
2651 Item.prototype._changed.call(this, flags);
2652 if (flags & ChangeFlag.GEOMETRY) {
2653 delete this._strokeBounds;
2654 delete this._handleBounds;
2655 delete this._roughBounds;
2656 }
2657 },
2658
2659 getMatrix: function() {
2660 return this._matrix;
2661 },
2662
2663 setMatrix: function(matrix) {
2664 this._matrix = matrix.clone();
2665 this._changed(Change.GEOMETRY);
2666 },
2667
2668 getBounds: function() {
2669 var useCache = arguments[0] === undefined;
2670 if (useCache && this._bounds)
2671 return this._bounds;
2672 var bounds = this.getStrokeBounds(arguments[0]);
2673 if (useCache)
2674 bounds = this._bounds = this._createBounds(bounds);
2675 return bounds;
2676 },
2677
2678 _getBounds: function(getter, cacheName, args) {
2679 var matrix = args[0],
2680 useCache = matrix === undefined;
2681 if (useCache && this[cacheName])
2682 return this[cacheName];
2683 matrix = matrix ? matrix.clone().concatenate(this._matrix)
2684 : this._matrix;
2685 var bounds = this._calculateBounds(getter, matrix);
2686 if (useCache)
2687 this[cacheName] = bounds;
2688 return bounds;
2689 }
2690});
2691
2692var Raster = this.Raster = PlacedItem.extend({
2693 initialize: function(object) {
2694 this.base();
2695 if (object.getContext) {
2696 this.setCanvas(object);
2697 } else {
2698 if (typeof object === 'string')
2699 object = document.getElementById(object);
2700 this.setImage(object);
2701 }
2702 this._matrix = new Matrix();
2703 },
2704
2705 clone: function() {
2706 var image = this._image;
2707 if (!image) {
2708 image = CanvasProvider.getCanvas(this._size);
2709 image.getContext('2d').drawImage(this._canvas, 0, 0);
2710 }
2711 var copy = new Raster(image);
2712 copy._matrix = this._matrix.clone();
2713 return this._clone(copy);
2714 },
2715
2716 getSize: function() {
2717 return this._size;
2718 },
2719
2720 setSize: function() {
2721 var size = Size.read(arguments),
2722 image = this.getImage();
2723 this.setCanvas(CanvasProvider.getCanvas(size));
2724 this.getContext(true).drawImage(image, 0, 0, size.width, size.height);
2725 },
2726
2727 getWidth: function() {
2728 return this._size.width;
2729 },
2730
2731 getHeight: function() {
2732 return this._size.height;
2733 },
2734
2735 getPpi: function() {
2736 var matrix = this._matrix,
2737 orig = new Point(0, 0).transform(matrix),
2738 u = new Point(1, 0).transform(matrix).subtract(orig),
2739 v = new Point(0, 1).transform(matrix).subtract(orig);
2740 return new Size(
2741 72 / u.getLength(),
2742 72 / v.getLength()
2743 );
2744 },
2745
2746 getContext: function() {
2747 if (!this._context)
2748 this._context = this.getCanvas().getContext('2d');
2749 if (arguments[0])
2750 this._changed(Change.PIXELS);
2751 return this._context;
2752 },
2753
2754 setContext: function(context) {
2755 this._context = context;
2756 },
2757
2758 getCanvas: function() {
2759 if (!this._canvas) {
2760 this._canvas = CanvasProvider.getCanvas(this._size);
2761 if (this._image)
2762 this.getContext(true).drawImage(this._image, 0, 0);
2763 }
2764 return this._canvas;
2765 },
2766
2767 setCanvas: function(canvas) {
2768 if (this._canvas)
2769 CanvasProvider.returnCanvas(this._canvas);
2770 this._canvas = canvas;
2771 this._size = new Size(canvas.width, canvas.height);
2772 this._image = null;
2773 this._context = null;
2774 this._changed(Change.GEOMETRY);
2775 },
2776
2777 getImage: function() {
2778 return this._image || this.getCanvas();
2779 },
2780
2781 setImage: function(image) {
2782 if (this._canvas)
2783 CanvasProvider.returnCanvas(this._canvas);
2784 this._image = image;
2785 this._size = new Size(image.naturalWidth, image.naturalHeight);
2786 this._canvas = null;
2787 this._context = null;
2788 this._changed(Change.GEOMETRY);
2789 },
2790
2791 getSubImage: function(rect) {
2792 rect = Rectangle.read(arguments);
2793 var canvas = CanvasProvider.getCanvas(rect.getSize());
2794 canvas.getContext('2d').drawImage(this.getCanvas(), rect.x, rect.y,
2795 canvas.width, canvas.height, 0, 0, canvas.width, canvas.height);
2796 return canvas;
2797 },
2798
2799 drawImage: function(image, point) {
2800 point = Point.read(arguments, 1);
2801 this.getContext(true).drawImage(image, point.x, point.y);
2802 },
2803
2804 getAverageColor: function(object) {
2805 if (!object)
2806 object = this.getBounds();
2807 var bounds, path;
2808 if (object instanceof PathItem) {
2809 path = object;
2810 bounds = object.getBounds();
2811 } else if (object.width) {
2812 bounds = new Rectangle(object);
2813 } else if (object.x) {
2814 bounds = Rectangle.create(object.x - 0.5, object.y - 0.5, 1, 1);
2815 }
2816 var sampleSize = 32,
2817 width = Math.min(bounds.width, sampleSize),
2818 height = Math.min(bounds.height, sampleSize);
2819 var ctx = Raster._sampleContext;
2820 if (!ctx) {
2821 ctx = Raster._sampleContext = CanvasProvider.getCanvas(
2822 new Size(sampleSize)).getContext('2d');
2823 } else {
2824 ctx.clearRect(0, 0, sampleSize, sampleSize);
2825 }
2826 ctx.save();
2827 ctx.scale(width / bounds.width, height / bounds.height);
2828 ctx.translate(-bounds.x, -bounds.y);
2829 if (path)
2830 path.draw(ctx, { clip: true });
2831 this._matrix.applyToContext(ctx);
2832 ctx.drawImage(this._canvas || this._image,
2833 -this._size.width / 2, -this._size.height / 2);
2834 ctx.restore();
2835 var pixels = ctx.getImageData(0.5, 0.5, Math.ceil(width),
2836 Math.ceil(height)).data,
2837 channels = [0, 0, 0],
2838 total = 0;
2839 for (var i = 0, l = pixels.length; i < l; i += 4) {
2840 var alpha = pixels[i + 3];
2841 total += alpha;
2842 alpha /= 255;
2843 channels[0] += pixels[i] * alpha;
2844 channels[1] += pixels[i + 1] * alpha;
2845 channels[2] += pixels[i + 2] * alpha;
2846 }
2847 for (var i = 0; i < 3; i++)
2848 channels[i] /= total;
2849 return total ? Color.read(channels) : null;
2850 },
2851
2852 getPixel: function(point) {
2853 point = Point.read(arguments);
2854 var pixels = this.getContext().getImageData(point.x, point.y, 1, 1).data,
2855 channels = new Array(4);
2856 for (var i = 0; i < 4; i++)
2857 channels[i] = pixels[i] / 255;
2858 return RGBColor.read(channels);
2859 },
2860
2861 setPixel: function(point, color) {
2862 var hasPoint = arguments.length == 2;
2863 point = Point.read(arguments, 0, hasPoint ? 1 : 2);
2864 color = Color.read(arguments, hasPoint ? 1 : 2);
2865 var ctx = this.getContext(true),
2866 imageData = ctx.createImageData(1, 1),
2867 alpha = color.getAlpha();
2868 imageData.data[0] = color.getRed() * 255;
2869 imageData.data[1] = color.getGreen() * 255;
2870 imageData.data[2] = color.getBlue() * 255;
2871 imageData.data[3] = alpha != null ? alpha * 255 : 255;
2872 ctx.putImageData(imageData, point.x, point.y);
2873 },
2874
2875 createData: function(size) {
2876 size = Size.read(arguments);
2877 return this.getContext().createImageData(size.width, size.height);
2878 },
2879
2880 getData: function(rect) {
2881 rect = Rectangle.read(arguments);
2882 if (rect.isEmpty())
2883 rect = new Rectangle(this.getSize());
2884 return this.getContext().getImageData(rect.x, rect.y,
2885 rect.width, rect.height);
2886 },
2887
2888 setData: function(data, point) {
2889 point = Point.read(arguments, 1);
2890 this.getContext(true).putImageData(data, point.x, point.y);
2891 },
2892
2893 _calculateBounds: function(getter, matrix) {
2894 return matrix._transformBounds(
2895 new Rectangle(this._size).setCenter(0, 0));
2896 },
2897
2898 getHandleBounds: function() {
2899 return this.getStrokeBounds(arguments[0]);
2900 },
2901
2902 getRoughBounds: function() {
2903 return this.getStrokeBounds(arguments[0]);
2904 },
2905
2906 draw: function(ctx, param) {
2907 if (param.selection) {
2908 var bounds = new Rectangle(this._size).setCenter(0, 0);
2909 Item.drawSelectedBounds(bounds, ctx, this._matrix);
2910 } else {
2911 ctx.save();
2912 this._matrix.applyToContext(ctx);
2913 ctx.drawImage(this._canvas || this._image,
2914 -this._size.width / 2, -this._size.height / 2);
2915 ctx.restore();
2916 }
2917 }
2918});
2919
2920var PlacedSymbol = this.PlacedSymbol = PlacedItem.extend({
2921 initialize: function(symbol, matrixOrOffset) {
2922 this.base();
2923 this.setSymbol(symbol instanceof Symbol ? symbol : new Symbol(symbol));
2924 this._matrix = matrixOrOffset !== undefined
2925 ? matrixOrOffset instanceof Matrix
2926 ? matrixOrOffset
2927 : new Matrix().translate(Point.read(arguments, 1))
2928 : new Matrix();
2929 },
2930
2931 getSymbol: function() {
2932 return this._symbol;
2933 },
2934
2935 setSymbol: function(symbol) {
2936 if (this._symbol)
2937 delete this._symbol._instances[this._id];
2938 this._symbol = symbol;
2939 symbol._instances[this._id] = this;
2940 },
2941
2942 clone: function() {
2943 return this._clone(new PlacedSymbol(this.symbol, this._matrix.clone()));
2944 },
2945
2946 _calculateBounds: function(getter, matrix) {
2947 return this.symbol._definition[getter](matrix);
2948 },
2949
2950 draw: function(ctx, param) {
2951 if (param.selection) {
2952 Item.drawSelectedBounds(this.symbol._definition.getStrokeBounds(),
2953 ctx, this._matrix);
2954 } else {
2955 ctx.save();
2956 this._matrix.applyToContext(ctx);
2957 Item.draw(this.symbol.getDefinition(), ctx, param);
2958 ctx.restore();
2959 }
2960 }
2961
2962});
2963
2964HitResult = Base.extend({
2965 initialize: function(type, item, values) {
2966 this.type = type;
2967 this.item = item;
2968 if (values) {
2969 Base.each(values, function(value, key) {
2970 this[key] = value;
2971 }, this);
2972 }
2973 },
2974
2975 statics: {
2976 getOptions: function(point, options) {
2977 return options && options._merged ? options : Base.merge({
2978 point: Point.read(arguments, 0, 1),
2979 type: null,
2980 tolerance: 2,
2981 fill: !options,
2982 stroke: !options,
2983 segments: !options,
2984 handles: false,
2985 ends: false,
2986 center: false,
2987 bounds: false,
2988 guides: false,
2989 selected: false,
2990 _merged: true
2991 }, options);
2992 }
2993 }
2994});
2995
2996var Segment = this.Segment = Base.extend({
2997 initialize: function(arg0, arg1, arg2, arg3, arg4, arg5) {
2998 var count = arguments.length,
2999 createPoint = SegmentPoint.create,
3000 point, handleIn, handleOut;
3001 if (count == 0) {
3002 } else if (count == 1) {
3003 if (arg0.point) {
3004 point = arg0.point;
3005 handleIn = arg0.handleIn;
3006 handleOut = arg0.handleOut;
3007 } else {
3008 point = arg0;
3009 }
3010 } else if (count < 6) {
3011 if (count == 2 && arg1.x === undefined) {
3012 point = [ arg0, arg1 ];
3013 } else {
3014 point = arg0;
3015 handleIn = arg1;
3016 handleOut = arg2;
3017 }
3018 } else if (count == 6) {
3019 point = [ arg0, arg1 ];
3020 handleIn = [ arg2, arg3 ];
3021 handleOut = [ arg4, arg5 ];
3022 }
3023 createPoint(this, '_point', point);
3024 createPoint(this, '_handleIn', handleIn);
3025 createPoint(this, '_handleOut', handleOut);
3026 },
3027
3028 _changed: function(point) {
3029 if (!this._path)
3030 return;
3031 var curve = this._path._curves && this.getCurve(), other;
3032 if (curve) {
3033 curve._changed();
3034 if (other = (curve[point == this._point
3035 || point == this._handleIn && curve._segment1 == this
3036 ? 'getPrevious' : 'getNext']())) {
3037 other._changed();
3038 }
3039 }
3040 this._path._changed(Change.GEOMETRY);
3041 },
3042
3043 getPoint: function() {
3044 return this._point;
3045 },
3046
3047 setPoint: function(point) {
3048 point = Point.read(arguments);
3049 this._point.set(point.x, point.y);
3050 },
3051
3052 getHandleIn: function() {
3053 return this._handleIn;
3054 },
3055
3056 setHandleIn: function(point) {
3057 point = Point.read(arguments);
3058 this._handleIn.set(point.x, point.y);
3059 },
3060
3061 getHandleOut: function() {
3062 return this._handleOut;
3063 },
3064
3065 setHandleOut: function(point) {
3066 point = Point.read(arguments);
3067 this._handleOut.set(point.x, point.y);
3068 },
3069
3070 _isSelected: function(point) {
3071 var state = this._selectionState;
3072 return point == this._point ? !!(state & SelectionState.POINT)
3073 : point == this._handleIn ? !!(state & SelectionState.HANDLE_IN)
3074 : point == this._handleOut ? !!(state & SelectionState.HANDLE_OUT)
3075 : false;
3076 },
3077
3078 _setSelected: function(point, selected) {
3079 var path = this._path,
3080 selected = !!selected,
3081 state = this._selectionState || 0,
3082 selection = [
3083 !!(state & SelectionState.POINT),
3084 !!(state & SelectionState.HANDLE_IN),
3085 !!(state & SelectionState.HANDLE_OUT)
3086 ];
3087 if (point == this._point) {
3088 if (selected) {
3089 selection[1] = selection[2] = false;
3090 } else {
3091 var previous = this.getPrevious(),
3092 next = this.getNext();
3093 selection[1] = previous && (previous._point.isSelected()
3094 || previous._handleOut.isSelected());
3095 selection[2] = next && (next._point.isSelected()
3096 || next._handleIn.isSelected());
3097 }
3098 selection[0] = selected;
3099 } else {
3100 var index = point == this._handleIn ? 1 : 2;
3101 if (selection[index] != selected) {
3102 if (selected)
3103 selection[0] = false;
3104 selection[index] = selected;
3105 }
3106 }
3107 this._selectionState = (selection[0] ? SelectionState.POINT : 0)
3108 | (selection[1] ? SelectionState.HANDLE_IN : 0)
3109 | (selection[2] ? SelectionState.HANDLE_OUT : 0);
3110 if (path && state != this._selectionState)
3111 path._updateSelection(this, state, this._selectionState);
3112 },
3113
3114 isSelected: function() {
3115 return this._isSelected(this._point);
3116 },
3117
3118 setSelected: function(selected) {
3119 this._setSelected(this._point, selected);
3120 },
3121
3122 getIndex: function() {
3123 return this._index !== undefined ? this._index : null;
3124 },
3125
3126 getPath: function() {
3127 return this._path || null;
3128 },
3129
3130 getCurve: function() {
3131 if (this._path) {
3132 var index = this._index;
3133 if (!this._path._closed && index == this._path._segments.length - 1)
3134 index--;
3135 return this._path.getCurves()[index] || null;
3136 }
3137 return null;
3138 },
3139
3140 getNext: function() {
3141 var segments = this._path && this._path._segments;
3142 return segments && (segments[this._index + 1]
3143 || this._path._closed && segments[0]) || null;
3144 },
3145
3146 getPrevious: function() {
3147 var segments = this._path && this._path._segments;
3148 return segments && (segments[this._index - 1]
3149 || this._path._closed && segments[segments.length - 1]) || null;
3150 },
3151
3152 reverse: function() {
3153 return new Segment(this._point, this._handleOut, this._handleIn);
3154 },
3155
3156 remove: function() {
3157 return this._path ? !!this._path.removeSegment(this._index) : false;
3158 },
3159
3160 toString: function() {
3161 var parts = [ 'point: ' + this._point ];
3162 if (!this._handleIn.isZero())
3163 parts.push('handleIn: ' + this._handleIn);
3164 if (!this._handleOut.isZero())
3165 parts.push('handleOut: ' + this._handleOut);
3166 return '{ ' + parts.join(', ') + ' }';
3167 },
3168
3169 _transformCoordinates: function(matrix, coords, change) {
3170 var point = this._point,
3171 handleIn = !change || !this._handleIn.isZero()
3172 ? this._handleIn : null,
3173 handleOut = !change || !this._handleOut.isZero()
3174 ? this._handleOut : null,
3175 x = point._x,
3176 y = point._y,
3177 i = 2;
3178 coords[0] = x;
3179 coords[1] = y;
3180 if (handleIn) {
3181 coords[i++] = handleIn._x + x;
3182 coords[i++] = handleIn._y + y;
3183 }
3184 if (handleOut) {
3185 coords[i++] = handleOut._x + x;
3186 coords[i++] = handleOut._y + y;
3187 }
3188 if (!matrix)
3189 return;
3190 matrix._transformCoordinates(coords, 0, coords, 0, i / 2);
3191 x = coords[0];
3192 y = coords[1];
3193 if (change) {
3194 point._x = x;
3195 point._y = y;
3196 i = 2;
3197 if (handleIn) {
3198 handleIn._x = coords[i++] - x;
3199 handleIn._y = coords[i++] - y;
3200 }
3201 if (handleOut) {
3202 handleOut._x = coords[i++] - x;
3203 handleOut._y = coords[i++] - y;
3204 }
3205 } else {
3206 if (!handleIn) {
3207 coords[i++] = x;
3208 coords[i++] = y;
3209 }
3210 if (!handleOut) {
3211 coords[i++] = x;
3212 coords[i++] = y;
3213 }
3214 }
3215 }
3216});
3217
3218var SegmentPoint = Point.extend({
3219 set: function(x, y) {
3220 this._x = x;
3221 this._y = y;
3222 this._owner._changed(this);
3223 return this;
3224 },
3225
3226 getX: function() {
3227 return this._x;
3228 },
3229
3230 setX: function(x) {
3231 this._x = x;
3232 this._owner._changed(this);
3233 },
3234
3235 getY: function() {
3236 return this._y;
3237 },
3238
3239 setY: function(y) {
3240 this._y = y;
3241 this._owner._changed(this);
3242 },
3243
3244 setSelected: function(selected) {
3245 this._owner._setSelected(this, selected);
3246 },
3247
3248 isSelected: function() {
3249 return this._owner._isSelected(this);
3250 },
3251
3252 statics: {
3253 create: function(segment, key, pt) {
3254 var point = new SegmentPoint(SegmentPoint.dont),
3255 x, y, selected;
3256 if (!pt) {
3257 x = y = 0;
3258 } else if ((x = pt[0]) !== undefined) {
3259 y = pt[1];
3260 } else {
3261 if ((x = pt.x) === undefined) {
3262 pt = Point.read(arguments, 2, 1);
3263 x = pt.x;
3264 }
3265 y = pt.y;
3266 selected = pt.selected;
3267 }
3268 point._x = x;
3269 point._y = y;
3270 point._owner = segment;
3271 segment[key] = point;
3272 if (selected)
3273 point.setSelected(true);
3274 return point;
3275 }
3276 }
3277});
3278
3279var SelectionState = {
3280 HANDLE_IN: 1,
3281 HANDLE_OUT: 2,
3282 POINT: 4
3283};
3284
3285var Curve = this.Curve = Base.extend({
3286 initialize: function(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7) {
3287 var count = arguments.length;
3288 if (count == 0) {
3289 this._segment1 = new Segment();
3290 this._segment2 = new Segment();
3291 } else if (count == 1) {
3292 this._segment1 = new Segment(arg0.segment1);
3293 this._segment2 = new Segment(arg0.segment2);
3294 } else if (count == 2) {
3295 this._segment1 = new Segment(arg0);
3296 this._segment2 = new Segment(arg1);
3297 } else if (count == 4) {
3298 this._segment1 = new Segment(arg0, null, arg1);
3299 this._segment2 = new Segment(arg3, arg2, null);
3300 } else if (count == 8) {
3301 var p1 = Point.create(arg0, arg1),
3302 p2 = Point.create(arg6, arg7);
3303 this._segment1 = new Segment(p1, null,
3304 Point.create(arg2, arg3).subtract(p1));
3305 this._segment2 = new Segment(p2,
3306 Point.create(arg4, arg5).subtract(p2), null);
3307 }
3308 },
3309
3310 _changed: function() {
3311 delete this._length;
3312 },
3313
3314 getPoint1: function() {
3315 return this._segment1._point;
3316 },
3317
3318 setPoint1: function(point) {
3319 point = Point.read(arguments);
3320 this._segment1._point.set(point.x, point.y);
3321 },
3322
3323 getPoint2: function() {
3324 return this._segment2._point;
3325 },
3326
3327 setPoint2: function(point) {
3328 point = Point.read(arguments);
3329 this._segment2._point.set(point.x, point.y);
3330 },
3331
3332 getHandle1: function() {
3333 return this._segment1._handleOut;
3334 },
3335
3336 setHandle1: function(point) {
3337 point = Point.read(arguments);
3338 this._segment1._handleOut.set(point.x, point.y);
3339 },
3340
3341 getHandle2: function() {
3342 return this._segment2._handleIn;
3343 },
3344
3345 setHandle2: function(point) {
3346 point = Point.read(arguments);
3347 this._segment2._handleIn.set(point.x, point.y);
3348 },
3349
3350 getSegment1: function() {
3351 return this._segment1;
3352 },
3353
3354 getSegment2: function() {
3355 return this._segment2;
3356 },
3357
3358 getPath: function() {
3359 return this._path;
3360 },
3361
3362 getIndex: function() {
3363 return this._segment1._index;
3364 },
3365
3366 getNext: function() {
3367 var curves = this._path && this._path._curves;
3368 return curves && (curves[this._segment1._index + 1]
3369 || this._path._closed && curves[0]) || null;
3370 },
3371
3372 getPrevious: function() {
3373 var curves = this._path && this._path._curves;
3374 return curves && (curves[this._segment1._index - 1]
3375 || this._path._closed && curves[curves.length - 1]) || null;
3376 },
3377
3378 isSelected: function() {
3379 return this.getHandle1().isSelected() && this.getHandle2().isSelected();
3380 },
3381
3382 setSelected: function(selected) {
3383 this.getHandle1().setSelected(selected);
3384 this.getHandle2().setSelected(selected);
3385 },
3386
3387 getValues: function(matrix) {
3388 return Curve.getValues(this._segment1, this._segment2, matrix);
3389 },
3390
3391 getPoints: function(matrix) {
3392 var coords = this.getValues(matrix),
3393 points = [];
3394 for (var i = 0; i < 8; i += 2)
3395 points.push(Point.create(coords[i], coords[i + 1]));
3396 return points;
3397 },
3398
3399 getLength: function() {
3400 var from = arguments[0],
3401 to = arguments[1];
3402 fullLength = arguments.length == 0 || from == 0 && to == 1;
3403 if (fullLength && this._length != null)
3404 return this._length;
3405 var length = Curve.getLength(this.getValues(), from, to);
3406 if (fullLength)
3407 this._length = length;
3408 return length;
3409 },
3410
3411 getPart: function(from, to) {
3412 return new Curve(Curve.getPart(this.getValues(), from, to));
3413 },
3414
3415 isLinear: function() {
3416 return this._segment1._handleOut.isZero()
3417 && this._segment2._handleIn.isZero();
3418 },
3419
3420 getParameterAt: function(offset, start) {
3421 return Curve.getParameterAt(this.getValues(), offset,
3422 start !== undefined ? start : offset < 0 ? 1 : 0);
3423 },
3424
3425 getPoint: function(parameter) {
3426 return Curve.evaluate(this.getValues(), parameter, 0);
3427 },
3428
3429 getTangent: function(parameter) {
3430 return Curve.evaluate(this.getValues(), parameter, 1);
3431 },
3432
3433 getNormal: function(parameter) {
3434 return Curve.evaluate(this.getValues(), parameter, 2);
3435 },
3436
3437 getParameter: function(point) {
3438 point = Point.read(point);
3439 return Curve.getParameter(this.getValues(), point.x, point.y);
3440 },
3441
3442 getCrossings: function(point, matrix, roots) {
3443 var vals = this.getValues(matrix),
3444 num = Curve.solveCubic(vals, 1, point.y, roots),
3445 crossings = 0;
3446 for (var i = 0; i < num; i++) {
3447 var t = roots[i];
3448 if (t >= 0 && t < 1 && Curve.evaluate(vals, t, 0).x > point.x) {
3449 if (t < Numerical.TOLERANCE && Curve.evaluate(
3450 this.getPrevious().getValues(matrix), 1, 1).y
3451 * Curve.evaluate(vals, t, 1).y >= 0)
3452 continue;
3453 crossings++;
3454 }
3455 }
3456 return crossings;
3457 },
3458
3459 reverse: function() {
3460 return new Curve(this._segment2.reverse(), this._segment1.reverse());
3461 },
3462
3463 clone: function() {
3464 return new Curve(this._segment1, this._segment2);
3465 },
3466
3467 toString: function() {
3468 var parts = [ 'point1: ' + this._segment1._point ];
3469 if (!this._segment1._handleOut.isZero())
3470 parts.push('handle1: ' + this._segment1._handleOut);
3471 if (!this._segment2._handleIn.isZero())
3472 parts.push('handle2: ' + this._segment2._handleIn);
3473 parts.push('point2: ' + this._segment2._point);
3474 return '{ ' + parts.join(', ') + ' }';
3475 },
3476
3477 statics: {
3478 create: function(path, segment1, segment2) {
3479 var curve = new Curve(Curve.dont);
3480 curve._path = path;
3481 curve._segment1 = segment1;
3482 curve._segment2 = segment2;
3483 return curve;
3484 },
3485
3486 getValues: function(segment1, segment2, matrix) {
3487 var p1 = segment1._point,
3488 h1 = segment1._handleOut,
3489 h2 = segment2._handleIn,
3490 p2 = segment2._point,
3491 coords = [
3492 p1._x, p1._y,
3493 p1._x + h1._x, p1._y + h1._y,
3494 p2._x + h2._x, p2._y + h2._y,
3495 p2._x, p2._y
3496 ];
3497 return matrix
3498 ? matrix._transformCoordinates(coords, 0, coords, 0, 4)
3499 : coords;
3500 },
3501
3502 evaluate: function(v, t, type) {
3503 var p1x = v[0], p1y = v[1],
3504 c1x = v[2], c1y = v[3],
3505 c2x = v[4], c2y = v[5],
3506 p2x = v[6], p2y = v[7],
3507 x, y;
3508
3509 if (type == 0 && (t == 0 || t == 1)) {
3510 x = t == 0 ? p1x : p2x;
3511 y = t == 0 ? p1y : p2y;
3512 } else {
3513 var tMin = Numerical.TOLERANCE;
3514 if (t < tMin && c1x == p1x && c1y == p1y)
3515 t = tMin;
3516 else if (t > 1 - tMin && c2x == p2x && c2y == p2y)
3517 t = 1 - tMin;
3518 var cx = 3 * (c1x - p1x),
3519 bx = 3 * (c2x - c1x) - cx,
3520 ax = p2x - p1x - cx - bx,
3521
3522 cy = 3 * (c1y - p1y),
3523 by = 3 * (c2y - c1y) - cy,
3524 ay = p2y - p1y - cy - by;
3525
3526 switch (type) {
3527 case 0:
3528 x = ((ax * t + bx) * t + cx) * t + p1x;
3529 y = ((ay * t + by) * t + cy) * t + p1y;
3530 break;
3531 case 1:
3532 case 2:
3533 x = (3 * ax * t + 2 * bx) * t + cx;
3534 y = (3 * ay * t + 2 * by) * t + cy;
3535 break;
3536 }
3537 }
3538 return type == 2 ? new Point(y, -x) : new Point(x, y);
3539 },
3540
3541 subdivide: function(v, t) {
3542 var p1x = v[0], p1y = v[1],
3543 c1x = v[2], c1y = v[3],
3544 c2x = v[4], c2y = v[5],
3545 p2x = v[6], p2y = v[7];
3546 if (t === undefined)
3547 t = 0.5;
3548 var u = 1 - t,
3549 p3x = u * p1x + t * c1x, p3y = u * p1y + t * c1y,
3550 p4x = u * c1x + t * c2x, p4y = u * c1y + t * c2y,
3551 p5x = u * c2x + t * p2x, p5y = u * c2y + t * p2y,
3552 p6x = u * p3x + t * p4x, p6y = u * p3y + t * p4y,
3553 p7x = u * p4x + t * p5x, p7y = u * p4y + t * p5y,
3554 p8x = u * p6x + t * p7x, p8y = u * p6y + t * p7y;
3555 return [
3556 [p1x, p1y, p3x, p3y, p6x, p6y, p8x, p8y],
3557 [p8x, p8y, p7x, p7y, p5x, p5y, p2x, p2y]
3558 ];
3559 },
3560
3561 solveCubic: function (v, coord, val, roots) {
3562 var p1 = v[coord],
3563 c1 = v[coord + 2],
3564 c2 = v[coord + 4],
3565 p2 = v[coord + 6],
3566 c = 3 * (c1 - p1),
3567 b = 3 * (c2 - c1) - c,
3568 a = p2 - p1 - c - b;
3569 return Numerical.solveCubic(a, b, c, p1 - val, roots,
3570 Numerical.TOLERANCE);
3571 },
3572
3573 getParameter: function(v, x, y) {
3574 var txs = [],
3575 tys = [],
3576 sx = Curve.solveCubic(v, 0, x, txs),
3577 sy = Curve.solveCubic(v, 1, y, tys),
3578 tx, ty;
3579 for (var cx = 0; sx == -1 || cx < sx;) {
3580 if (sx == -1 || (tx = txs[cx++]) >= 0 && tx <= 1) {
3581 for (var cy = 0; sy == -1 || cy < sy;) {
3582 if (sy == -1 || (ty = tys[cy++]) >= 0 && ty <= 1) {
3583 if (sx == -1) tx = ty;
3584 else if (sy == -1) ty = tx;
3585 if (Math.abs(tx - ty) < Numerical.TOLERANCE)
3586 return (tx + ty) * 0.5;
3587 }
3588 }
3589 if (sx == -1)
3590 break;
3591 }
3592 }
3593 return null;
3594 },
3595
3596 getPart: function(v, from, to) {
3597 if (from > 0)
3598 v = Curve.subdivide(v, from)[1];
3599 if (to < 1)
3600 v = Curve.subdivide(v, (to - from) / (1 - from))[0];
3601 return v;
3602 },
3603
3604 isFlatEnough: function(v) {
3605 var p1x = v[0], p1y = v[1],
3606 c1x = v[2], c1y = v[3],
3607 c2x = v[4], c2y = v[5],
3608 p2x = v[6], p2y = v[7],
3609
3610 a = p1y - p2y,
3611 b = p2x - p1x,
3612 c = p1x * p2y - p2x * p1y,
3613 v1 = a * c1x + b * c1y + c,
3614 v2 = a * c2x + b * c2y + c;
3615 return Math.abs((v1 * v1 + v2 * v2) / (a * (a * a + b * b))) < 0.005;
3616 }
3617 }
3618}, new function() {
3619
3620 function getLengthIntegrand(v) {
3621 var p1x = v[0], p1y = v[1],
3622 c1x = v[2], c1y = v[3],
3623 c2x = v[4], c2y = v[5],
3624 p2x = v[6], p2y = v[7],
3625
3626 ax = 9 * (c1x - c2x) + 3 * (p2x - p1x),
3627 bx = 6 * (p1x + c2x) - 12 * c1x,
3628 cx = 3 * (c1x - p1x),
3629
3630 ay = 9 * (c1y - c2y) + 3 * (p2y - p1y),
3631 by = 6 * (p1y + c2y) - 12 * c1y,
3632 cy = 3 * (c1y - p1y);
3633
3634 return function(t) {
3635 var dx = (ax * t + bx) * t + cx,
3636 dy = (ay * t + by) * t + cy;
3637 return Math.sqrt(dx * dx + dy * dy);
3638 };
3639 }
3640
3641 function getIterations(a, b) {
3642 return Math.max(2, Math.min(16, Math.ceil(Math.abs(b - a) * 32)));
3643 }
3644
3645 return {
3646 statics: true,
3647
3648 getLength: function(v, a, b) {
3649 if (a === undefined)
3650 a = 0;
3651 if (b === undefined)
3652 b = 1;
3653 if (v[0] == v[2] && v[1] == v[3] && v[6] == v[4] && v[7] == v[5]) {
3654 var dx = v[6] - v[0],
3655 dy = v[7] - v[1];
3656 return (b - a) * Math.sqrt(dx * dx + dy * dy);
3657 }
3658 var ds = getLengthIntegrand(v);
3659 return Numerical.integrate(ds, a, b, getIterations(a, b));
3660 },
3661
3662 getParameterAt: function(v, offset, start) {
3663 if (offset == 0)
3664 return start;
3665 var forward = offset > 0,
3666 a = forward ? start : 0,
3667 b = forward ? 1 : start,
3668 offset = Math.abs(offset),
3669 ds = getLengthIntegrand(v),
3670 rangeLength = Numerical.integrate(ds, a, b,
3671 getIterations(a, b));
3672 if (offset >= rangeLength)
3673 return forward ? b : a;
3674 var guess = offset / rangeLength,
3675 length = 0;
3676 function f(t) {
3677 var count = getIterations(start, t);
3678 length += start < t
3679 ? Numerical.integrate(ds, start, t, count)
3680 : -Numerical.integrate(ds, t, start, count);
3681 start = t;
3682 return length - offset;
3683 }
3684 return Numerical.findRoot(f, ds,
3685 forward ? a + guess : b - guess,
3686 a, b, 16, Numerical.TOLERANCE);
3687 }
3688 };
3689}, new function() {
3690
3691 var maxDepth = 32,
3692 epsilon = Math.pow(2, -maxDepth - 1);
3693
3694 var zCubic = [
3695 [1.0, 0.6, 0.3, 0.1],
3696 [0.4, 0.6, 0.6, 0.4],
3697 [0.1, 0.3, 0.6, 1.0]
3698 ];
3699
3700 var xAxis = new Line(new Point(0, 0), new Point(1, 0));
3701
3702 function toBezierForm(v, point) {
3703 var n = 3,
3704 degree = 5,
3705 c = [],
3706 d = [],
3707 cd = [],
3708 w = [];
3709 for(var i = 0; i <= n; i++) {
3710 c[i] = v[i].subtract(point);
3711 if (i < n)
3712 d[i] = v[i + 1].subtract(v[i]).multiply(n);
3713 }
3714
3715 for (var row = 0; row < n; row++) {
3716 cd[row] = [];
3717 for (var column = 0; column <= n; column++)
3718 cd[row][column] = d[row].dot(c[column]);
3719 }
3720
3721 for (var i = 0; i <= degree; i++)
3722 w[i] = new Point(i / degree, 0);
3723
3724 for (k = 0; k <= degree; k++) {
3725 var lb = Math.max(0, k - n + 1),
3726 ub = Math.min(k, n);
3727 for (var i = lb; i <= ub; i++) {
3728 var j = k - i;
3729 w[k].y += cd[j][i] * zCubic[j][i];
3730 }
3731 }
3732
3733 return w;
3734 }
3735
3736 function findRoots(w, depth) {
3737 switch (countCrossings(w)) {
3738 case 0:
3739 return [];
3740 case 1:
3741 if (depth >= maxDepth)
3742 return [0.5 * (w[0].x + w[5].x)];
3743 if (isFlatEnough(w)) {
3744 var line = new Line(w[0], w[5], true);
3745 return [ line.vector.getLength(true) < Numerical.EPSILON
3746 ? line.point.x
3747 : xAxis.intersect(line).x ];
3748 }
3749 }
3750
3751 var p = [[]],
3752 left = [],
3753 right = [];
3754 for (var j = 0; j <= 5; j++)
3755 p[0][j] = new Point(w[j]);
3756
3757 for (var i = 1; i <= 5; i++) {
3758 p[i] = [];
3759 for (var j = 0 ; j <= 5 - i; j++)
3760 p[i][j] = p[i - 1][j].add(p[i - 1][j + 1]).multiply(0.5);
3761 }
3762 for (var j = 0; j <= 5; j++) {
3763 left[j] = p[j][0];
3764 right[j] = p[5 - j][j];
3765 }
3766
3767 return findRoots(left, depth + 1).concat(findRoots(right, depth + 1));
3768 }
3769
3770 function countCrossings(v) {
3771 var crossings = 0,
3772 prevSign = null;
3773 for (var i = 0, l = v.length; i < l; i++) {
3774 var sign = v[i].y < 0 ? -1 : 1;
3775 if (prevSign != null && sign != prevSign)
3776 crossings++;
3777 prevSign = sign;
3778 }
3779 return crossings;
3780 }
3781
3782 function isFlatEnough(v) {
3783
3784 var n = v.length - 1,
3785 a = v[0].y - v[n].y,
3786 b = v[n].x - v[0].x,
3787 c = v[0].x * v[n].y - v[n].x * v[0].y,
3788 maxAbove = 0,
3789 maxBelow = 0;
3790 for (var i = 1; i < n; i++) {
3791 var val = a * v[i].x + b * v[i].y + c,
3792 dist = val * val;
3793 if (val < 0 && dist > maxBelow) {
3794 maxBelow = dist;
3795 } else if (dist > maxAbove) {
3796 maxAbove = dist;
3797 }
3798 }
3799 return Math.abs((maxAbove + maxBelow) / (2 * a * (a * a + b * b)))
3800 < epsilon;
3801 }
3802
3803 return {
3804 getNearestLocation: function(point, matrix) {
3805 var w = toBezierForm(this.getPoints(matrix), point);
3806 var roots = findRoots(w, 0).concat([0, 1]);
3807 var minDist = Infinity,
3808 minT,
3809 minPoint;
3810 for (var i = 0; i < roots.length; i++) {
3811 var pt = this.getPoint(roots[i]),
3812 dist = point.getDistance(pt, true);
3813 if (dist < minDist) {
3814 minDist = dist;
3815 minT = roots[i];
3816 minPoint = pt;
3817 }
3818 }
3819 return new CurveLocation(this, minT, minPoint, Math.sqrt(minDist));
3820 },
3821
3822 getNearestPoint: function(point, matrix) {
3823 return this.getNearestLocation(point, matrix).getPoint();
3824 }
3825 };
3826});
3827
3828CurveLocation = Base.extend({
3829 initialize: function(curve, parameter, point, distance) {
3830 this._curve = curve;
3831 this._parameter = parameter;
3832 this._point = point;
3833 this._distance = distance;
3834 },
3835
3836 getSegment: function() {
3837 if (!this._segment) {
3838 var curve = this._curve,
3839 parameter = this.getParameter();
3840 if (parameter == 0) {
3841 this._segment = curve._segment1;
3842 } else if (parameter == 1) {
3843 this._segment = curve._segment2;
3844 } else if (parameter == null) {
3845 return null;
3846 } else {
3847 this._segment = curve.getLength(0, parameter)
3848 < curve.getLength(parameter, 1)
3849 ? curve._segment1
3850 : curve._segment2;
3851 }
3852 }
3853 return this._segment;
3854 },
3855
3856 getCurve: function() {
3857 return this._curve;
3858 },
3859
3860 getPath: function() {
3861 return this._curve && this._curve._path;
3862 },
3863
3864 getIndex: function() {
3865 return this._curve && this._curve.getIndex();
3866 },
3867
3868 getOffset: function() {
3869 var path = this._curve && this._curve._path;
3870 return path && path._getOffset(this);
3871 },
3872
3873 getCurveOffset: function() {
3874 var parameter = this.getParameter();
3875 return parameter != null && this._curve
3876 && this._curve.getLength(0, parameter);
3877 },
3878
3879 getParameter: function() {
3880 if (this._parameter == null && this._curve && this._point)
3881 this._parameter = this._curve.getParameterAt(this._point);
3882 return this._parameter;
3883 },
3884
3885 getPoint: function() {
3886 if (!this._point && this._curve && this._parameter != null)
3887 this._point = this._curve.getPoint(this._parameter);
3888 return this._point;
3889 },
3890
3891 getTangent: function() {
3892 var parameter = this.getParameter();
3893 return parameter != null && this._curve
3894 && this._curve.getTangent(parameter);
3895 },
3896
3897 getNormal: function() {
3898 var parameter = this.getParameter();
3899 return parameter != null && this._curve
3900 && this._curve.getNormal(parameter);
3901 },
3902
3903 getDistance: function() {
3904 return this._distance;
3905 },
3906
3907 toString: function() {
3908 var parts = [],
3909 point = this.getPoint();
3910 if (point)
3911 parts.push('point: ' + point);
3912 var index = this.getIndex();
3913 if (index != null)
3914 parts.push('index: ' + index);
3915 var parameter = this.getParameter();
3916 if (parameter != null)
3917 parts.push('parameter: ' + Base.formatNumber(parameter));
3918 if (this._distance != null)
3919 parts.push('distance: ' + Base.formatNumber(this._distance));
3920 return '{ ' + parts.join(', ') + ' }';
3921 }
3922});
3923
3924var PathItem = this.PathItem = Item.extend({
3925
3926});
3927
3928var Path = this.Path = PathItem.extend({
3929 initialize: function(segments) {
3930 this.base();
3931 this._closed = false;
3932 this._selectedSegmentState = 0;
3933 this.setSegments(!segments || !Array.isArray(segments)
3934 || typeof segments[0] !== 'object' ? arguments : segments);
3935 },
3936
3937 clone: function() {
3938 var copy = this._clone(new Path(this._segments));
3939 copy._closed = this._closed;
3940 if (this._clockwise !== undefined)
3941 copy._clockwise = this._clockwise;
3942 return copy;
3943 },
3944
3945 _changed: function(flags) {
3946 Item.prototype._changed.call(this, flags);
3947 if (flags & ChangeFlag.GEOMETRY) {
3948 delete this._strokeBounds;
3949 delete this._handleBounds;
3950 delete this._roughBounds;
3951 delete this._length;
3952 delete this._clockwise;
3953 } else if (flags & ChangeFlag.STROKE) {
3954 delete this._strokeBounds;
3955 }
3956 },
3957
3958 getSegments: function() {
3959 return this._segments;
3960 },
3961
3962 setSegments: function(segments) {
3963 if (!this._segments) {
3964 this._segments = [];
3965 } else {
3966 this._selectedSegmentState = 0;
3967 this._segments.length = 0;
3968 if (this._curves)
3969 delete this._curves;
3970 }
3971 this._add(Segment.readAll(segments));
3972 },
3973
3974 getFirstSegment: function() {
3975 return this._segments[0];
3976 },
3977
3978 getLastSegment: function() {
3979 return this._segments[this._segments.length - 1];
3980 },
3981
3982 getCurves: function() {
3983 if (!this._curves) {
3984 var segments = this._segments,
3985 length = segments.length;
3986 if (!this._closed && length > 0)
3987 length--;
3988 this._curves = new Array(length);
3989 for (var i = 0; i < length; i++)
3990 this._curves[i] = Curve.create(this, segments[i],
3991 segments[i + 1] || segments[0]);
3992 }
3993 return this._curves;
3994 },
3995
3996 getFirstCurve: function() {
3997 return this.getCurves()[0];
3998 },
3999
4000 getLastCurve: function() {
4001 var curves = this.getCurves();
4002 return curves[curves.length - 1];
4003 },
4004
4005 getClosed: function() {
4006 return this._closed;
4007 },
4008
4009 setClosed: function(closed) {
4010 if (this._closed != (closed = !!closed)) {
4011 this._closed = closed;
4012 if (this._curves) {
4013 var length = this._segments.length,
4014 i;
4015 if (!closed && length > 0)
4016 length--;
4017 this._curves.length = length;
4018 if (closed)
4019 this._curves[i = length - 1] = Curve.create(this,
4020 this._segments[i], this._segments[0]);
4021 }
4022 this._changed(Change.GEOMETRY);
4023 }
4024 },
4025
4026 _transform: function(matrix, flags) {
4027 if (!matrix.isIdentity()) {
4028 var coords = new Array(6);
4029 for (var i = 0, l = this._segments.length; i < l; i++) {
4030 this._segments[i]._transformCoordinates(matrix, coords, true);
4031 }
4032 var fillColor = this.getFillColor(),
4033 strokeColor = this.getStrokeColor();
4034 if (fillColor && fillColor.transform)
4035 fillColor.transform(matrix);
4036 if (strokeColor && strokeColor.transform)
4037 strokeColor.transform(matrix);
4038 }
4039 },
4040
4041 _add: function(segs, index) {
4042 var segments = this._segments,
4043 curves = this._curves,
4044 amount = segs.length,
4045 append = index == null,
4046 index = append ? segments.length : index,
4047 fullySelected = this.isFullySelected();
4048 for (var i = 0; i < amount; i++) {
4049 var segment = segs[i];
4050 if (segment._path) {
4051 segment = segs[i] = new Segment(segment);
4052 }
4053 segment._path = this;
4054 segment._index = index + i;
4055 if (fullySelected)
4056 segment._selectionState = SelectionState.POINT;
4057 if (segment._selectionState)
4058 this._updateSelection(segment, 0, segment._selectionState);
4059 }
4060 if (append) {
4061 segments.push.apply(segments, segs);
4062 } else {
4063 segments.splice.apply(segments, [index, 0].concat(segs));
4064 for (var i = index + amount, l = segments.length; i < l; i++) {
4065 segments[i]._index = i;
4066 }
4067 }
4068 if (curves && --index >= 0) {
4069 curves.splice(index, 0, Curve.create(this, segments[index],
4070 segments[index + 1]));
4071 var curve = curves[index + amount];
4072 if (curve) {
4073 curve._segment1 = segments[index + amount];
4074 }
4075 }
4076 this._changed(Change.GEOMETRY);
4077 return segs;
4078 },
4079
4080 add: function(segment1 ) {
4081 return arguments.length > 1 && typeof segment1 !== 'number'
4082 ? this._add(Segment.readAll(arguments))
4083 : this._add([ Segment.read(arguments) ])[0];
4084 },
4085
4086 insert: function(index, segment1 ) {
4087 return arguments.length > 2 && typeof segment1 !== 'number'
4088 ? this._add(Segment.readAll(arguments, 1), index)
4089 : this._add([ Segment.read(arguments, 1) ], index)[0];
4090 },
4091
4092 addSegment: function(segment) {
4093 return this._add([ Segment.read(arguments) ])[0];
4094 },
4095
4096 insertSegment: function(index, segment) {
4097 return this._add([ Segment.read(arguments, 1) ], index)[0];
4098 },
4099
4100 addSegments: function(segments) {
4101 return this._add(Segment.readAll(segments));
4102 },
4103
4104 insertSegments: function(index, segments) {
4105 return this._add(Segment.readAll(segments), index);
4106 },
4107
4108 removeSegment: function(index) {
4109 var segments = this.removeSegments(index, index + 1);
4110 return segments[0] || null;
4111 },
4112
4113 removeSegments: function(from, to) {
4114 from = from || 0;
4115 to = Base.pick(to, this._segments.length);
4116 var segments = this._segments,
4117 curves = this._curves,
4118 last = to >= segments.length,
4119 removed = segments.splice(from, to - from),
4120 amount = removed.length;
4121 if (!amount)
4122 return removed;
4123 for (var i = 0; i < amount; i++) {
4124 var segment = removed[i];
4125 if (segment._selectionState)
4126 this._updateSelection(segment, segment._selectionState, 0);
4127 removed._index = removed._path = undefined;
4128 }
4129 for (var i = from, l = segments.length; i < l; i++)
4130 segments[i]._index = i;
4131 if (curves) {
4132 curves.splice(from, amount);
4133 var curve;
4134 if (curve = curves[from - 1])
4135 curve._segment2 = segments[from];
4136 if (curve = curves[from])
4137 curve._segment1 = segments[from];
4138 if (last && this._closed && (curve = curves[curves.length - 1]))
4139 curve._segment2 = segments[0];
4140 }
4141 this._changed(Change.GEOMETRY);
4142 return removed;
4143 },
4144
4145 isFullySelected: function() {
4146 return this._selected && this._selectedSegmentState
4147 == this._segments.length * SelectionState.POINT;
4148 },
4149
4150 setFullySelected: function(selected) {
4151 var length = this._segments.length;
4152 this._selectedSegmentState = selected
4153 ? length * SelectionState.POINT : 0;
4154 for (var i = 0; i < length; i++)
4155 this._segments[i]._selectionState = selected
4156 ? SelectionState.POINT : 0;
4157 this.setSelected(selected);
4158 },
4159
4160 _updateSelection: function(segment, oldState, newState) {
4161 segment._selectionState = newState;
4162 var total = this._selectedSegmentState += newState - oldState;
4163 if (total > 0)
4164 this.setSelected(true);
4165 },
4166
4167 flatten: function(maxDistance) {
4168 var flattener = new PathFlattener(this),
4169 pos = 0,
4170 step = flattener.length / Math.ceil(flattener.length / maxDistance),
4171 end = flattener.length + (this._closed ? -step : step) / 2;
4172 var segments = [];
4173 while (pos <= end) {
4174 segments.push(new Segment(flattener.evaluate(pos, 0)));
4175 pos += step;
4176 }
4177 this.setSegments(segments);
4178 },
4179
4180 simplify: function(tolerance) {
4181 if (this._segments.length > 2) {
4182 var fitter = new PathFitter(this, tolerance || 2.5);
4183 this.setSegments(fitter.fit());
4184 }
4185 },
4186
4187 isClockwise: function() {
4188 if (this._clockwise !== undefined)
4189 return this._clockwise;
4190 var sum = 0,
4191 xPre, yPre;
4192 function edge(x, y) {
4193 if (xPre !== undefined)
4194 sum += (xPre - x) * (y + yPre);
4195 xPre = x;
4196 yPre = y;
4197 }
4198 for (var i = 0, l = this._segments.length; i < l; i++) {
4199 var seg1 = this._segments[i],
4200 seg2 = this._segments[i + 1 < l ? i + 1 : 0],
4201 point1 = seg1._point,
4202 handle1 = seg1._handleOut,
4203 handle2 = seg2._handleIn,
4204 point2 = seg2._point;
4205 edge(point1._x, point1._y);
4206 edge(point1._x + handle1._x, point1._y + handle1._y);
4207 edge(point2._x + handle2._x, point2._y + handle2._y);
4208 edge(point2._x, point2._y);
4209 }
4210 return sum > 0;
4211 },
4212
4213 setClockwise: function(clockwise) {
4214 if (this.isClockwise() != (clockwise = !!clockwise)) {
4215 this.reverse();
4216 this._clockwise = clockwise;
4217 }
4218 },
4219
4220 reverse: function() {
4221 this._segments.reverse();
4222 for (var i = 0, l = this._segments.length; i < l; i++) {
4223 var segment = this._segments[i];
4224 var handleIn = segment._handleIn;
4225 segment._handleIn = segment._handleOut;
4226 segment._handleOut = handleIn;
4227 }
4228 if (this._clockwise !== undefined)
4229 this._clockwise = !this._clockwise;
4230 },
4231
4232 join: function(path) {
4233 if (path) {
4234 var segments = path._segments,
4235 last1 = this.getLastSegment(),
4236 last2 = path.getLastSegment();
4237 if (last1._point.equals(last2._point))
4238 path.reverse();
4239 var first2 = path.getFirstSegment();
4240 if (last1._point.equals(first2._point)) {
4241 last1.setHandleOut(first2._handleOut);
4242 this._add(segments.slice(1));
4243 } else {
4244 var first1 = this.getFirstSegment();
4245 if (first1._point.equals(first2._point))
4246 path.reverse();
4247 last2 = path.getLastSegment();
4248 if (first1._point.equals(last2._point)) {
4249 first1.setHandleIn(last2._handleIn);
4250 this._add(segments.slice(0, segments.length - 1), 0);
4251 } else {
4252 this._add(segments.slice(0));
4253 }
4254 }
4255 path.remove();
4256 var first1 = this.getFirstSegment();
4257 last1 = this.getLastSegment();
4258 if (last1._point.equals(first1._point)) {
4259 first1.setHandleIn(last1._handleIn);
4260 last1.remove();
4261 this.setClosed(true);
4262 }
4263 this._changed(Change.GEOMETRY);
4264 return true;
4265 }
4266 return false;
4267 },
4268
4269 getLength: function() {
4270 if (this._length == null) {
4271 var curves = this.getCurves();
4272 this._length = 0;
4273 for (var i = 0, l = curves.length; i < l; i++)
4274 this._length += curves[i].getLength();
4275 }
4276 return this._length;
4277 },
4278
4279 _getOffset: function(location) {
4280 var index = location && location.getIndex();
4281 if (index != null) {
4282 var curves = this.getCurves(),
4283 offset = 0;
4284 for (var i = 0; i < index; i++)
4285 offset += curves[i].getLength();
4286 var curve = curves[index];
4287 return offset + curve.getLength(0, location.getParameter());
4288 }
4289 return null;
4290 },
4291
4292 getLocation: function(point) {
4293 var curves = this.getCurves();
4294 for (var i = 0, l = curves.length; i < l; i++) {
4295 var curve = curves[i];
4296 var t = curve.getParameter(point);
4297 if (t != null)
4298 return new CurveLocation(curve, t);
4299 }
4300 return null;
4301 },
4302
4303 getLocationAt: function(offset, isParameter) {
4304 var curves = this.getCurves(),
4305 length = 0;
4306 if (isParameter) {
4307 var index = ~~offset;
4308 return new CurveLocation(curves[index], offset - index);
4309 }
4310 for (var i = 0, l = curves.length; i < l; i++) {
4311 var start = length,
4312 curve = curves[i];
4313 length += curve.getLength();
4314 if (length >= offset) {
4315 return new CurveLocation(curve,
4316 curve.getParameterAt(offset - start));
4317 }
4318 }
4319 if (offset <= this.getLength())
4320 return new CurveLocation(curves[curves.length - 1], 1);
4321 return null;
4322 },
4323
4324 getPointAt: function(offset, isParameter) {
4325 var loc = this.getLocationAt(offset, isParameter);
4326 return loc && loc.getPoint();
4327 },
4328
4329 getTangentAt: function(offset, isParameter) {
4330 var loc = this.getLocationAt(offset, isParameter);
4331 return loc && loc.getTangent();
4332 },
4333
4334 getNormalAt: function(offset, isParameter) {
4335 var loc = this.getLocationAt(offset, isParameter);
4336 return loc && loc.getNormal();
4337 },
4338
4339 getNearestLocation: function(point, matrix) {
4340 var curves = this.getCurves(),
4341 minDist = Infinity,
4342 minLoc = null;
4343 for (var i = 0, l = curves.length; i < l; i++) {
4344 var loc = curves[i].getNearestLocation(point, matrix);
4345 if (loc._distance < minDist) {
4346 minDist = loc._distance;
4347 minLoc = loc;
4348 }
4349 }
4350 return minLoc;
4351 },
4352
4353 getNearestPoint: function(point, matrix) {
4354 return this.getNearestLocation(point, matrix).getPoint();
4355 },
4356
4357 contains: function(point, matrix) {
4358 point = Point.read(arguments);
4359 if (!this._closed || !this.getRoughBounds(matrix)._containsPoint(point))
4360 return false;
4361 var curves = this.getCurves(),
4362 crossings = 0,
4363 roots = [];
4364 for (var i = 0, l = curves.length; i < l; i++)
4365 crossings += curves[i].getCrossings(point, matrix, roots);
4366 return (crossings & 1) == 1;
4367 },
4368
4369 _hitTest: function(point, options, matrix) {
4370 var tolerance = options.tolerance || 0,
4371 radius = (options.stroke ? this.getStrokeWidth() / 2 : 0) + tolerance,
4372 loc,
4373 res;
4374 var coords = [],
4375 that = this;
4376 function checkSegment(segment, ends) {
4377 segment._transformCoordinates(matrix, coords);
4378 for (var j = ends || options.segments ? 0 : 2,
4379 m = !ends && options.handles ? 6 : 2; j < m; j += 2) {
4380 if (point.getDistance(coords[j], coords[j + 1]) < tolerance)
4381 return new HitResult(j == 0 ? 'segment'
4382 : 'handle-' + (j == 2 ? 'in' : 'out'),
4383 that, { segment: segment });
4384 }
4385 }
4386 if (options.ends && !options.segments && !this._closed) {
4387 if (res = checkSegment(this.getFirstSegment(), true)
4388 || checkSegment(this.getLastSegment(), true))
4389 return res;
4390 } else if (options.segments || options.handles) {
4391 for (var i = 0, l = this._segments.length; i < l; i++) {
4392 if (res = checkSegment(this._segments[i]))
4393 return res;
4394 }
4395 }
4396 if (options.stroke && radius > 0)
4397 loc = this.getNearestLocation(point, matrix);
4398 if (!(loc && loc._distance <= radius) && options.fill
4399 && this.getFillColor() && this.contains(point, matrix))
4400 return new HitResult('fill', this);
4401 if (!loc && options.stroke && radius > 0)
4402 loc = this.getNearestLocation(point, matrix);
4403 if (loc && loc._distance <= radius)
4404 return options.stroke
4405 ? new HitResult('stroke', this, { location: loc })
4406 : new HitResult('fill', this);
4407 }
4408
4409}, new function() {
4410
4411 function drawHandles(ctx, segments) {
4412 for (var i = 0, l = segments.length; i < l; i++) {
4413 var segment = segments[i],
4414 point = segment._point,
4415 state = segment._selectionState,
4416 selected = state & SelectionState.POINT;
4417 if (selected || (state & SelectionState.HANDLE_IN))
4418 drawHandle(ctx, point, segment._handleIn);
4419 if (selected || (state & SelectionState.HANDLE_OUT))
4420 drawHandle(ctx, point, segment._handleOut);
4421 ctx.save();
4422 ctx.beginPath();
4423 ctx.rect(point._x - 2, point._y - 2, 4, 4);
4424 ctx.fill();
4425 if (!selected) {
4426 ctx.beginPath();
4427 ctx.rect(point._x - 1, point._y - 1, 2, 2);
4428 ctx.fillStyle = '#ffffff';
4429 ctx.fill();
4430 ctx.restore();
4431 }
4432 }
4433 }
4434
4435 function drawHandle(ctx, point, handle) {
4436 if (!handle.isZero()) {
4437 var handleX = point._x + handle._x,
4438 handleY = point._y + handle._y;
4439 ctx.beginPath();
4440 ctx.moveTo(point._x, point._y);
4441 ctx.lineTo(handleX, handleY);
4442 ctx.stroke();
4443 ctx.beginPath();
4444 ctx.arc(handleX, handleY, 1.75, 0, Math.PI * 2, true);
4445 ctx.fill();
4446 }
4447 }
4448
4449 function drawSegments(ctx, path) {
4450 var segments = path._segments,
4451 length = segments.length,
4452 handleOut, outX, outY;
4453
4454 function drawSegment(i) {
4455 var segment = segments[i],
4456 point = segment._point,
4457 x = point._x,
4458 y = point._y,
4459 handleIn = segment._handleIn;
4460 if (!handleOut) {
4461 ctx.moveTo(x, y);
4462 } else {
4463 if (handleIn.isZero() && handleOut.isZero()) {
4464 ctx.lineTo(x, y);
4465 } else {
4466 ctx.bezierCurveTo(outX, outY,
4467 handleIn._x + x, handleIn._y + y, x, y);
4468 }
4469 }
4470 handleOut = segment._handleOut;
4471 outX = handleOut._x + x;
4472 outY = handleOut._y + y;
4473 }
4474
4475 for (var i = 0; i < length; i++)
4476 drawSegment(i);
4477 if (path._closed && length > 1)
4478 drawSegment(0);
4479 }
4480
4481 function drawDashes(ctx, path, dashArray, dashOffset) {
4482 var flattener = new PathFlattener(path),
4483 from = dashOffset, to,
4484 i = 0;
4485 while (from < flattener.length) {
4486 to = from + dashArray[(i++) % dashArray.length];
4487 flattener.drawPart(ctx, from, to);
4488 from = to + dashArray[(i++) % dashArray.length];
4489 }
4490 }
4491
4492 return {
4493 draw: function(ctx, param) {
4494 if (!param.compound)
4495 ctx.beginPath();
4496
4497 var fillColor = this.getFillColor(),
4498 strokeColor = this.getStrokeColor(),
4499 dashArray = this.getDashArray() || [],
4500 hasDash = !!dashArray.length;
4501
4502 if (param.compound || param.selection || this._clipMask || fillColor
4503 || strokeColor && !hasDash) {
4504 drawSegments(ctx, this);
4505 }
4506
4507 if (param.selection) {
4508 ctx.stroke();
4509 drawHandles(ctx, this._segments);
4510 } else if (this._clipMask) {
4511 ctx.clip();
4512 } else if (!param.compound && (fillColor || strokeColor)) {
4513 ctx.save();
4514 this._setStyles(ctx);
4515 if (!fillColor || !strokeColor)
4516 ctx.globalAlpha = this._opacity;
4517 if (fillColor) {
4518 ctx.fillStyle = fillColor.getCanvasStyle(ctx);
4519 ctx.fill();
4520 }
4521 if (strokeColor) {
4522 ctx.strokeStyle = strokeColor.getCanvasStyle(ctx);
4523 if (hasDash) {
4524 ctx.beginPath();
4525 drawDashes(ctx, this, dashArray, this.getDashOffset());
4526 }
4527 ctx.stroke();
4528 }
4529 ctx.restore();
4530 }
4531 }
4532 };
4533}, new function() {
4534
4535 function getFirstControlPoints(rhs) {
4536 var n = rhs.length,
4537 x = [],
4538 tmp = [],
4539 b = 2;
4540 x[0] = rhs[0] / b;
4541 for (var i = 1; i < n; i++) {
4542 tmp[i] = 1 / b;
4543 b = (i < n - 1 ? 4 : 2) - tmp[i];
4544 x[i] = (rhs[i] - x[i - 1]) / b;
4545 }
4546 for (var i = 1; i < n; i++) {
4547 x[n - i - 1] -= tmp[n - i] * x[n - i];
4548 }
4549 return x;
4550 };
4551
4552 var styles = {
4553 getStrokeWidth: 'lineWidth',
4554 getStrokeJoin: 'lineJoin',
4555 getStrokeCap: 'lineCap',
4556 getMiterLimit: 'miterLimit'
4557 };
4558
4559 return {
4560 _setStyles: function(ctx) {
4561 for (var i in styles) {
4562 var style = this._style[i]();
4563 if (style)
4564 ctx[styles[i]] = style;
4565 }
4566 },
4567
4568 smooth: function() {
4569 var segments = this._segments,
4570 size = segments.length,
4571 n = size,
4572 overlap;
4573
4574 if (size <= 2)
4575 return;
4576
4577 if (this._closed) {
4578 overlap = Math.min(size, 4);
4579 n += Math.min(size, overlap) * 2;
4580 } else {
4581 overlap = 0;
4582 }
4583 var knots = [];
4584 for (var i = 0; i < size; i++)
4585 knots[i + overlap] = segments[i]._point;
4586 if (this._closed) {
4587 for (var i = 0; i < overlap; i++) {
4588 knots[i] = segments[i + size - overlap]._point;
4589 knots[i + size + overlap] = segments[i]._point;
4590 }
4591 } else {
4592 n--;
4593 }
4594 var rhs = [];
4595
4596 for (var i = 1; i < n - 1; i++)
4597 rhs[i] = 4 * knots[i]._x + 2 * knots[i + 1]._x;
4598 rhs[0] = knots[0]._x + 2 * knots[1]._x;
4599 rhs[n - 1] = 3 * knots[n - 1]._x;
4600 var x = getFirstControlPoints(rhs);
4601
4602 for (var i = 1; i < n - 1; i++)
4603 rhs[i] = 4 * knots[i]._y + 2 * knots[i + 1]._y;
4604 rhs[0] = knots[0]._y + 2 * knots[1]._y;
4605 rhs[n - 1] = 3 * knots[n - 1]._y;
4606 var y = getFirstControlPoints(rhs);
4607
4608 if (this._closed) {
4609 for (var i = 0, j = size; i < overlap; i++, j++) {
4610 var f1 = (i / overlap);
4611 var f2 = 1 - f1;
4612 x[j] = x[i] * f1 + x[j] * f2;
4613 y[j] = y[i] * f1 + y[j] * f2;
4614 var ie = i + overlap, je = j + overlap;
4615 x[je] = x[ie] * f2 + x[je] * f1;
4616 y[je] = y[ie] * f2 + y[je] * f1;
4617 }
4618 n--;
4619 }
4620 var handleIn = null;
4621 for (var i = overlap; i <= n - overlap; i++) {
4622 var segment = segments[i - overlap];
4623 if (handleIn)
4624 segment.setHandleIn(handleIn.subtract(segment._point));
4625 if (i < n) {
4626 segment.setHandleOut(
4627 new Point(x[i], y[i]).subtract(segment._point));
4628 if (i < n - 1)
4629 handleIn = new Point(
4630 2 * knots[i + 1]._x - x[i + 1],
4631 2 * knots[i + 1]._y - y[i + 1]);
4632 else
4633 handleIn = new Point(
4634 (knots[n]._x + x[n - 1]) / 2,
4635 (knots[n]._y + y[n - 1]) / 2);
4636 }
4637 }
4638 if (this._closed && handleIn) {
4639 var segment = this._segments[0];
4640 segment.setHandleIn(handleIn.subtract(segment._point));
4641 }
4642 }
4643 };
4644}, new function() {
4645 function getCurrentSegment(that) {
4646 var segments = that._segments;
4647 if (segments.length == 0)
4648 throw new Error('Use a moveTo() command first');
4649 return segments[segments.length - 1];
4650 }
4651
4652 return {
4653 moveTo: function(point) {
4654 if (!this._segments.length)
4655 this._add([ new Segment(Point.read(arguments)) ]);
4656 },
4657
4658 moveBy: function(point) {
4659 throw new Error('moveBy() is unsupported on Path items.');
4660 },
4661
4662 lineTo: function(point) {
4663 this._add([ new Segment(Point.read(arguments)) ]);
4664 },
4665
4666 cubicCurveTo: function(handle1, handle2, to) {
4667 handle1 = Point.read(arguments, 0, 1);
4668 handle2 = Point.read(arguments, 1, 1);
4669 to = Point.read(arguments, 2, 1);
4670 var current = getCurrentSegment(this);
4671 current.setHandleOut(handle1.subtract(current._point));
4672 this._add([ new Segment(to, handle2.subtract(to)) ]);
4673 },
4674
4675 quadraticCurveTo: function(handle, to) {
4676 handle = Point.read(arguments, 0, 1);
4677 to = Point.read(arguments, 1, 1);
4678 var current = getCurrentSegment(this)._point;
4679 this.cubicCurveTo(
4680 handle.add(current.subtract(handle).multiply(1/3)),
4681 handle.add(to.subtract(handle).multiply(1/3)),
4682 to
4683 );
4684 },
4685
4686 curveTo: function(through, to, parameter) {
4687 through = Point.read(arguments, 0, 1);
4688 to = Point.read(arguments, 1, 1);
4689 var t = Base.pick(parameter, 0.5),
4690 t1 = 1 - t,
4691 current = getCurrentSegment(this)._point,
4692 handle = through.subtract(current.multiply(t1 * t1))
4693 .subtract(to.multiply(t * t)).divide(2 * t * t1);
4694 if (handle.isNaN())
4695 throw new Error(
4696 'Cannot put a curve through points with parameter = ' + t);
4697 this.quadraticCurveTo(handle, to);
4698 },
4699
4700 arcTo: function(to, clockwise ) {
4701 var current = getCurrentSegment(this),
4702 from = current._point,
4703 through;
4704 if (clockwise === undefined)
4705 clockwise = true;
4706 if (typeof clockwise === 'boolean') {
4707 to = Point.read(arguments, 0, 1);
4708 var middle = from.add(to).divide(2),
4709 through = middle.add(middle.subtract(from).rotate(
4710 clockwise ? -90 : 90));
4711 } else {
4712 through = Point.read(arguments, 0, 1);
4713 to = Point.read(arguments, 1, 1);
4714 }
4715 var l1 = new Line(from.add(through).divide(2),
4716 through.subtract(from).rotate(90)),
4717 l2 = new Line(through.add(to).divide(2),
4718 to.subtract(through).rotate(90)),
4719 center = l1.intersect(l2),
4720 line = new Line(from, to, true),
4721 throughSide = line.getSide(through);
4722 if (!center) {
4723 if (!throughSide)
4724 return this.lineTo(to);
4725 throw new Error("Cannot put an arc through the given points: "
4726 + [from, through, to]);
4727 }
4728 var vector = from.subtract(center),
4729 radius = vector.getLength(),
4730 extent = vector.getDirectedAngle(to.subtract(center)),
4731 centerSide = line.getSide(center);
4732 if (centerSide == 0) {
4733 extent = throughSide * Math.abs(extent);
4734 } else if (throughSide == centerSide) {
4735 extent -= 360 * (extent < 0 ? -1 : 1);
4736 }
4737 var ext = Math.abs(extent),
4738 count = ext >= 360 ? 4 : Math.ceil(ext / 90),
4739 inc = extent / count,
4740 half = inc * Math.PI / 360,
4741 z = 4 / 3 * Math.sin(half) / (1 + Math.cos(half)),
4742 segments = [];
4743 for (var i = 0; i <= count; i++) {
4744 var pt = i < count ? center.add(vector) : to;
4745 var out = i < count ? vector.rotate(90).multiply(z) : null;
4746 if (i == 0) {
4747 current.setHandleOut(out);
4748 } else {
4749 segments.push(
4750 new Segment(pt, vector.rotate(-90).multiply(z), out));
4751 }
4752 vector = vector.rotate(inc);
4753 }
4754 this._add(segments);
4755 },
4756
4757 lineBy: function(vector) {
4758 vector = Point.read(arguments);
4759 var current = getCurrentSegment(this);
4760 this.lineTo(current._point.add(vector));
4761 },
4762
4763 curveBy: function(throughVector, toVector, parameter) {
4764 throughVector = Point.read(throughVector);
4765 toVector = Point.read(toVector);
4766 var current = getCurrentSegment(this)._point;
4767 this.curveTo(current.add(throughVector), current.add(toVector),
4768 parameter);
4769 },
4770
4771 arcBy: function(throughVector, toVector) {
4772 throughVector = Point.read(throughVector);
4773 toVector = Point.read(toVector);
4774 var current = getCurrentSegment(this)._point;
4775 this.arcBy(current.add(throughVector), current.add(toVector));
4776 },
4777
4778 closePath: function() {
4779 this.setClosed(true);
4780 }
4781 };
4782}, new function() {
4783
4784 function getBounds(that, matrix, strokePadding) {
4785 var segments = that._segments,
4786 first = segments[0];
4787 if (!first)
4788 return null;
4789 var coords = new Array(6),
4790 prevCoords = new Array(6);
4791 if (matrix && matrix.isIdentity())
4792 matrix = null;
4793 first._transformCoordinates(matrix, prevCoords, false);
4794 var min = prevCoords.slice(0, 2),
4795 max = min.slice(0),
4796 tMin = Numerical.TOLERANCE,
4797 tMax = 1 - tMin;
4798 function processSegment(segment) {
4799 segment._transformCoordinates(matrix, coords, false);
4800
4801 for (var i = 0; i < 2; i++) {
4802 var v0 = prevCoords[i],
4803 v1 = prevCoords[i + 4],
4804 v2 = coords[i + 2],
4805 v3 = coords[i];
4806
4807 function add(value, t) {
4808 var padding = 0;
4809 if (value == null) {
4810 var u = 1 - t;
4811 value = u * u * u * v0
4812 + 3 * u * u * t * v1
4813 + 3 * u * t * t * v2
4814 + t * t * t * v3;
4815 padding = strokePadding ? strokePadding[i] : 0;
4816 }
4817 var left = value - padding,
4818 right = value + padding;
4819 if (left < min[i])
4820 min[i] = left;
4821 if (right > max[i])
4822 max[i] = right;
4823
4824 }
4825 add(v3, null);
4826
4827 var a = 3 * (v1 - v2) - v0 + v3,
4828 b = 2 * (v0 + v2) - 4 * v1,
4829 c = v1 - v0;
4830
4831 if (a == 0) {
4832 if (b == 0)
4833 continue;
4834 var t = -c / b;
4835 if (tMin < t && t < tMax)
4836 add(null, t);
4837 continue;
4838 }
4839
4840 var q = b * b - 4 * a * c;
4841 if (q < 0)
4842 continue;
4843 var sqrt = Math.sqrt(q),
4844 f = -0.5 / a,
4845 t1 = (b - sqrt) * f,
4846 t2 = (b + sqrt) * f;
4847 if (tMin < t1 && t1 < tMax)
4848 add(null, t1);
4849 if (tMin < t2 && t2 < tMax)
4850 add(null, t2);
4851 }
4852 var tmp = prevCoords;
4853 prevCoords = coords;
4854 coords = tmp;
4855 }
4856 for (var i = 1, l = segments.length; i < l; i++)
4857 processSegment(segments[i]);
4858 if (that._closed)
4859 processSegment(first);
4860 return Rectangle.create(min[0], min[1],
4861 max[0] - min[0], max[1] - min[1]);
4862 }
4863
4864 function getPenPadding(radius, matrix) {
4865 if (!matrix)
4866 return [radius, radius];
4867 var mx = matrix.createShiftless(),
4868 hor = mx.transform(new Point(radius, 0)),
4869 ver = mx.transform(new Point(0, radius)),
4870 phi = hor.getAngleInRadians(),
4871 a = hor.getLength(),
4872 b = ver.getLength();
4873 var tx = - Math.atan(b * Math.tan(phi)),
4874 ty = + Math.atan(b / Math.tan(phi)),
4875 x = a * Math.cos(tx) * Math.cos(phi)
4876 - b * Math.sin(tx) * Math.sin(phi),
4877 y = b * Math.sin(ty) * Math.cos(phi)
4878 + a * Math.cos(ty) * Math.sin(phi);
4879 return [Math.abs(x), Math.abs(y)];
4880 }
4881
4882 return {
4883 getBounds: function() {
4884 var useCache = arguments[0] === undefined;
4885 if (useCache && this._bounds)
4886 return this._bounds;
4887 var bounds = this._createBounds(getBounds(this, arguments[0]));
4888 if (useCache)
4889 this._bounds = bounds;
4890 return bounds;
4891 },
4892
4893 getStrokeBounds: function() {
4894 if (!this._style._strokeColor || !this._style._strokeWidth)
4895 return this.getBounds.apply(this, arguments);
4896 var useCache = arguments[0] === undefined;
4897 if (useCache && this._strokeBounds)
4898 return this._strokeBounds;
4899 var matrix = arguments[0],
4900 width = this.getStrokeWidth(),
4901 radius = width / 2,
4902 padding = getPenPadding(radius, matrix),
4903 join = this.getStrokeJoin(),
4904 cap = this.getStrokeCap(),
4905 miter = this.getMiterLimit() * width / 2,
4906 segments = this._segments,
4907 length = segments.length,
4908 bounds = getBounds(this, matrix, getPenPadding(radius));
4909 var joinBounds = new Rectangle(new Size(padding).multiply(2));
4910
4911 function add(point) {
4912 bounds = bounds.include(matrix
4913 ? matrix.transform(point) : point);
4914 }
4915
4916 function addBevelJoin(curve, t) {
4917 var point = curve.getPoint(t),
4918 normal = curve.getNormal(t).normalize(radius);
4919 add(point.add(normal));
4920 add(point.subtract(normal));
4921 }
4922
4923 function addJoin(segment, join) {
4924 if (join === 'round' || !segment._handleIn.isZero()
4925 && !segment._handleOut.isZero()) {
4926 bounds = bounds.unite(joinBounds.setCenter(matrix
4927 ? matrix.transform(segment._point) : segment._point));
4928 } else if (join == 'bevel') {
4929 var curve = segment.getCurve();
4930 addBevelJoin(curve, 0);
4931 addBevelJoin(curve.getPrevious(), 1);
4932 } else if (join == 'miter') {
4933 var curve2 = segment.getCurve(),
4934 curve1 = curve2.getPrevious(),
4935 point = curve2.getPoint(0),
4936 normal1 = curve1.getNormal(1).normalize(radius),
4937 normal2 = curve2.getNormal(0).normalize(radius),
4938 line1 = new Line(point.subtract(normal1),
4939 new Point(-normal1.y, normal1.x)),
4940 line2 = new Line(point.subtract(normal2),
4941 new Point(-normal2.y, normal2.x)),
4942 corner = line1.intersect(line2);
4943 if (!corner || point.getDistance(corner) > miter) {
4944 addJoin(segment, 'bevel');
4945 } else {
4946 add(corner);
4947 }
4948 }
4949 }
4950
4951 function addCap(segment, cap, t) {
4952 switch (cap) {
4953 case 'round':
4954 return addJoin(segment, cap);
4955 case 'butt':
4956 case 'square':
4957 var curve = segment.getCurve(),
4958 point = curve.getPoint(t),
4959 normal = curve.getNormal(t).normalize(radius);
4960 if (cap === 'square')
4961 point = point.add(normal.y, -normal.x);
4962 add(point.add(normal));
4963 add(point.subtract(normal));
4964 break;
4965 }
4966 }
4967
4968 for (var i = 1, l = length - (this._closed ? 0 : 1); i < l; i++) {
4969 addJoin(segments[i], join);
4970 }
4971 if (this._closed) {
4972 addJoin(segments[0], join);
4973 } else {
4974 addCap(segments[0], cap, 0);
4975 addCap(segments[length - 1], cap, 1);
4976 }
4977 if (useCache)
4978 this._strokeBounds = bounds;
4979 return bounds;
4980 },
4981
4982 getHandleBounds: function() {
4983 var matrix = arguments[0],
4984 useCache = matrix === undefined;
4985 if (useCache && this._handleBounds)
4986 return this._handleBounds;
4987 var coords = new Array(6),
4988 stroke = arguments[1] / 2 || 0,
4989 join = arguments[2] / 2 || 0,
4990 open = !this._closed,
4991 x1 = Infinity,
4992 x2 = -x1,
4993 y1 = x1,
4994 y2 = x2;
4995 for (var i = 0, l = this._segments.length; i < l; i++) {
4996 var segment = this._segments[i];
4997 segment._transformCoordinates(matrix, coords, false);
4998 for (var j = 0; j < 6; j += 2) {
4999 var padding = j == 0 ? join : stroke,
5000 x = coords[j],
5001 y = coords[j + 1],
5002 xn = x - padding,
5003 xx = x + padding,
5004 yn = y - padding,
5005 yx = y + padding;
5006 if (xn < x1) x1 = xn;
5007 if (xx > x2) x2 = xx;
5008 if (yn < y1) y1 = yn;
5009 if (yx > y2) y2 = yx;
5010 }
5011 }
5012 var bounds = Rectangle.create(x1, y1, x2 - x1, y2 - y1);
5013 if (useCache)
5014 this._handleBounds = bounds;
5015 return bounds;
5016 },
5017
5018 getRoughBounds: function() {
5019 var useCache = arguments[0] === undefined;
5020 if (useCache && this._roughBounds)
5021 return this._roughBounds;
5022 var bounds = this.getHandleBounds(arguments[0], this.strokeWidth,
5023 this.getStrokeJoin() == 'miter'
5024 ? this.strokeWidth * this.getMiterLimit()
5025 : this.strokeWidth);
5026 if (useCache)
5027 this._roughBounds = bounds;
5028 return bounds;
5029 }
5030 };
5031});
5032
5033Path.inject({ statics: new function() {
5034 var kappa = 2 / 3 * (Math.sqrt(2) - 1);
5035
5036 var ovalSegments = [
5037 new Segment([0, 0.5], [0, kappa ], [0, -kappa]),
5038 new Segment([0.5, 0], [-kappa, 0], [kappa, 0 ]),
5039 new Segment([1, 0.5], [0, -kappa], [0, kappa ]),
5040 new Segment([0.5, 1], [kappa, 0 ], [-kappa, 0])
5041 ];
5042
5043 return {
5044 Line: function() {
5045 var step = Math.floor(arguments.length / 2);
5046 return new Path(
5047 Segment.read(arguments, 0, step),
5048 Segment.read(arguments, step, step)
5049 );
5050 },
5051
5052 Rectangle: function(rect) {
5053 rect = Rectangle.read(arguments);
5054 var path = new Path(),
5055 corners = ['getBottomLeft', 'getTopLeft', 'getTopRight',
5056 'getBottomRight'],
5057 segments = new Array(4);
5058 for (var i = 0; i < 4; i++)
5059 segments[i] = new Segment(rect[corners[i]]());
5060 path._add(segments);
5061 path._closed = true;
5062 return path;
5063 },
5064
5065 RoundRectangle: function(rect, size) {
5066 if (arguments.length == 2) {
5067 rect = Rectangle.read(arguments, 0, 1);
5068 size = Size.read(arguments, 1, 1);
5069 } else if (arguments.length == 6) {
5070 rect = Rectangle.read(arguments, 0, 4);
5071 size = Size.read(arguments, 4, 2);
5072 }
5073 size = Size.min(size, rect.getSize().divide(2));
5074 var path = new Path(),
5075 uSize = size.multiply(kappa * 2),
5076 bl = rect.getBottomLeft(),
5077 tl = rect.getTopLeft(),
5078 tr = rect.getTopRight(),
5079 br = rect.getBottomRight();
5080 path._add([
5081 new Segment(bl.add(size.width, 0), null, [-uSize.width, 0]),
5082 new Segment(bl.subtract(0, size.height), [0, uSize.height], null),
5083
5084 new Segment(tl.add(0, size.height), null, [0, -uSize.height]),
5085 new Segment(tl.add(size.width, 0), [-uSize.width, 0], null),
5086
5087 new Segment(tr.subtract(size.width, 0), null, [uSize.width, 0]),
5088 new Segment(tr.add(0, size.height), [0, -uSize.height], null),
5089
5090 new Segment(br.subtract(0, size.height), null, [0, uSize.height]),
5091 new Segment(br.subtract(size.width, 0), [uSize.width, 0], null)
5092 ]);
5093 path._closed = true;
5094 return path;
5095 },
5096
5097 Oval: function(rect) {
5098 rect = Rectangle.read(arguments);
5099 var path = new Path(),
5100 topLeft = rect.getTopLeft(),
5101 size = new Size(rect.width, rect.height),
5102 segments = new Array(4);
5103 for (var i = 0; i < 4; i++) {
5104 var segment = ovalSegments[i];
5105 segments[i] = new Segment(
5106 segment._point.multiply(size).add(topLeft),
5107 segment._handleIn.multiply(size),
5108 segment._handleOut.multiply(size)
5109 );
5110 }
5111 path._add(segments);
5112 path._closed = true;
5113 return path;
5114 },
5115
5116 Circle: function(center, radius) {
5117 if (arguments.length == 3) {
5118 center = Point.read(arguments, 0, 2);
5119 radius = arguments[2];
5120 } else {
5121 center = Point.read(arguments, 0, 1);
5122 }
5123 return Path.Oval(new Rectangle(center.subtract(radius),
5124 new Size(radius * 2, radius * 2)));
5125 },
5126
5127 Arc: function(from, through, to) {
5128 var path = new Path();
5129 path.moveTo(from);
5130 path.arcTo(through, to);
5131 return path;
5132 },
5133
5134 RegularPolygon: function(center, numSides, radius) {
5135 center = Point.read(arguments, 0, 1);
5136 var path = new Path(),
5137 step = 360 / numSides,
5138 three = !(numSides % 3),
5139 vector = new Point(0, three ? -radius : radius),
5140 offset = three ? -1 : 0.5,
5141 segments = new Array(numSides);
5142 for (var i = 0; i < numSides; i++) {
5143 segments[i] = new Segment(center.add(
5144 vector.rotate((i + offset) * step)));
5145 }
5146 path._add(segments);
5147 path._closed = true;
5148 return path;
5149 },
5150
5151 Star: function(center, numPoints, radius1, radius2) {
5152 center = Point.read(arguments, 0, 1);
5153 numPoints *= 2;
5154 var path = new Path(),
5155 step = 360 / numPoints,
5156 vector = new Point(0, -1),
5157 segments = new Array(numPoints);
5158 for (var i = 0; i < numPoints; i++) {
5159 segments[i] = new Segment(center.add(
5160 vector.rotate(step * i).multiply(i % 2 ? radius2 : radius1)));
5161 }
5162 path._add(segments);
5163 path._closed = true;
5164 return path;
5165 }
5166 };
5167}});
5168
5169var CompoundPath = this.CompoundPath = PathItem.extend({
5170 initialize: function(paths) {
5171 this.base();
5172 this._children = [];
5173 this._namedChildren = {};
5174 var items = !paths || !Array.isArray(paths)
5175 || typeof paths[0] !== 'object' ? arguments : paths;
5176 this.addChildren(items);
5177 },
5178
5179 insertChild: function(index, item) {
5180 this.base(index, item);
5181 if (item._clockwise === undefined)
5182 item.setClockwise(item._index == 0);
5183 },
5184
5185 simplify: function() {
5186 if (this._children.length == 1) {
5187 var child = this._children[0];
5188 child.insertAbove(this);
5189 this.remove();
5190 return child;
5191 }
5192 return this;
5193 },
5194
5195 smooth: function() {
5196 for (var i = 0, l = this._children.length; i < l; i++)
5197 this._children[i].smooth();
5198 },
5199
5200 draw: function(ctx, param) {
5201 var l = this._children.length;
5202 if (l == 0) {
5203 return;
5204 }
5205 var firstChild = this._children[0];
5206 ctx.beginPath();
5207 param.compound = true;
5208 for (var i = 0; i < l; i++)
5209 Item.draw(this._children[i], ctx, param);
5210 firstChild._setStyles(ctx);
5211 var fillColor = firstChild.getFillColor(),
5212 strokeColor = firstChild.getStrokeColor();
5213 if (fillColor) {
5214 ctx.fillStyle = fillColor.getCanvasStyle(ctx);
5215 ctx.fill();
5216 }
5217 if (strokeColor) {
5218 ctx.strokeStyle = strokeColor.getCanvasStyle(ctx);
5219 ctx.stroke();
5220 }
5221 param.compound = false;
5222 }
5223}, new function() {
5224 function getCurrentPath(that) {
5225 if (!that._children.length)
5226 throw new Error('Use a moveTo() command first');
5227 return that._children[that._children.length - 1];
5228 }
5229
5230 var fields = {
5231 moveTo: function(point) {
5232 var path = new Path();
5233 this.addChild(path);
5234 path.moveTo.apply(path, arguments);
5235 },
5236
5237 moveBy: function(point) {
5238 this.moveTo(getCurrentPath(this).getLastSegment()._point.add(
5239 Point.read(arguments)));
5240 },
5241
5242 closePath: function() {
5243 getCurrentPath(this).setClosed(true);
5244 }
5245 };
5246
5247 Base.each(['lineTo', 'cubicCurveTo', 'quadraticCurveTo', 'curveTo',
5248 'arcTo', 'lineBy', 'curveBy', 'arcBy'], function(key) {
5249 fields[key] = function() {
5250 var path = getCurrentPath(this);
5251 path[key].apply(path, arguments);
5252 };
5253 });
5254
5255 return fields;
5256});
5257
5258var PathFlattener = Base.extend({
5259 initialize: function(path) {
5260 this.curves = [];
5261 this.parts = [];
5262 this.length = 0;
5263 this.index = 0;
5264
5265 var segments = path._segments,
5266 segment1 = segments[0],
5267 segment2,
5268 that = this;
5269
5270 function addCurve(segment1, segment2) {
5271 var curve = Curve.getValues(segment1, segment2);
5272 that.curves.push(curve);
5273 that._computeParts(curve, segment1._index, 0, 1);
5274 }
5275
5276 for (var i = 1, l = segments.length; i < l; i++) {
5277 segment2 = segments[i];
5278 addCurve(segment1, segment2);
5279 segment1 = segment2;
5280 }
5281 if (path._closed)
5282 addCurve(segment2, segments[0]);
5283 },
5284
5285 _computeParts: function(curve, index, minT, maxT) {
5286 if ((maxT - minT) > 1 / 32 && !Curve.isFlatEnough(curve)) {
5287 var curves = Curve.subdivide(curve);
5288 var halfT = (minT + maxT) / 2;
5289 this._computeParts(curves[0], index, minT, halfT);
5290 this._computeParts(curves[1], index, halfT, maxT);
5291 } else {
5292 var x = curve[6] - curve[0],
5293 y = curve[7] - curve[1],
5294 dist = Math.sqrt(x * x + y * y);
5295 if (dist > Numerical.TOLERANCE) {
5296 this.length += dist;
5297 this.parts.push({
5298 offset: this.length,
5299 value: maxT,
5300 index: index
5301 });
5302 }
5303 }
5304 },
5305
5306 getParameterAt: function(offset) {
5307 var i, j = this.index;
5308 for (;;) {
5309 i = j;
5310 if (j == 0 || this.parts[--j].offset < offset)
5311 break;
5312 }
5313 for (var l = this.parts.length; i < l; i++) {
5314 var part = this.parts[i];
5315 if (part.offset >= offset) {
5316 this.index = i;
5317 var prev = this.parts[i - 1];
5318 var prevVal = prev && prev.index == part.index ? prev.value : 0,
5319 prevLen = prev ? prev.offset : 0;
5320 return {
5321 value: prevVal + (part.value - prevVal)
5322 * (offset - prevLen) / (part.offset - prevLen),
5323 index: part.index
5324 };
5325 }
5326 }
5327 var part = this.parts[this.parts.length - 1];
5328 return {
5329 value: 1,
5330 index: part.index
5331 };
5332 },
5333
5334 evaluate: function(offset, type) {
5335 var param = this.getParameterAt(offset);
5336 return Curve.evaluate(this.curves[param.index], param.value, type);
5337 },
5338
5339 drawPart: function(ctx, from, to) {
5340 from = this.getParameterAt(from);
5341 to = this.getParameterAt(to);
5342 for (var i = from.index; i <= to.index; i++) {
5343 var curve = Curve.getPart(this.curves[i],
5344 i == from.index ? from.value : 0,
5345 i == to.index ? to.value : 1);
5346 if (i == from.index)
5347 ctx.moveTo(curve[0], curve[1]);
5348 ctx.bezierCurveTo.apply(ctx, curve.slice(2));
5349 }
5350 }
5351});
5352
5353var PathFitter = Base.extend({
5354 initialize: function(path, error) {
5355 this.points = [];
5356 var segments = path._segments,
5357 prev;
5358 for (var i = 0, l = segments.length; i < l; i++) {
5359 var point = segments[i].point.clone();
5360 if (!prev || !prev.equals(point)) {
5361 this.points.push(point);
5362 prev = point;
5363 }
5364 }
5365 this.error = error;
5366 },
5367
5368 fit: function() {
5369 this.segments = [new Segment(this.points[0])];
5370 this.fitCubic(0, this.points.length - 1,
5371 this.points[1].subtract(this.points[0]).normalize(),
5372 this.points[this.points.length - 2].subtract(
5373 this.points[this.points.length - 1]).normalize());
5374 return this.segments;
5375 },
5376
5377 fitCubic: function(first, last, tan1, tan2) {
5378 if (last - first == 1) {
5379 var pt1 = this.points[first],
5380 pt2 = this.points[last],
5381 dist = pt1.getDistance(pt2) / 3;
5382 this.addCurve([pt1, pt1.add(tan1.normalize(dist)),
5383 pt2.add(tan2.normalize(dist)), pt2]);
5384 return;
5385 }
5386 var uPrime = this.chordLengthParameterize(first, last),
5387 maxError = Math.max(this.error, this.error * this.error),
5388 error,
5389 split;
5390 for (var i = 0; i <= 4; i++) {
5391 var curve = this.generateBezier(first, last, uPrime, tan1, tan2);
5392 var max = this.findMaxError(first, last, curve, uPrime);
5393 if (max.error < this.error) {
5394 this.addCurve(curve);
5395 return;
5396 }
5397 split = max.index;
5398 if (max.error >= maxError)
5399 break;
5400 this.reparameterize(first, last, uPrime, curve);
5401 maxError = max.error;
5402 }
5403 var V1 = this.points[split - 1].subtract(this.points[split]),
5404 V2 = this.points[split].subtract(this.points[split + 1]),
5405 tanCenter = V1.add(V2).divide(2).normalize();
5406 this.fitCubic(first, split, tan1, tanCenter);
5407 this.fitCubic(split, last, tanCenter.negate(), tan2);
5408 },
5409
5410 addCurve: function(curve) {
5411 var prev = this.segments[this.segments.length - 1];
5412 prev.setHandleOut(curve[1].subtract(curve[0]));
5413 this.segments.push(
5414 new Segment(curve[3], curve[2].subtract(curve[3])));
5415 },
5416
5417 generateBezier: function(first, last, uPrime, tan1, tan2) {
5418 var epsilon = Numerical.EPSILON,
5419 pt1 = this.points[first],
5420 pt2 = this.points[last],
5421 C = [[0, 0], [0, 0]],
5422 X = [0, 0];
5423
5424 for (var i = 0, l = last - first + 1; i < l; i++) {
5425 var u = uPrime[i],
5426 t = 1 - u,
5427 b = 3 * u * t,
5428 b0 = t * t * t,
5429 b1 = b * t,
5430 b2 = b * u,
5431 b3 = u * u * u,
5432 a1 = tan1.normalize(b1),
5433 a2 = tan2.normalize(b2),
5434 tmp = this.points[first + i]
5435 .subtract(pt1.multiply(b0 + b1))
5436 .subtract(pt2.multiply(b2 + b3));
5437 C[0][0] += a1.dot(a1);
5438 C[0][1] += a1.dot(a2);
5439 C[1][0] = C[0][1];
5440 C[1][1] += a2.dot(a2);
5441 X[0] += a1.dot(tmp);
5442 X[1] += a2.dot(tmp);
5443 }
5444
5445 var detC0C1 = C[0][0] * C[1][1] - C[1][0] * C[0][1],
5446 alpha1, alpha2;
5447 if (Math.abs(detC0C1) > epsilon) {
5448 var detC0X = C[0][0] * X[1] - C[1][0] * X[0],
5449 detXC1 = X[0] * C[1][1] - X[1] * C[0][1];
5450 alpha1 = detXC1 / detC0C1;
5451 alpha2 = detC0X / detC0C1;
5452 } else {
5453 var c0 = C[0][0] + C[0][1],
5454 c1 = C[1][0] + C[1][1];
5455 if (Math.abs(c0) > epsilon) {
5456 alpha1 = alpha2 = X[0] / c0;
5457 } else if (Math.abs(c0) > epsilon) {
5458 alpha1 = alpha2 = X[1] / c1;
5459 } else {
5460 alpha1 = alpha2 = 0.;
5461 }
5462 }
5463
5464 var segLength = pt2.getDistance(pt1);
5465 epsilon *= segLength;
5466 if (alpha1 < epsilon || alpha2 < epsilon) {
5467 alpha1 = alpha2 = segLength / 3;
5468 }
5469
5470 return [pt1, pt1.add(tan1.normalize(alpha1)),
5471 pt2.add(tan2.normalize(alpha2)), pt2];
5472 },
5473
5474 reparameterize: function(first, last, u, curve) {
5475 for (var i = first; i <= last; i++) {
5476 u[i - first] = this.findRoot(curve, this.points[i], u[i - first]);
5477 }
5478 },
5479
5480 findRoot: function(curve, point, u) {
5481 var curve1 = [],
5482 curve2 = [];
5483 for (var i = 0; i <= 2; i++) {
5484 curve1[i] = curve[i + 1].subtract(curve[i]).multiply(3);
5485 }
5486 for (var i = 0; i <= 1; i++) {
5487 curve2[i] = curve1[i + 1].subtract(curve1[i]).multiply(2);
5488 }
5489 var pt = this.evaluate(3, curve, u),
5490 pt1 = this.evaluate(2, curve1, u),
5491 pt2 = this.evaluate(1, curve2, u),
5492 diff = pt.subtract(point),
5493 df = pt1.dot(pt1) + diff.dot(pt2);
5494 if (Math.abs(df) < Numerical.TOLERANCE)
5495 return u;
5496 return u - diff.dot(pt1) / df;
5497 },
5498
5499 evaluate: function(degree, curve, t) {
5500 var tmp = curve.slice();
5501 for (var i = 1; i <= degree; i++) {
5502 for (var j = 0; j <= degree - i; j++) {
5503 tmp[j] = tmp[j].multiply(1 - t).add(tmp[j + 1].multiply(t));
5504 }
5505 }
5506 return tmp[0];
5507 },
5508
5509 chordLengthParameterize: function(first, last) {
5510 var u = [0];
5511 for (var i = first + 1; i <= last; i++) {
5512 u[i - first] = u[i - first - 1]
5513 + this.points[i].getDistance(this.points[i - 1]);
5514 }
5515 for (var i = 1, m = last - first; i <= m; i++) {
5516 u[i] /= u[m];
5517 }
5518 return u;
5519 },
5520
5521 findMaxError: function(first, last, curve, u) {
5522 var index = Math.floor((last - first + 1) / 2),
5523 maxDist = 0;
5524 for (var i = first + 1; i < last; i++) {
5525 var P = this.evaluate(3, curve, u[i - first]);
5526 var v = P.subtract(this.points[i]);
5527 var dist = v.x * v.x + v.y * v.y;
5528 if (dist >= maxDist) {
5529 maxDist = dist;
5530 index = i;
5531 }
5532 }
5533 return {
5534 error: maxDist,
5535 index: index
5536 };
5537 }
5538});
5539
5540var TextItem = this.TextItem = Item.extend({
5541 initialize: function() {
5542 this.base();
5543 this._content = '';
5544 this._characterStyle = CharacterStyle.create(this);
5545 this.setCharacterStyle(this._project.getCurrentStyle());
5546 this._paragraphStyle = ParagraphStyle.create(this);
5547 this.setParagraphStyle();
5548 },
5549
5550 _clone: function(copy) {
5551 copy._content = this._content;
5552 copy.setCharacterStyle(this._characterStyle);
5553 copy.setParagraphStyle(this._paragraphStyle);
5554 return this.base(copy);
5555 },
5556
5557 getContent: function() {
5558 return this._content;
5559 },
5560
5561 setContent: function(content) {
5562 this._changed(Change.CONTENT);
5563 this._content = '' + content;
5564 },
5565
5566 getCharacterStyle: function() {
5567 return this._characterStyle;
5568 },
5569
5570 setCharacterStyle: function(style) {
5571 this._characterStyle.initialize(style);
5572 },
5573
5574 getParagraphStyle: function() {
5575 return this._paragraphStyle;
5576 },
5577
5578 setParagraphStyle: function(style) {
5579 this._paragraphStyle.initialize(style);
5580 }
5581});
5582
5583var PointText = this.PointText = TextItem.extend({
5584 initialize: function(point) {
5585 this.base();
5586 var point = Point.read(arguments);
5587 this._point = LinkedPoint.create(this, 'setPoint', point.x, point.y);
5588 this._matrix = new Matrix().translate(point);
5589 },
5590
5591 clone: function() {
5592 var copy = this._clone(new PointText(this._point));
5593 copy._matrix.initialize(this._matrix);
5594 return copy;
5595 },
5596
5597 getPoint: function() {
5598 return this._point;
5599 },
5600
5601 setPoint: function(point) {
5602 this._transform(new Matrix().translate(
5603 Point.read(arguments).subtract(this._point)));
5604 },
5605
5606 getPosition: function() {
5607 return this._point;
5608 },
5609
5610 setPosition: function(point) {
5611 this.setPoint.apply(this, arguments);
5612 },
5613
5614 _transform: function(matrix, flags) {
5615 this._matrix.preConcatenate(matrix);
5616 matrix._transformPoint(this._point, this._point, true);
5617 },
5618
5619 draw: function(ctx) {
5620 if (!this._content)
5621 return;
5622 ctx.save();
5623 ctx.font = this.getFontSize() + 'pt ' + this.getFont();
5624 ctx.textAlign = this.getJustification();
5625 this._matrix.applyToContext(ctx);
5626
5627 var fillColor = this.getFillColor();
5628 var strokeColor = this.getStrokeColor();
5629 if (!fillColor || !strokeColor)
5630 ctx.globalAlpha = this._opacity;
5631 if (fillColor) {
5632 ctx.fillStyle = fillColor.getCanvasStyle(ctx);
5633 ctx.fillText(this._content, 0, 0);
5634 }
5635 if (strokeColor) {
5636 ctx.strokeStyle = strokeColor.getCanvasStyle(ctx);
5637 ctx.strokeText(this._content, 0, 0);
5638 }
5639 ctx.restore();
5640 }
5641});
5642
5643var Style = Item.extend({
5644 initialize: function(style) {
5645 var clone = style instanceof Style;
5646 return Base.each(this._defaults, function(value, key) {
5647 value = style && style[key] || value;
5648 this[key] = value && clone && value.clone
5649 ? value.clone() : value;
5650 }, this);
5651 },
5652
5653 statics: {
5654 create: function(item) {
5655 var style = new this(this.dont);
5656 style._item = item;
5657 return style;
5658 },
5659
5660 extend: function(src) {
5661 var styleKey = src._style,
5662 flags = src._flags || {};
5663 src._owner.inject(Base.each(src._defaults, function(value, key) {
5664 var isColor = !!key.match(/Color$/),
5665 part = Base.capitalize(key),
5666 set = 'set' + part,
5667 get = 'get' + part;
5668 src[set] = function(value) {
5669 var children = this._item && this._item._children;
5670 value = isColor ? Color.read(arguments) : value;
5671 if (children) {
5672 for (var i = 0, l = children.length; i < l; i++)
5673 children[i][styleKey][set](value);
5674 } else {
5675 var old = this['_' + key];
5676 if (old != value && !(old && old.equals
5677 && old.equals(value))) {
5678 this['_' + key] = value;
5679 if (isColor) {
5680 if (old)
5681 old._removeOwner(this._item);
5682 if (value)
5683 value._addOwner(this._item);
5684 }
5685 if (this._item)
5686 this._item._changed(flags[key] || Change.STYLE);
5687 }
5688 }
5689 return this;
5690 };
5691 src[get] = function() {
5692 var children = this._item && this._item._children,
5693 style;
5694 if (!children)
5695 return this['_' + key];
5696 for (var i = 0, l = children.length; i < l; i++) {
5697 var childStyle = children[i][styleKey][get]();
5698 if (!style) {
5699 style = childStyle;
5700 } else if (style != childStyle && !(style
5701 && style.equals && style.equals(childStyle))) {
5702 return undefined;
5703 }
5704 }
5705 return style;
5706 };
5707 this[set] = function(value) {
5708 this[styleKey][set](value);
5709 return this;
5710 };
5711 this[get] = function() {
5712 return this[styleKey][get]();
5713 };
5714 }, {}));
5715 return this.base(src);
5716 }
5717 }
5718});
5719
5720var PathStyle = this.PathStyle = Style.extend({
5721 _defaults: {
5722 fillColor: undefined,
5723 strokeColor: undefined,
5724 strokeWidth: 1,
5725 strokeCap: 'butt',
5726 strokeJoin: 'miter',
5727 miterLimit: 10,
5728 dashOffset: 0,
5729 dashArray: []
5730 },
5731 _flags: {
5732 strokeWidth: Change.STROKE,
5733 strokeCap: Change.STROKE,
5734 strokeJoin: Change.STROKE,
5735 miterLimit: Change.STROKE
5736 },
5737 _owner: Item,
5738 _style: '_style'
5739
5740});
5741
5742var ParagraphStyle = this.ParagraphStyle = Style.extend({
5743 _defaults: {
5744 justification: 'left'
5745 },
5746 _owner: TextItem,
5747 _style: '_paragraphStyle'
5748
5749});
5750
5751var CharacterStyle = this.CharacterStyle = PathStyle.extend({
5752 _defaults: Base.merge(PathStyle.prototype._defaults, {
5753 fillColor: 'black',
5754 fontSize: 10,
5755 font: 'sans-serif'
5756 }),
5757 _owner: TextItem,
5758 _style: '_characterStyle'
5759
5760});
5761
5762var Color = this.Color = Base.extend(new function() {
5763
5764 var components = {
5765 gray: ['gray'],
5766 rgb: ['red', 'green', 'blue'],
5767 hsb: ['hue', 'saturation', 'brightness'],
5768 hsl: ['hue', 'saturation', 'lightness']
5769 };
5770
5771 var colorCache = {},
5772 colorContext;
5773
5774 function nameToRGBColor(name) {
5775 var color = colorCache[name];
5776 if (color)
5777 return color.clone();
5778 if (!colorContext) {
5779 var canvas = CanvasProvider.getCanvas(Size.create(1, 1));
5780 colorContext = canvas.getContext('2d');
5781 colorContext.globalCompositeOperation = 'copy';
5782 }
5783 colorContext.fillStyle = 'rgba(0,0,0,0)';
5784 colorContext.fillStyle = name;
5785 colorContext.fillRect(0, 0, 1, 1);
5786 var data = colorContext.getImageData(0, 0, 1, 1).data,
5787 rgb = [data[0] / 255, data[1] / 255, data[2] / 255];
5788 return (colorCache[name] = RGBColor.read(rgb)).clone();
5789 }
5790
5791 function hexToRGBColor(string) {
5792 var hex = string.match(/^#?(\w{1,2})(\w{1,2})(\w{1,2})$/);
5793 if (hex.length >= 4) {
5794 var rgb = new Array(3);
5795 for (var i = 0; i < 3; i++) {
5796 var channel = hex[i + 1];
5797 rgb[i] = parseInt(channel.length == 1
5798 ? channel + channel : channel, 16) / 255;
5799 }
5800 return RGBColor.read(rgb);
5801 }
5802 }
5803
5804 var hsbIndices = [
5805 [0, 3, 1],
5806 [2, 0, 1],
5807 [1, 0, 3],
5808 [1, 2, 0],
5809 [3, 1, 0],
5810 [0, 1, 2]
5811 ];
5812
5813 var converters = {
5814 'rgb-hsb': function(color) {
5815 var r = color._red,
5816 g = color._green,
5817 b = color._blue,
5818 max = Math.max(r, g, b),
5819 min = Math.min(r, g, b),
5820 delta = max - min,
5821 h = delta == 0 ? 0
5822 : ( max == r ? (g - b) / delta + (g < b ? 6 : 0)
5823 : max == g ? (b - r) / delta + 2
5824 : (r - g) / delta + 4) * 60,
5825 s = max == 0 ? 0 : delta / max,
5826 v = max;
5827 return new HSBColor(h, s, v, color._alpha);
5828 },
5829
5830 'hsb-rgb': function(color) {
5831 var h = (color._hue / 60) % 6,
5832 s = color._saturation,
5833 b = color._brightness,
5834 i = Math.floor(h),
5835 f = h - i,
5836 i = hsbIndices[i],
5837 v = [
5838 b,
5839 b * (1 - s),
5840 b * (1 - s * f),
5841 b * (1 - s * (1 - f))
5842 ];
5843 return new RGBColor(v[i[0]], v[i[1]], v[i[2]], color._alpha);
5844 },
5845
5846 'rgb-hsl': function(color) {
5847 var r = color._red,
5848 g = color._green,
5849 b = color._blue,
5850 max = Math.max(r, g, b),
5851 min = Math.min(r, g, b),
5852 delta = max - min,
5853 h = delta == 0 ? 0
5854 : ( max == r ? (g - b) / delta + (g < b ? 6 : 0)
5855 : max == g ? (b - r) / delta + 2
5856 : (r - g) / delta + 4) * 60,
5857 l = (max + min) / 2,
5858 s = l < 0.5
5859 ? delta / (max + min)
5860 : delta / (2 - max - min);
5861 return new HSLColor(h, s, l, color._alpha);
5862 },
5863
5864 'hsl-rgb': function(color) {
5865 var s = color._saturation,
5866 h = color._hue / 360,
5867 l = color._lightness,
5868 t1, t2, c;
5869 if (s == 0)
5870 return new RGBColor(l, l, l, color._alpha);
5871 var t3s = [ h + 1 / 3, h, h - 1 / 3 ],
5872 t2 = l < 0.5 ? l * (1 + s) : l + s - l * s,
5873 t1 = 2 * l - t2,
5874 c = [];
5875 for (var i = 0; i < 3; i++) {
5876 var t3 = t3s[i];
5877 if (t3 < 0) t3 += 1;
5878 if (t3 > 1) t3 -= 1;
5879 c[i] = 6 * t3 < 1
5880 ? t1 + (t2 - t1) * 6 * t3
5881 : 2 * t3 < 1
5882 ? t2
5883 : 3 * t3 < 2
5884 ? t1 + (t2 - t1) * ((2 / 3) - t3) * 6
5885 : t1;
5886 }
5887 return new RGBColor(c[0], c[1], c[2], color._alpha);
5888 },
5889
5890 'rgb-gray': function(color) {
5891 return new GrayColor(1 - (color._red * 0.2989 + color._green * 0.587
5892 + color._blue * 0.114), color._alpha);
5893 },
5894
5895 'gray-rgb': function(color) {
5896 var comp = 1 - color._gray;
5897 return new RGBColor(comp, comp, comp, color._alpha);
5898 },
5899
5900 'gray-hsb': function(color) {
5901 return new HSBColor(0, 0, 1 - color._gray, color._alpha);
5902 },
5903
5904 'gray-hsl': function(color) {
5905 return new HSLColor(0, 0, 1 - color._gray, color._alpha);
5906 }
5907 };
5908
5909 var fields = {
5910 _readNull: true,
5911
5912 initialize: function(arg) {
5913 var isArray = Array.isArray(arg),
5914 type = this._colorType;
5915 if (typeof arg === 'object' && !isArray) {
5916 if (!type) {
5917 return arg.red !== undefined
5918 ? new RGBColor(arg.red, arg.green, arg.blue, arg.alpha)
5919 : arg.gray !== undefined
5920 ? new GrayColor(arg.gray, arg.alpha)
5921 : arg.lightness !== undefined
5922 ? new HSLColor(arg.hue, arg.saturation, arg.lightness,
5923 arg.alpha)
5924 : arg.hue !== undefined
5925 ? new HSBColor(arg.hue, arg.saturation, arg.brightness,
5926 arg.alpha)
5927 : new RGBColor();
5928 } else {
5929 return Color.read(arguments).convert(type);
5930 }
5931 } else if (typeof arg === 'string') {
5932 var rgbColor = arg.match(/^#[0-9a-f]{3,6}$/i)
5933 ? hexToRGBColor(arg)
5934 : nameToRGBColor(arg);
5935 return type
5936 ? rgbColor.convert(type)
5937 : rgbColor;
5938 } else {
5939 var components = isArray ? arg
5940 : Array.prototype.slice.call(arguments);
5941 if (!type) {
5942 if (components.length >= 3)
5943 return new RGBColor(components);
5944 return new GrayColor(components);
5945 } else {
5946 Base.each(this._components,
5947 function(name, i) {
5948 var value = components[i];
5949 this['_' + name] = value !== undefined
5950 ? value : null;
5951 },
5952 this);
5953 }
5954 }
5955 },
5956
5957 clone: function() {
5958 var ctor = this.constructor,
5959 copy = new ctor(ctor.dont),
5960 components = this._components;
5961 for (var i = 0, l = components.length; i < l; i++) {
5962 var key = '_' + components[i];
5963 copy[key] = this[key];
5964 }
5965 return copy;
5966 },
5967
5968 convert: function(type) {
5969 var converter;
5970 return this._colorType == type
5971 ? this.clone()
5972 : (converter = converters[this._colorType + '-' + type])
5973 ? converter(this)
5974 : converters['rgb-' + type](
5975 converters[this._colorType + '-rgb'](this));
5976 },
5977
5978 statics: {
5979 extend: function(src) {
5980 src.beans = true;
5981 if (src._colorType) {
5982 var comps = components[src._colorType];
5983 src._components = comps.concat(['alpha']);
5984 Base.each(comps, function(name) {
5985 var isHue = name === 'hue',
5986 part = Base.capitalize(name),
5987 name = '_' + name;
5988 this['get' + part] = function() {
5989 return this[name];
5990 };
5991 this['set' + part] = function(value) {
5992 this[name] = isHue
5993 ? ((value % 360) + 360) % 360
5994 : Math.min(Math.max(value, 0), 1);
5995 this._changed();
5996 return this;
5997 };
5998 }, src);
5999 }
6000 return this.base(src);
6001 }
6002 }
6003 };
6004
6005 Base.each(components, function(comps, type) {
6006 Base.each(comps, function(component) {
6007 var part = Base.capitalize(component);
6008 fields['get' + part] = function() {
6009 return this.convert(type)[component];
6010 };
6011 fields['set' + part] = function(value) {
6012 var color = this.convert(type);
6013 color[component] = value;
6014 color = color.convert(this._colorType);
6015 for (var i = 0, l = this._components.length; i < l; i++) {
6016 var key = this._components[i];
6017 this[key] = color[key];
6018 }
6019 };
6020 });
6021 });
6022
6023 return fields;
6024}, {
6025
6026 _changed: function() {
6027 this._cssString = null;
6028 for (var i = 0, l = this._owners && this._owners.length; i < l; i++)
6029 this._owners[i]._changed(Change.STYLE);
6030 },
6031
6032 _addOwner: function(item) {
6033 if (!this._owners)
6034 this._owners = [];
6035 this._owners.push(item);
6036 },
6037
6038 _removeOwner: function(item) {
6039 var index = this._owners ? this._owners.indexOf(item) : -1;
6040 if (index != -1) {
6041 this._owners.splice(index, 1);
6042 if (this._owners.length == 0)
6043 delete this._owners;
6044 }
6045 },
6046
6047 getType: function() {
6048 return this._colorType;
6049 },
6050
6051 getComponents: function() {
6052 var length = this._components.length;
6053 var comps = new Array(length);
6054 for (var i = 0; i < length; i++)
6055 comps[i] = this['_' + this._components[i]];
6056 return comps;
6057 },
6058
6059 getAlpha: function() {
6060 return this._alpha != null ? this._alpha : 1;
6061 },
6062
6063 setAlpha: function(alpha) {
6064 this._alpha = alpha == null ? null : Math.min(Math.max(alpha, 0), 1);
6065 this._changed();
6066 return this;
6067 },
6068
6069 hasAlpha: function() {
6070 return this._alpha != null;
6071 },
6072
6073 equals: function(color) {
6074 if (color && color._colorType === this._colorType) {
6075 for (var i = 0, l = this._components.length; i < l; i++) {
6076 var component = '_' + this._components[i];
6077 if (this[component] !== color[component])
6078 return false;
6079 }
6080 return true;
6081 }
6082 return false;
6083 },
6084
6085 toString: function() {
6086 var parts = [],
6087 format = Base.formatNumber;
6088 for (var i = 0, l = this._components.length; i < l; i++) {
6089 var component = this._components[i],
6090 value = this['_' + component];
6091 if (component === 'alpha' && value == null)
6092 value = 1;
6093 parts.push(component + ': ' + format(value));
6094 }
6095 return '{ ' + parts.join(', ') + ' }';
6096 },
6097
6098 toCssString: function() {
6099 if (!this._cssString) {
6100 var color = this.convert('rgb'),
6101 alpha = color.getAlpha(),
6102 components = [
6103 Math.round(color._red * 255),
6104 Math.round(color._green * 255),
6105 Math.round(color._blue * 255),
6106 alpha != null ? alpha : 1
6107 ];
6108 this._cssString = 'rgba(' + components.join(', ') + ')';
6109 }
6110 return this._cssString;
6111 },
6112
6113 getCanvasStyle: function() {
6114 return this.toCssString();
6115 }
6116
6117});
6118
6119var GrayColor = this.GrayColor = Color.extend({
6120
6121 _colorType: 'gray'
6122});
6123
6124var RGBColor = this.RGBColor = Color.extend({
6125
6126 _colorType: 'rgb'
6127});
6128
6129var HSBColor = this.HSBColor = Color.extend({
6130
6131 _colorType: 'hsb'
6132});
6133
6134var HSLColor = this.HSLColor = Color.extend({
6135
6136 _colorType: 'hsl'
6137});
6138
6139var GradientColor = this.GradientColor = Color.extend({
6140
6141 initialize: function(gradient, origin, destination, hilite) {
6142 this.gradient = gradient || new Gradient();
6143 this.setOrigin(origin);
6144 this.setDestination(destination);
6145 if (hilite)
6146 this.setHilite(hilite);
6147 },
6148
6149 clone: function() {
6150 return new GradientColor(this.gradient, this._origin, this._destination,
6151 this._hilite);
6152 },
6153
6154 getOrigin: function() {
6155 return this._origin;
6156 },
6157
6158 setOrigin: function(origin) {
6159 origin = Point.read(arguments).clone();
6160 this._origin = origin;
6161 if (this._destination)
6162 this._radius = this._destination.getDistance(this._origin);
6163 this._changed();
6164 return this;
6165 },
6166
6167 getDestination: function() {
6168 return this._destination;
6169 },
6170
6171 setDestination: function(destination) {
6172 destination = Point.read(arguments).clone();
6173 this._destination = destination;
6174 this._radius = this._destination.getDistance(this._origin);
6175 this._changed();
6176 return this;
6177 },
6178
6179 getHilite: function() {
6180 return this._hilite;
6181 },
6182
6183 setHilite: function(hilite) {
6184 hilite = Point.read(arguments).clone();
6185 var vector = hilite.subtract(this._origin);
6186 if (vector.getLength() > this._radius) {
6187 this._hilite = this._origin.add(
6188 vector.normalize(this._radius - 0.1));
6189 } else {
6190 this._hilite = hilite;
6191 }
6192 this._changed();
6193 return this;
6194 },
6195
6196 getCanvasStyle: function(ctx) {
6197 var gradient;
6198 if (this.gradient.type === 'linear') {
6199 gradient = ctx.createLinearGradient(this._origin.x, this._origin.y,
6200 this._destination.x, this._destination.y);
6201 } else {
6202 var origin = this._hilite || this._origin;
6203 gradient = ctx.createRadialGradient(origin.x, origin.y,
6204 0, this._origin.x, this._origin.y, this._radius);
6205 }
6206 for (var i = 0, l = this.gradient._stops.length; i < l; i++) {
6207 var stop = this.gradient._stops[i];
6208 gradient.addColorStop(stop._rampPoint, stop._color.toCssString());
6209 }
6210 return gradient;
6211 },
6212
6213 equals: function(color) {
6214 return color == this || color && color._colorType === this._colorType
6215 && this.gradient.equals(color.gradient)
6216 && this._origin.equals(color._origin)
6217 && this._destination.equals(color._destination);
6218 },
6219
6220 transform: function(matrix) {
6221 matrix._transformPoint(this._origin, this._origin, true);
6222 matrix._transformPoint(this._destination, this._destination, true);
6223 if (this._hilite)
6224 matrix._transformPoint(this._hilite, this._hilite, true);
6225 this._radius = this._destination.getDistance(this._origin);
6226 }
6227});
6228
6229var Gradient = this.Gradient = Base.extend({
6230 initialize: function(stops, type) {
6231 this.setStops(stops || ['white', 'black']);
6232 this.type = type || 'linear';
6233 },
6234
6235 clone: function() {
6236 var stops = [];
6237 for (var i = 0, l = this._stops.length; i < l; i++)
6238 stops[i] = this._stops[i].clone();
6239 return new Gradient(stops, this.type);
6240 },
6241
6242 getStops: function() {
6243 return this._stops;
6244 },
6245
6246 setStops: function(stops) {
6247 if (stops.length < 2)
6248 throw new Error(
6249 'Gradient stop list needs to contain at least two stops.');
6250 this._stops = GradientStop.readAll(stops);
6251 for (var i = 0, l = this._stops.length; i < l; i++) {
6252 var stop = this._stops[i];
6253 if (stop._defaultRamp)
6254 stop.setRampPoint(i / (l - 1));
6255 }
6256 },
6257
6258 equals: function(gradient) {
6259 if (gradient.type != this.type)
6260 return false;
6261 if (this._stops.length == gradient._stops.length) {
6262 for (var i = 0, l = this._stops.length; i < l; i++) {
6263 if (!this._stops[i].equals(gradient._stops[i]))
6264 return false;
6265 }
6266 return true;
6267 }
6268 return false;
6269 }
6270});
6271
6272var GradientStop = this.GradientStop = Base.extend({
6273 initialize: function(arg0, arg1) {
6274 if (arg1 === undefined && Array.isArray(arg0)) {
6275 this.setColor(arg0[0]);
6276 this.setRampPoint(arg0[1]);
6277 } else if (arg0.color) {
6278 this.setColor(arg0.color);
6279 this.setRampPoint(arg0.rampPoint);
6280 } else {
6281 this.setColor(arg0);
6282 this.setRampPoint(arg1);
6283 }
6284 },
6285
6286 clone: function() {
6287 return new GradientStop(this._color.clone(), this._rampPoint);
6288 },
6289
6290 getRampPoint: function() {
6291 return this._rampPoint;
6292 },
6293
6294 setRampPoint: function(rampPoint) {
6295 this._defaultRamp = rampPoint == null;
6296 this._rampPoint = rampPoint || 0;
6297 },
6298
6299 getColor: function() {
6300 return this._color;
6301 },
6302
6303 setColor: function(color) {
6304 this._color = Color.read(arguments);
6305 },
6306
6307 equals: function(stop) {
6308 return stop == this || stop instanceof GradientStop
6309 && this._color.equals(stop._color)
6310 && this._rampPoint == stop._rampPoint;
6311 }
6312});
6313
6314var DomElement = {
6315 getBounds: function(el, viewport) {
6316 var rect = el.getBoundingClientRect(),
6317 doc = el.ownerDocument,
6318 body = doc.body,
6319 docEl = doc.documentElement,
6320 x = rect.left - (docEl.clientLeft || body.clientLeft || 0),
6321 y = rect.top - (docEl.clientTop || body.clientTop || 0);
6322 if (!viewport) {
6323 var win = DomElement.getViewport(doc);
6324 x += win.pageXOffset || docEl.scrollLeft || body.scrollLeft;
6325 y += win.pageYOffset || docEl.scrollTop || body.scrollTop;
6326 }
6327 return new Rectangle(x, y, rect.width, rect.height);
6328 },
6329
6330 getOffset: function(el, viewport) {
6331 return this.getBounds(el, viewport).getPoint();
6332 },
6333
6334 getSize: function(el) {
6335 return this.getBounds(el, true).getSize();
6336 },
6337
6338 isInvisible: function(el) {
6339 return this.getSize(el).equals([0, 0]);
6340 },
6341
6342 isVisible: function(el) {
6343 return !this.isInvisible(el) && this.getViewportBounds(el).intersects(
6344 this.getBounds(el, true));
6345 },
6346
6347 getViewport: function(doc) {
6348 return doc.defaultView || doc.parentWindow;
6349 },
6350
6351 getViewportBounds: function(el) {
6352 var doc = el.ownerDocument,
6353 view = this.getViewport(doc),
6354 body = doc.getElementsByTagName(
6355 doc.compatMode === 'CSS1Compat' ? 'html' : 'body')[0];
6356 return Rectangle.create(0, 0,
6357 view.innerWidth || body.clientWidth,
6358 view.innerHeight || body.clientHeight
6359 );
6360 },
6361
6362 getComputedStyle: function(el, name) {
6363 if (el.currentStyle)
6364 return el.currentStyle[Base.camelize(name)];
6365 var style = this.getViewport(el.ownerDocument)
6366 .getComputedStyle(el, null);
6367 return style ? style.getPropertyValue(Base.hyphenate(name)) : null;
6368 }
6369};
6370
6371var DomEvent = {
6372 add: function(el, events) {
6373 for (var type in events) {
6374 var func = events[type];
6375 if (el.addEventListener) {
6376 el.addEventListener(type, func, false);
6377 } else if (el.attachEvent) {
6378 el.attachEvent('on' + type, func.bound = function() {
6379 func.call(el, window.event);
6380 });
6381 }
6382 }
6383 },
6384
6385 remove: function(el, events) {
6386 for (var type in events) {
6387 var func = events[type];
6388 if (el.removeEventListener) {
6389 el.removeEventListener(type, func, false);
6390 } else if (el.detachEvent) {
6391 el.detachEvent('on' + type, func.bound);
6392 }
6393 }
6394 },
6395
6396 getPoint: function(event) {
6397 var pos = event.targetTouches
6398 ? event.targetTouches.length
6399 ? event.targetTouches[0]
6400 : event.changedTouches[0]
6401 : event;
6402 return Point.create(
6403 pos.pageX || pos.clientX + document.documentElement.scrollLeft,
6404 pos.pageY || pos.clientY + document.documentElement.scrollTop
6405 );
6406 },
6407
6408 getTarget: function(event) {
6409 return event.target || event.srcElement;
6410 },
6411
6412 getOffset: function(event, target) {
6413 return DomEvent.getPoint(event).subtract(DomElement.getOffset(
6414 target || DomEvent.getTarget(event)));
6415 },
6416
6417 preventDefault: function(event) {
6418 if (event.preventDefault) {
6419 event.preventDefault();
6420 } else {
6421 event.returnValue = false;
6422 }
6423 },
6424
6425 stopPropagation: function(event) {
6426 if (event.stopPropagation) {
6427 event.stopPropagation();
6428 } else {
6429 event.cancelBubble = true;
6430 }
6431 },
6432
6433 stop: function(event) {
6434 DomEvent.stopPropagation(event);
6435 DomEvent.preventDefault(event);
6436 }
6437};
6438
6439DomEvent.requestAnimationFrame = new function() {
6440 var part = 'equestAnimationFrame',
6441 request = window['r' + part] || window['webkitR' + part]
6442 || window['mozR' + part] || window['oR' + part]
6443 || window['msR' + part];
6444 if (request) {
6445 request(function(time) {
6446 if (time == undefined)
6447 request = null;
6448 });
6449 }
6450
6451 var callbacks = [],
6452 focused = true,
6453 timer;
6454
6455 DomEvent.add(window, {
6456 focus: function() {
6457 focused = true;
6458 },
6459 blur: function() {
6460 focused = false;
6461 }
6462 });
6463
6464 return function(callback, element) {
6465 if (request)
6466 return request(callback, element);
6467 callbacks.push([callback, element]);
6468 if (timer)
6469 return;
6470 timer = window.setInterval(function() {
6471 for (var i = callbacks.length - 1; i >= 0; i--) {
6472 var entry = callbacks[i],
6473 func = entry[0],
6474 el = entry[1];
6475 if (!el || (PaperScript.getAttribute(el, 'keepalive') == 'true'
6476 || focused) && DomElement.isVisible(el)) {
6477 callbacks.splice(i, 1);
6478 func(Date.now());
6479 }
6480 }
6481 }, 1000 / 60);
6482 };
6483};
6484
6485var View = this.View = PaperScopeItem.extend({
6486 _list: 'views',
6487 _reference: 'view',
6488
6489 initialize: function(canvas) {
6490 this.base();
6491 var size;
6492 if (typeof canvas === 'string')
6493 canvas = document.getElementById(canvas);
6494 if (canvas instanceof HTMLCanvasElement) {
6495 this._canvas = canvas;
6496 if (PaperScript.hasAttribute(canvas, 'resize')) {
6497 var offset = DomElement.getOffset(canvas, true),
6498 that = this;
6499 size = DomElement.getViewportBounds(canvas)
6500 .getSize().subtract(offset);
6501 canvas.width = size.width;
6502 canvas.height = size.height;
6503 DomEvent.add(window, {
6504 resize: function(event) {
6505 if (!DomElement.isInvisible(canvas))
6506 offset = DomElement.getOffset(canvas, true);
6507 that.setViewSize(DomElement.getViewportBounds(canvas)
6508 .getSize().subtract(offset));
6509 }
6510 });
6511 } else {
6512 size = DomElement.isInvisible(canvas)
6513 ? Size.create(parseInt(canvas.getAttribute('width')),
6514 parseInt(canvas.getAttribute('height')))
6515 : DomElement.getSize(canvas);
6516 }
6517 if (PaperScript.hasAttribute(canvas, 'stats')) {
6518 this._stats = new Stats();
6519 var element = this._stats.domElement,
6520 style = element.style,
6521 offset = DomElement.getOffset(canvas);
6522 style.position = 'absolute';
6523 style.left = offset.x + 'px';
6524 style.top = offset.y + 'px';
6525 document.body.appendChild(element);
6526 }
6527 } else {
6528 size = Size.read(arguments, 1);
6529 if (size.isZero())
6530 size = new Size(1024, 768);
6531 this._canvas = CanvasProvider.getCanvas(size);
6532 }
6533 this._id = this._canvas.getAttribute('id');
6534 if (this._id == null)
6535 this._canvas.setAttribute('id', this._id = 'canvas-' + View._id++);
6536 View._views[this._id] = this;
6537 this._viewSize = LinkedSize.create(this, 'setViewSize',
6538 size.width, size.height);
6539 this._context = this._canvas.getContext('2d');
6540 this._matrix = new Matrix();
6541 this._zoom = 1;
6542 this._events = this._createEvents();
6543 DomEvent.add(this._canvas, this._events);
6544 if (!View._focused)
6545 View._focused = this;
6546 this._scope._redrawNotified = false;
6547 },
6548
6549 remove: function() {
6550 if (!this.base())
6551 return false;
6552 if (View._focused == this)
6553 View._focused = null;
6554 delete View._views[this._id];
6555 DomEvent.remove(this._canvas, this._events);
6556 this._canvas = this._events = this._onFrame = null;
6557 return true;
6558 },
6559
6560 getCanvas: function() {
6561 return this._canvas;
6562 },
6563
6564 getViewSize: function() {
6565 return this._viewSize;
6566 },
6567
6568 setViewSize: function(size) {
6569 size = Size.read(arguments);
6570 var delta = size.subtract(this._viewSize);
6571 if (delta.isZero())
6572 return;
6573 this._canvas.width = size.width;
6574 this._canvas.height = size.height;
6575 this._viewSize.set(size.width, size.height, true);
6576 this._bounds = null;
6577 this._redrawNeeded = true;
6578 if (this.onResize) {
6579 this.onResize({
6580 size: size,
6581 delta: delta
6582 });
6583 }
6584 if (this._onFrameCallback) {
6585 this._onFrameCallback(0, true);
6586 } else {
6587 this.draw(true);
6588 }
6589 },
6590
6591 getBounds: function() {
6592 if (!this._bounds)
6593 this._bounds = this._matrix._transformBounds(
6594 new Rectangle(new Point(), this._viewSize));
6595 return this._bounds;
6596 },
6597
6598 getSize: function() {
6599 return this.getBounds().getSize();
6600 },
6601
6602 getCenter: function() {
6603 return this.getBounds().getCenter();
6604 },
6605
6606 setCenter: function(center) {
6607 this.scrollBy(Point.read(arguments).subtract(this.getCenter()));
6608 },
6609
6610 getZoom: function() {
6611 return this._zoom;
6612 },
6613
6614 setZoom: function(zoom) {
6615 this._transform(new Matrix().scale(zoom / this._zoom, this.getCenter()));
6616 this._zoom = zoom;
6617 },
6618
6619 isVisible: function() {
6620 return DomElement.isVisible(this._canvas);
6621 },
6622
6623 scrollBy: function(point) {
6624 this._transform(new Matrix().translate(Point.read(arguments).negate()));
6625 },
6626
6627 _transform: function(matrix, flags) {
6628 this._matrix.preConcatenate(matrix);
6629 this._bounds = null;
6630 this._inverse = null;
6631 },
6632
6633 draw: function(checkRedraw) {
6634 if (checkRedraw && !this._redrawNeeded)
6635 return false;
6636 if (this._stats)
6637 this._stats.update();
6638 var ctx = this._context,
6639 size = this._viewSize;
6640 ctx.clearRect(0, 0, size._width + 1, size._height + 1);
6641
6642 ctx.save();
6643 this._matrix.applyToContext(ctx);
6644 this._scope.project.draw(ctx);
6645 ctx.restore();
6646 if (this._redrawNeeded) {
6647 this._redrawNeeded = false;
6648 this._scope._redrawNotified = false;
6649 }
6650 return true;
6651 },
6652
6653 projectToView: function(point) {
6654 return this._matrix._transformPoint(Point.read(arguments));
6655 },
6656
6657 viewToProject: function(point) {
6658 return this._getInverse()._transformPoint(Point.read(arguments));
6659 },
6660
6661 _getInverse: function() {
6662 if (!this._inverse)
6663 this._inverse = this._matrix.createInverse();
6664 return this._inverse;
6665 },
6666
6667 getOnFrame: function() {
6668 return this._onFrame;
6669 },
6670
6671 setOnFrame: function(onFrame) {
6672 this._onFrame = onFrame;
6673 if (!onFrame) {
6674 delete this._onFrameCallback;
6675 return;
6676 }
6677 var that = this,
6678 requested = false,
6679 before,
6680 time = 0,
6681 count = 0;
6682 this._onFrameCallback = function(param, dontRequest) {
6683 requested = false;
6684 if (!that._onFrame)
6685 return;
6686 paper = that._scope;
6687 requested = true;
6688 if (!dontRequest) {
6689 DomEvent.requestAnimationFrame(that._onFrameCallback,
6690 that._canvas);
6691 }
6692 var now = Date.now() / 1000,
6693 delta = before ? now - before : 0;
6694 that._onFrame(Base.merge({
6695 delta: delta,
6696 time: time += delta,
6697 count: count++
6698 }));
6699 before = now;
6700 that.draw(true);
6701 };
6702 if (!requested)
6703 this._onFrameCallback();
6704 },
6705
6706 onResize: null
6707}, new function() {
6708 var tool,
6709 timer,
6710 curPoint,
6711 tempFocus,
6712 dragging = false;
6713
6714 function viewToProject(view, event) {
6715 return view.viewToProject(DomEvent.getOffset(event, view._canvas));
6716 }
6717
6718 function updateFocus() {
6719 if (!View._focused || !View._focused.isVisible()) {
6720 PaperScope.each(function(scope) {
6721 for (var i = 0, l = scope.views.length; i < l; i++) {
6722 var view = scope.views[i];
6723 if (view.isVisible()) {
6724 View._focused = tempFocus = view;
6725 throw Base.stop;
6726 }
6727 }
6728 });
6729 }
6730 }
6731
6732 function mousemove(event) {
6733 var view;
6734 if (!dragging) {
6735 view = View._views[DomEvent.getTarget(event).getAttribute('id')];
6736 if (view) {
6737 View._focused = tempFocus = view;
6738 } else if (tempFocus && tempFocus == View._focused) {
6739 View._focused = null;
6740 updateFocus();
6741 }
6742 }
6743 if (!(view = view || View._focused) || !(tool = view._scope.tool))
6744 return;
6745 var point = event && viewToProject(view, event);
6746 var onlyMove = !!(!tool.onMouseDrag && tool.onMouseMove);
6747 if (dragging && !onlyMove) {
6748 curPoint = point || curPoint;
6749 if (curPoint && tool.onHandleEvent('mousedrag', curPoint, event)) {
6750 view.draw(true);
6751 DomEvent.stop(event);
6752 }
6753 } else if ((!dragging || onlyMove)
6754 && tool.onHandleEvent('mousemove', point, event)) {
6755 view.draw(true);
6756 DomEvent.stop(event);
6757 }
6758 }
6759
6760 function mouseup(event) {
6761 var view = View._focused;
6762 if (!view || !dragging)
6763 return;
6764 dragging = false;
6765 curPoint = null;
6766 if (tool) {
6767 if (timer != null)
6768 timer = clearInterval(timer);
6769 if (tool.onHandleEvent('mouseup', viewToProject(view, event), event)) {
6770 view.draw(true);
6771 DomEvent.stop(event);
6772 }
6773 }
6774 }
6775
6776 function selectstart(event) {
6777 if (dragging)
6778 DomEvent.stop(event);
6779 }
6780
6781 DomEvent.add(document, {
6782 mousemove: mousemove,
6783 mouseup: mouseup,
6784 touchmove: mousemove,
6785 touchend: mouseup,
6786 selectstart: selectstart,
6787 scroll: updateFocus
6788 });
6789
6790 DomEvent.add(window, {
6791 load: updateFocus
6792 });
6793
6794 return {
6795 _createEvents: function() {
6796 var view = this;
6797
6798 function mousedown(event) {
6799 View._focused = view;
6800 if (!(tool = view._scope.tool))
6801 return;
6802 curPoint = viewToProject(view, event);
6803 if (tool.onHandleEvent('mousedown', curPoint, event))
6804 view.draw(true);
6805 if (tool.eventInterval != null)
6806 timer = setInterval(mousemove, tool.eventInterval);
6807 dragging = true;
6808 }
6809
6810 return {
6811 mousedown: mousedown,
6812 touchstart: mousedown,
6813 selectstart: selectstart
6814 };
6815 },
6816
6817 statics: {
6818 _views: {},
6819 _id: 0,
6820
6821 updateFocus: updateFocus
6822 }
6823 };
6824});
6825
6826var Event = this.Event = Base.extend({
6827 initialize: function(event) {
6828 this.event = event;
6829 },
6830
6831 preventDefault: function() {
6832 DomEvent.preventDefault(this.event);
6833 },
6834
6835 stopPropagation: function() {
6836 DomEvent.stopPropagation(this.event);
6837 },
6838
6839 stop: function() {
6840 DomEvent.stop(this.event);
6841 },
6842
6843 getModifiers: function() {
6844 return Key.modifiers;
6845 }
6846});
6847
6848var KeyEvent = this.KeyEvent = Event.extend(new function() {
6849 return {
6850 initialize: function(down, key, character, event) {
6851 this.base(event);
6852 this.type = down ? 'keydown' : 'keyup';
6853 this.key = key;
6854 this.character = character;
6855 },
6856
6857 toString: function() {
6858 return '{ type: ' + this.type
6859 + ', key: ' + this.key
6860 + ', character: ' + this.character
6861 + ', modifiers: ' + this.getModifiers()
6862 + ' }';
6863 }
6864 };
6865});
6866
6867var Key = this.Key = new function() {
6868
6869 var keys = {
6870 8: 'backspace',
6871 13: 'enter',
6872 16: 'shift',
6873 17: 'control',
6874 18: 'option',
6875 19: 'pause',
6876 20: 'caps-lock',
6877 27: 'escape',
6878 32: 'space',
6879 35: 'end',
6880 36: 'home',
6881 37: 'left',
6882 38: 'up',
6883 39: 'right',
6884 40: 'down',
6885 46: 'delete',
6886 91: 'command'
6887 },
6888
6889 modifiers = Base.merge({
6890 shift: false,
6891 control: false,
6892 option: false,
6893 command: false,
6894 capsLock: false
6895 }),
6896
6897 charCodeMap = {},
6898 keyMap = {},
6899 downCode;
6900
6901 function handleKey(down, keyCode, charCode, event) {
6902 var character = String.fromCharCode(charCode),
6903 key = keys[keyCode] || character.toLowerCase(),
6904 handler = down ? 'onKeyDown' : 'onKeyUp',
6905 view = View._focused,
6906 scope = view && view.isVisible() && view._scope,
6907 tool = scope && scope.tool;
6908 keyMap[key] = down;
6909 if (tool && tool[handler]) {
6910 var keyEvent = new KeyEvent(down, key, character, event);
6911 if (tool[handler](keyEvent) === false)
6912 keyEvent.preventDefault();
6913 if (view)
6914 view.draw(true);
6915 }
6916 }
6917
6918 DomEvent.add(document, {
6919 keydown: function(event) {
6920 var code = event.which || event.keyCode;
6921 var key = keys[code], name;
6922 if (key) {
6923 if (modifiers[name = Base.camelize(key)] !== undefined) {
6924 modifiers[name] = true;
6925 } else {
6926 charCodeMap[code] = 0;
6927 handleKey(true, code, null, event);
6928 }
6929 } else {
6930 downCode = code;
6931 }
6932 },
6933
6934 keypress: function(event) {
6935 if (downCode != null) {
6936 var code = event.which || event.keyCode;
6937 charCodeMap[downCode] = code;
6938 handleKey(true, downCode, code, event);
6939 downCode = null;
6940 }
6941 },
6942
6943 keyup: function(event) {
6944 var code = event.which || event.keyCode,
6945 key = keys[code], name;
6946 if (key && modifiers[name = Base.camelize(key)] !== undefined) {
6947 modifiers[name] = false;
6948 } else if (charCodeMap[code] != null) {
6949 handleKey(false, code, charCodeMap[code], event);
6950 delete charCodeMap[code];
6951 }
6952 }
6953 });
6954
6955 return {
6956 modifiers: modifiers,
6957
6958 isDown: function(key) {
6959 return !!keyMap[key];
6960 }
6961 };
6962};
6963
6964var ToolEvent = this.ToolEvent = Event.extend({
6965 initialize: function(tool, type, event) {
6966 this.tool = tool;
6967 this.type = type;
6968 this.event = event;
6969 },
6970
6971 _choosePoint: function(point, toolPoint) {
6972 return point ? point : toolPoint ? toolPoint.clone() : null;
6973 },
6974
6975 getPoint: function() {
6976 return this._choosePoint(this._point, this.tool._point);
6977 },
6978
6979 setPoint: function(point) {
6980 this._point = point;
6981 },
6982
6983 getLastPoint: function() {
6984 return this._choosePoint(this._lastPoint, this.tool._lastPoint);
6985 },
6986
6987 setLastPoint: function(lastPoint) {
6988 this._lastPoint = lastPoint;
6989 },
6990
6991 getDownPoint: function() {
6992 return this._choosePoint(this._downPoint, this.tool._downPoint);
6993 },
6994
6995 setDownPoint: function(downPoint) {
6996 this._downPoint = downPoint;
6997 },
6998
6999 getMiddlePoint: function() {
7000 if (!this._middlePoint && this.tool._lastPoint) {
7001 return this.tool._point.add(this.tool._lastPoint).divide(2);
7002 }
7003 return this.middlePoint;
7004 },
7005
7006 setMiddlePoint: function(middlePoint) {
7007 this._middlePoint = middlePoint;
7008 },
7009
7010 getDelta: function() {
7011 return !this._delta && this.tool._lastPoint
7012 ? this.tool._point.subtract(this.tool._lastPoint)
7013 : this._delta;
7014 },
7015
7016 setDelta: function(delta) {
7017 this._delta = delta;
7018 },
7019
7020 getCount: function() {
7021 return /^mouse(down|up)$/.test(this.type)
7022 ? this.tool._downCount
7023 : this.tool._count;
7024 },
7025
7026 setCount: function(count) {
7027 this.tool[/^mouse(down|up)$/.test(this.type) ? 'downCount' : 'count']
7028 = count;
7029 },
7030
7031 getItem: function() {
7032 if (!this._item) {
7033 var result = this.tool._scope.project.hitTest(this.getPoint());
7034 if (result) {
7035 var item = result.item,
7036 parent = item._parent;
7037 while ((parent instanceof Group && !(parent instanceof Layer))
7038 || parent instanceof CompoundPath) {
7039 item = parent;
7040 parent = parent._parent;
7041 }
7042 this._item = item;
7043 }
7044 }
7045 return this._item;
7046 },
7047 setItem: function(item) {
7048 this._item = item;
7049 },
7050
7051 toString: function() {
7052 return '{ type: ' + this.type
7053 + ', point: ' + this.getPoint()
7054 + ', count: ' + this.getCount()
7055 + ', modifiers: ' + this.getModifiers()
7056 + ' }';
7057 }
7058});
7059
7060var Tool = this.Tool = PaperScopeItem.extend({
7061 _list: 'tools',
7062 _reference: 'tool',
7063
7064 initialize: function() {
7065 this.base();
7066 this._firstMove = true;
7067 this._count = 0;
7068 this._downCount = 0;
7069 },
7070
7071 eventInterval: null,
7072
7073 getMinDistance: function() {
7074 return this._minDistance;
7075 },
7076
7077 setMinDistance: function(minDistance) {
7078 this._minDistance = minDistance;
7079 if (this._minDistance != null && this._maxDistance != null
7080 && this._minDistance > this._maxDistance) {
7081 this._maxDistance = this._minDistance;
7082 }
7083 },
7084
7085 getMaxDistance: function() {
7086 return this._maxDistance;
7087 },
7088
7089 setMaxDistance: function(maxDistance) {
7090 this._maxDistance = maxDistance;
7091 if (this._minDistance != null && this._maxDistance != null
7092 && this._maxDistance < this._minDistance) {
7093 this._minDistance = maxDistance;
7094 }
7095 },
7096
7097 getFixedDistance: function() {
7098 return this._minDistance == this._maxDistance
7099 ? this._minDistance : null;
7100 },
7101
7102 setFixedDistance: function(distance) {
7103 this._minDistance = distance;
7104 this._maxDistance = distance;
7105 },
7106
7107 updateEvent: function(type, pt, minDistance, maxDistance, start,
7108 needsChange, matchMaxDistance) {
7109 if (!start) {
7110 if (minDistance != null || maxDistance != null) {
7111 var minDist = minDistance != null ? minDistance : 0;
7112 var vector = pt.subtract(this._point);
7113 var distance = vector.getLength();
7114 if (distance < minDist)
7115 return false;
7116 var maxDist = maxDistance != null ? maxDistance : 0;
7117 if (maxDist != 0) {
7118 if (distance > maxDist) {
7119 pt = this._point.add(vector.normalize(maxDist));
7120 } else if (matchMaxDistance) {
7121 return false;
7122 }
7123 }
7124 }
7125 if (needsChange && pt.equals(this._point))
7126 return false;
7127 }
7128 this._lastPoint = start && type == 'mousemove' ? pt : this._point;
7129 this._point = pt;
7130 switch (type) {
7131 case 'mousedown':
7132 this._lastPoint = this._downPoint;
7133 this._downPoint = this._point;
7134 this._downCount++;
7135 break;
7136 case 'mouseup':
7137 this._lastPoint = this._downPoint;
7138 break;
7139 }
7140 this._count = start ? 0 : this._count + 1;
7141 return true;
7142 },
7143
7144 onHandleEvent: function(type, pt, event) {
7145 paper = this._scope;
7146 var called = false;
7147 switch (type) {
7148 case 'mousedown':
7149 this.updateEvent(type, pt, null, null, true, false, false);
7150 if (this.onMouseDown) {
7151 this.onMouseDown(new ToolEvent(this, type, event));
7152 called = true;
7153 }
7154 break;
7155 case 'mousedrag':
7156 var needsChange = false,
7157 matchMaxDistance = false;
7158 while (this.updateEvent(type, pt, this.minDistance,
7159 this.maxDistance, false, needsChange, matchMaxDistance)) {
7160 if (this.onMouseDrag) {
7161 this.onMouseDrag(new ToolEvent(this, type, event));
7162 called = true;
7163 }
7164 needsChange = true;
7165 matchMaxDistance = true;
7166 }
7167 break;
7168 case 'mouseup':
7169 if ((this._point.x != pt.x || this._point.y != pt.y)
7170 && this.updateEvent('mousedrag', pt, this.minDistance,
7171 this.maxDistance, false, false, false)) {
7172 if (this.onMouseDrag) {
7173 this.onMouseDrag(new ToolEvent(this, type, event));
7174 called = true;
7175 }
7176 }
7177 this.updateEvent(type, pt, null, this.maxDistance, false,
7178 false, false);
7179 if (this.onMouseUp) {
7180 this.onMouseUp(new ToolEvent(this, type, event));
7181 called = true;
7182 }
7183 this.updateEvent(type, pt, null, null, true, false, false);
7184 this._firstMove = true;
7185 break;
7186 case 'mousemove':
7187 while (this.updateEvent(type, pt, this.minDistance,
7188 this.maxDistance, this._firstMove, true, false)) {
7189 if (this.onMouseMove) {
7190 this.onMouseMove(new ToolEvent(this, type, event));
7191 called = true;
7192 }
7193 this._firstMove = false;
7194 }
7195 break;
7196 }
7197 return called;
7198 }
7199});
7200
7201var CanvasProvider = {
7202 canvases: [],
7203 getCanvas: function(size) {
7204 if (this.canvases.length) {
7205 var canvas = this.canvases.pop();
7206 if ((canvas.width != size.width)
7207 || (canvas.height != size.height)) {
7208 canvas.width = size.width;
7209 canvas.height = size.height;
7210 } else {
7211 canvas.getContext('2d').clearRect(0, 0,
7212 size.width + 1, size.height + 1);
7213 }
7214 return canvas;
7215 } else {
7216 var canvas = document.createElement('canvas');
7217 canvas.width = size.width;
7218 canvas.height = size.height;
7219 return canvas;
7220 }
7221 },
7222
7223 returnCanvas: function(canvas) {
7224 this.canvases.push(canvas);
7225 }
7226};
7227
7228var Numerical = new function() {
7229
7230 var abscissas = [
7231 [ 0.5773502691896257645091488],
7232 [0,0.7745966692414833770358531],
7233 [ 0.3399810435848562648026658,0.8611363115940525752239465],
7234 [0,0.5384693101056830910363144,0.9061798459386639927976269],
7235 [ 0.2386191860831969086305017,0.6612093864662645136613996,0.9324695142031520278123016],
7236 [0,0.4058451513773971669066064,0.7415311855993944398638648,0.9491079123427585245261897],
7237 [ 0.1834346424956498049394761,0.5255324099163289858177390,0.7966664774136267395915539,0.9602898564975362316835609],
7238 [0,0.3242534234038089290385380,0.6133714327005903973087020,0.8360311073266357942994298,0.9681602395076260898355762],
7239 [ 0.1488743389816312108848260,0.4333953941292471907992659,0.6794095682990244062343274,0.8650633666889845107320967,0.9739065285171717200779640],
7240 [0,0.2695431559523449723315320,0.5190961292068118159257257,0.7301520055740493240934163,0.8870625997680952990751578,0.9782286581460569928039380],
7241 [ 0.1252334085114689154724414,0.3678314989981801937526915,0.5873179542866174472967024,0.7699026741943046870368938,0.9041172563704748566784659,0.9815606342467192506905491],
7242 [0,0.2304583159551347940655281,0.4484927510364468528779129,0.6423493394403402206439846,0.8015780907333099127942065,0.9175983992229779652065478,0.9841830547185881494728294],
7243 [ 0.1080549487073436620662447,0.3191123689278897604356718,0.5152486363581540919652907,0.6872929048116854701480198,0.8272013150697649931897947,0.9284348836635735173363911,0.9862838086968123388415973],
7244 [0,0.2011940939974345223006283,0.3941513470775633698972074,0.5709721726085388475372267,0.7244177313601700474161861,0.8482065834104272162006483,0.9372733924007059043077589,0.9879925180204854284895657],
7245 [ 0.0950125098376374401853193,0.2816035507792589132304605,0.4580167776572273863424194,0.6178762444026437484466718,0.7554044083550030338951012,0.8656312023878317438804679,0.9445750230732325760779884,0.9894009349916499325961542]
7246 ];
7247
7248 var weights = [
7249 [1],
7250 [0.8888888888888888888888889,0.5555555555555555555555556],
7251 [0.6521451548625461426269361,0.3478548451374538573730639],
7252 [0.5688888888888888888888889,0.4786286704993664680412915,0.2369268850561890875142640],
7253 [0.4679139345726910473898703,0.3607615730481386075698335,0.1713244923791703450402961],
7254 [0.4179591836734693877551020,0.3818300505051189449503698,0.2797053914892766679014678,0.1294849661688696932706114],
7255 [0.3626837833783619829651504,0.3137066458778872873379622,0.2223810344533744705443560,0.1012285362903762591525314],
7256 [0.3302393550012597631645251,0.3123470770400028400686304,0.2606106964029354623187429,0.1806481606948574040584720,0.0812743883615744119718922],
7257 [0.2955242247147528701738930,0.2692667193099963550912269,0.2190863625159820439955349,0.1494513491505805931457763,0.0666713443086881375935688],
7258 [0.2729250867779006307144835,0.2628045445102466621806889,0.2331937645919904799185237,0.1862902109277342514260976,0.1255803694649046246346943,0.0556685671161736664827537],
7259 [0.2491470458134027850005624,0.2334925365383548087608499,0.2031674267230659217490645,0.1600783285433462263346525,0.1069393259953184309602547,0.0471753363865118271946160],
7260 [0.2325515532308739101945895,0.2262831802628972384120902,0.2078160475368885023125232,0.1781459807619457382800467,0.1388735102197872384636018,0.0921214998377284479144218,0.0404840047653158795200216],
7261 [0.2152638534631577901958764,0.2051984637212956039659241,0.1855383974779378137417166,0.1572031671581935345696019,0.1215185706879031846894148,0.0801580871597602098056333,0.0351194603317518630318329],
7262 [0.2025782419255612728806202,0.1984314853271115764561183,0.1861610000155622110268006,0.1662692058169939335532009,0.1395706779261543144478048,0.1071592204671719350118695,0.0703660474881081247092674,0.0307532419961172683546284],
7263 [0.1894506104550684962853967,0.1826034150449235888667637,0.1691565193950025381893121,0.1495959888165767320815017,0.1246289712555338720524763,0.0951585116824927848099251,0.0622535239386478928628438,0.0271524594117540948517806]
7264 ];
7265
7266 var abs = Math.abs,
7267 sqrt = Math.sqrt,
7268 cos = Math.cos,
7269 PI = Math.PI;
7270
7271 return {
7272 TOLERANCE: 10e-6,
7273 EPSILON: 10e-12,
7274
7275 integrate: function(f, a, b, n) {
7276 var x = abscissas[n - 2],
7277 w = weights[n - 2],
7278 A = 0.5 * (b - a),
7279 B = A + a,
7280 i = 0,
7281 m = (n + 1) >> 1,
7282 sum = n & 1 ? w[i++] * f(B) : 0;
7283 while (i < m) {
7284 var Ax = A * x[i];
7285 sum += w[i++] * (f(B + Ax) + f(B - Ax));
7286 }
7287 return A * sum;
7288 },
7289
7290 findRoot: function(f, df, x, a, b, n, tolerance) {
7291 for (var i = 0; i < n; i++) {
7292 var fx = f(x),
7293 dx = fx / df(x);
7294 if (abs(dx) < tolerance)
7295 return x;
7296 var nx = x - dx;
7297 if (fx > 0) {
7298 b = x;
7299 x = nx <= a ? 0.5 * (a + b) : nx;
7300 } else {
7301 a = x;
7302 x = nx >= b ? 0.5 * (a + b) : nx;
7303 }
7304 }
7305 },
7306
7307 solveQuadratic: function(a, b, c, roots, tolerance) {
7308 if (abs(a) < tolerance) {
7309 if (abs(b) >= tolerance) {
7310 roots[0] = -c / b;
7311 return 1;
7312 }
7313 if (abs(c) < tolerance)
7314 return -1;
7315 return 0;
7316 }
7317 var q = b * b - 4 * a * c;
7318 if (q < 0)
7319 return 0;
7320 q = sqrt(q);
7321 if (b < 0)
7322 q = -q;
7323 q = (b + q) * -0.5;
7324 var n = 0;
7325 if (abs(q) >= tolerance)
7326 roots[n++] = c / q;
7327 if (abs(a) >= tolerance)
7328 roots[n++] = q / a;
7329 return n;
7330 },
7331
7332 solveCubic: function(a, b, c, d, roots, tolerance) {
7333 if (abs(a) < tolerance)
7334 return Numerical.solveQuadratic(b, c, d, roots, tolerance);
7335 b /= a;
7336 c /= a;
7337 d /= a;
7338 var Q = (b * b - 3 * c) / 9,
7339 R = (2 * b * b * b - 9 * b * c + 27 * d) / 54,
7340 Q3 = Q * Q * Q,
7341 R2 = R * R;
7342 b /= 3;
7343 if (R2 < Q3) {
7344 var theta = Math.acos(R / sqrt(Q3)),
7345 q = -2 * sqrt(Q);
7346 roots[0] = q * cos(theta / 3) - b;
7347 roots[1] = q * cos((theta + 2 * PI) / 3) - b;
7348 roots[2] = q * cos((theta - 2 * PI) / 3) - b;
7349 return 3;
7350 } else {
7351 var A = -Math.pow(abs(R) + sqrt(R2 - Q3), 1 / 3);
7352 if (R < 0) A = -A;
7353 var B = (abs(A) < tolerance) ? 0 : Q / A;
7354 roots[0] = (A + B) - b;
7355 return 1;
7356 }
7357 return 0;
7358 }
7359 };
7360};
7361
7362var BlendMode = {
7363 process: function(blendMode, srcContext, dstContext, alpha, offset) {
7364 var srcCanvas = srcContext.canvas,
7365 dstData = dstContext.getImageData(offset.x, offset.y,
7366 srcCanvas.width, srcCanvas.height),
7367 dst = dstData.data,
7368 src = srcContext.getImageData(0, 0,
7369 srcCanvas.width, srcCanvas.height).data,
7370 min = Math.min,
7371 max = Math.max,
7372 abs = Math.abs,
7373 sr, sg, sb, sa,
7374 br, bg, bb, ba,
7375 dr, dg, db;
7376
7377 function getLum(r, g, b) {
7378 return 0.2989 * r + 0.587 * g + 0.114 * b;
7379 }
7380
7381 function setLum(r, g, b, l) {
7382 var d = l - getLum(r, g, b);
7383 dr = r + d;
7384 dg = g + d;
7385 db = b + d;
7386 var l = getLum(dr, dg, db),
7387 mn = min(dr, dg, db),
7388 mx = max(dr, dg, db);
7389 if (mn < 0) {
7390 var lmn = l - mn;
7391 dr = l + (dr - l) * l / lmn;
7392 dg = l + (dg - l) * l / lmn;
7393 db = l + (db - l) * l / lmn;
7394 }
7395 if (mx > 255) {
7396 var ln = 255 - l, mxl = mx - l;
7397 dr = l + (dr - l) * ln / mxl;
7398 dg = l + (dg - l) * ln / mxl;
7399 db = l + (db - l) * ln / mxl;
7400 }
7401 }
7402
7403 function getSat(r, g, b) {
7404 return max(r, g, b) - min(r, g, b);
7405 }
7406
7407 function setSat(r, g, b, s) {
7408 var col = [r, g, b],
7409 mx = max(r, g, b),
7410 mn = min(r, g, b),
7411 md;
7412 mn = mn == r ? 0 : mn == g ? 1 : 2;
7413 mx = mx == r ? 0 : mx == g ? 1 : 2;
7414 md = min(mn, mx) == 0 ? max(mn, mx) == 1 ? 2 : 1 : 0;
7415 if (col[mx] > col[mn]) {
7416 col[md] = (col[md] - col[mn]) * s / (col[mx] - col[mn]);
7417 col[mx] = s;
7418 } else {
7419 col[md] = col[mx] = 0;
7420 }
7421 col[mn] = 0;
7422 dr = col[0];
7423 dg = col[1];
7424 db = col[2];
7425 }
7426
7427 var modes = {
7428 multiply: function() {
7429 dr = br * sr / 255;
7430 dg = bg * sg / 255;
7431 db = bb * sb / 255;
7432 },
7433
7434 screen: function() {
7435 dr = 255 - (255 - br) * (255 - sr) / 255;
7436 dg = 255 - (255 - bg) * (255 - sg) / 255;
7437 db = 255 - (255 - bb) * (255 - sb) / 255;
7438 },
7439
7440 overlay: function() {
7441 dr = br < 128 ? 2 * br * sr / 255 : 255 - 2 * (255 - br) * (255 - sr) / 255;
7442 dg = bg < 128 ? 2 * bg * sg / 255 : 255 - 2 * (255 - bg) * (255 - sg) / 255;
7443 db = bb < 128 ? 2 * bb * sb / 255 : 255 - 2 * (255 - bb) * (255 - sb) / 255;
7444 },
7445
7446 'soft-light': function() {
7447 var t = sr * br / 255;
7448 dr = t + br * (255 - (255 - br) * (255 - sr) / 255 - t) / 255;
7449 t = sg * bg / 255;
7450 dg = t + bg * (255 - (255 - bg) * (255 - sg) / 255 - t) / 255;
7451 t = sb * bb / 255;
7452 db = t + bb * (255 - (255 - bb) * (255 - sb) / 255 - t) / 255;
7453 },
7454
7455 'hard-light': function() {
7456 dr = sr < 128 ? 2 * sr * br / 255 : 255 - 2 * (255 - sr) * (255 - br) / 255;
7457 dg = sg < 128 ? 2 * sg * bg / 255 : 255 - 2 * (255 - sg) * (255 - bg) / 255;
7458 db = sb < 128 ? 2 * sb * bb / 255 : 255 - 2 * (255 - sb) * (255 - bb) / 255;
7459 },
7460
7461 'color-dodge': function() {
7462 dr = sr == 255 ? sr : min(255, br * 255 / (255 - sr));
7463 dg = sg == 255 ? sg : min(255, bg * 255 / (255 - sg));
7464 db = sb == 255 ? sb : min(255, bb * 255 / (255 - sb));
7465 },
7466
7467 'color-burn': function() {
7468 dr = sr == 0 ? 0 : max(255 - ((255 - br) * 255) / sr, 0);
7469 dg = sg == 0 ? 0 : max(255 - ((255 - bg) * 255) / sg, 0);
7470 db = sb == 0 ? 0 : max(255 - ((255 - bb) * 255) / sb, 0);
7471 },
7472
7473 darken: function() {
7474 dr = br < sr ? br : sr;
7475 dg = bg < sg ? bg : sg;
7476 db = bb < sb ? bb : sb;
7477 },
7478
7479 lighten: function() {
7480 dr = br > sr ? br : sr;
7481 dg = bg > sg ? bg : sg;
7482 db = bb > sb ? bb : sb;
7483 },
7484
7485 difference: function() {
7486 dr = br - sr;
7487 if (dr < 0)
7488 dr = -dr;
7489 dg = bg - sg;
7490 if (dg < 0)
7491 dg = -dg;
7492 db = bb - sb;
7493 if (db < 0)
7494 db = -db;
7495 },
7496
7497 exclusion: function() {
7498 dr = br + sr * (255 - br - br) / 255;
7499 dg = bg + sg * (255 - bg - bg) / 255;
7500 db = bb + sb * (255 - bb - bb) / 255;
7501 },
7502
7503 hue: function() {
7504 setSat(sr, sg, sb, getSat(br, bg, bb));
7505 setLum(dr, dg, db, getLum(br, bg, bb));
7506 },
7507
7508 saturation: function() {
7509 setSat(br, bg, bb, getSat(sr, sg, sb));
7510 setLum(dr, dg, db, getLum(br, bg, bb));
7511 },
7512
7513 luminosity: function() {
7514 setLum(br, bg, bb, getLum(sr, sg, sb));
7515 },
7516
7517 color: function() {
7518 setLum(sr, sg, sb, getLum(br, bg, bb));
7519 },
7520
7521 add: function() {
7522 dr = min(br + sr, 255);
7523 dg = min(bg + sg, 255);
7524 db = min(bb + sb, 255);
7525 },
7526
7527 subtract: function() {
7528 dr = max(br - sr, 0);
7529 dg = max(bg - sg, 0);
7530 db = max(bb - sb, 0);
7531 },
7532
7533 average: function() {
7534 dr = (br + sr) / 2;
7535 dg = (bg + sg) / 2;
7536 db = (bb + sb) / 2;
7537 },
7538
7539 negation: function() {
7540 dr = 255 - abs(255 - sr - br);
7541 dg = 255 - abs(255 - sg - bg);
7542 db = 255 - abs(255 - sb - bb);
7543 }
7544 };
7545
7546 var process = modes[blendMode];
7547 if (!process)
7548 return;
7549
7550 for (var i = 0, l = dst.length; i < l; i += 4) {
7551 sr = src[i];
7552 br = dst[i];
7553 sg = src[i + 1];
7554 bg = dst[i + 1];
7555 sb = src[i + 2];
7556 bb = dst[i + 2];
7557 sa = src[i + 3];
7558 ba = dst[i + 3];
7559 process();
7560 var a1 = sa * alpha / 255,
7561 a2 = 1 - a1;
7562 dst[i] = a1 * dr + a2 * br;
7563 dst[i + 1] = a1 * dg + a2 * bg;
7564 dst[i + 2] = a1 * db + a2 * bb;
7565 dst[i + 3] = sa * alpha + a2 * ba;
7566 }
7567 dstContext.putImageData(dstData, offset.x, offset.y);
7568 }
7569};
7570
7571var PaperScript = this.PaperScript = new function() {
7572var parse_js=new function(){function W(a,b,c){var d=[];for(var e=0;e<a.length;++e)d.push(b.call(c,a[e],e));return d}function V(c){return/^[a-z_$][a-z0-9_$]*$/i.test(c)&&c!="this"&&!M(d,c)&&!M(b,c)&&!M(a,c)}function U(a,b){var c={};a===!0&&(a={});for(var d in b)M(b,d)&&(c[d]=a&&M(a,d)?a[d]:b[d]);return c}function T(a,b){return b<1?"":Array(b+1).join(a)}function S(a,b){function z(a){var b=a[0],c=r[b];if(!c)throw new Error("Can't find generator for \""+b+'"');y.push(a);var d=c.apply(b,a.slice(1));y.pop();return d}function x(a){var b=a[0],c=a[1];c!=null&&(b=k([g(b),"=",m(c,"seq")]));return b}function w(a){return a?a.length==0?"{}":"{"+e+j(function(){return u(a).join(e)})+e+h("}"):";"}function v(a){var b=a.length;return b==0?"{}":"{"+e+W(a,function(a,d){var f=a[1].length>0,g=j(function(){return h(a[0]?k(["case",z(a[0])+":"]):"default:")},.5)+(f?e+j(function(){return u(a[1]).join(e)}):"");!c&&f&&d<b-1&&(g+=";");return g}).join(e)+e+h("}")}function u(a,b){for(var d=[],e=a.length-1,f=0;f<=e;++f){var g=a[f],i=z(g);i!=";"&&(!c&&f==e&&(g[0]=="while"&&O(g[2])||L(g[0],["for","for-in"])&&O(g[4])||g[0]=="if"&&O(g[2])&&!g[3]||g[0]=="if"&&g[3]&&O(g[3])?i=i.replace(/;*\s*$/,";"):i=i.replace(/;+\s*$/,"")),d.push(i))}return b?d:W(d,h)}function t(a,b,c,d){var e=d||"function";a&&(e+=" "+g(a)),e+="("+l(W(b,g))+")";return k([e,w(c)])}function s(a){if(a[0]=="do")return z(["block",[a]]);var b=a;for(;;){var c=b[0];if(c=="if"){if(!b[3])return z(["block",[a]]);b=b[3]}else if(c=="while"||c=="do")b=b[2];else if(c=="for"||c=="for-in")b=b[4];else break}return z(a)}function q(a){var b=a.toString(10),c=[b.replace(/^0\./,".")],d;Math.floor(a)===a?(c.push("0x"+a.toString(16).toLowerCase(),"0"+a.toString(8)),(d=/^(.*?)(0+)$/.exec(a))&&c.push(d[1]+"e"+d[2].length)):(d=/^0?\.(0+)(.*)$/.exec(a))&&c.push(d[2]+"e-"+(d[1].length+d[2].length),b.substr(b.indexOf(".")));return n(c)}function o(a){if(a[0]=="function"||a[0]=="object"){var b=J(y),c=b.pop(),d=b.pop();while(d){if(d[0]=="stat")return!0;if((d[0]=="seq"||d[0]=="call"||d[0]=="dot"||d[0]=="sub"||d[0]=="conditional")&&d[1]===c||(d[0]=="binary"||d[0]=="assign"||d[0]=="unary-postfix")&&d[2]===c)c=d,d=b.pop();else return!1}}return!M(P,a[0])}function n(a){if(a.length==1)return a[0];if(a.length==2){var b=a[1];a=a[0];return a.length<=b.length?a:b}return n([a[0],n(a.slice(1))])}function m(a){var b=z(a);for(var c=1;c<arguments.length;++c){var d=arguments[c];if(d instanceof Function&&d(a)||a[0]==d)return"("+b+")"}return b}function l(a){return a.join(","+f)}function k(a){if(c)return a.join(" ");var b=[];for(var d=0;d<a.length;++d){var e=a[d+1];b.push(a[d]),e&&(/[a-z0-9_\x24]$/i.test(a[d].toString())&&/^[a-z0-9_\x24]/i.test(e.toString())||/[\+\-]$/.test(a[d].toString())&&/^[\+\-]/.test(e.toString()))&&b.push(" ")}return b.join("")}function j(a,b){b==null&&(b=1),d+=b;try{return a.apply(null,J(arguments,1))}finally{d-=b}}function h(a){a==null&&(a=""),c&&(a=T(" ",b.indent_start+d*b.indent_level)+a);return a}function g(a){return a.toString()}b=U(b,{indent_start:0,indent_level:4,quote_keys:!1,space_colon:!1,beautify:!1});var c=!!b.beautify,d=0,e=c?"\n":"",f=c?" ":"",r={string:Q,num:q,name:g,toplevel:function(a){return u(a).join(e)},splice:function(a){var b=y[y.length-2][0];return M(R,b)?w.apply(this,arguments):W(u(a,!0),function(a,b){return b>0?h(a):a}).join(e)},block:w,"var":function(a){return"var "+l(W(a,x))+";"},"const":function(a){return"const "+l(W(a,x))+";"},"try":function(a,b,c){var d=["try",w(a)];b&&d.push("catch","("+b[0]+")",w(b[1])),c&&d.push("finally",w(c));return k(d)},"throw":function(a){return k(["throw",z(a)])+";"},"new":function(a,b){b=b.length>0?"("+l(W(b,z))+")":"";return k(["new",m(a,"seq","binary","conditional","assign",function(a){var b=N(),c={};try{b.with_walkers({call:function(){throw c},"function":function(){return this}},function(){b.walk(a)})}catch(d){if(d===c)return!0;throw d}})+b])},"switch":function(a,b){return k(["switch","("+z(a)+")",v(b)])},"break":function(a){var b="break";a!=null&&(b+=" "+g(a));return b+";"},"continue":function(a){var b="continue";a!=null&&(b+=" "+g(a));return b+";"},conditional:function(a,b,c){return k([m(a,"assign","seq","conditional"),"?",m(b,"seq"),":",m(c,"seq")])},assign:function(a,b,c){a&&a!==!0?a+="=":a="=";return k([z(b),a,m(c,"seq")])},dot:function(a){var b=z(a),c=1;a[0]=="num"?/\./.test(a[1])||(b+="."):o(a)&&(b="("+b+")");while(c<arguments.length)b+="."+g(arguments[c++]);return b},call:function(a,b){var c=z(a);o(a)&&(c="("+c+")");return c+"("+l(W(b,function(a){return m(a,"seq")}))+")"},"function":t,defun:t,"if":function(a,b,c){var d=["if","("+z(a)+")",c?s(b):z(b)];c&&d.push("else",z(c));return k(d)},"for":function(a,b,c,d){var e=["for"];a=(a!=null?z(a):"").replace(/;*\s*$/,";"+f),b=(b!=null?z(b):"").replace(/;*\s*$/,";"+f),c=(c!=null?z(c):"").replace(/;*\s*$/,"");var g=a+b+c;g=="; ; "&&(g=";;"),e.push("("+g+")",z(d));return k(e)},"for-in":function(a,b,c,d){return k(["for","("+(a?z(a).replace(/;+$/,""):z(b)),"in",z(c)+")",z(d)])},"while":function(a,b){return k(["while","("+z(a)+")",z(b)])},"do":function(a,b){return k(["do",z(b),"while","("+z(a)+")"])+";"},"return":function(a){var b=["return"];a!=null&&b.push(z(a));return k(b)+";"},binary:function(a,b,c){var d=z(b),e=z(c);if(L(b[0],["assign","conditional","seq"])||b[0]=="binary"&&B[a]>B[b[1]])d="("+d+")";if(L(c[0],["assign","conditional","seq"])||c[0]=="binary"&&B[a]>=B[c[1]]&&(c[1]!=a||!L(a,["&&","||","*"])))e="("+e+")";return k([d,a,e])},"unary-prefix":function(a,b){var c=z(b);b[0]=="num"||b[0]=="unary-prefix"&&!M(i,a+b[1])||!o(b)||(c="("+c+")");return a+(p(a.charAt(0))?" ":"")+c},"unary-postfix":function(a,b){var c=z(b);b[0]=="num"||b[0]=="unary-postfix"&&!M(i,a+b[1])||!o(b)||(c="("+c+")");return c+a},sub:function(a,b){var c=z(a);o(a)&&(c="("+c+")");return c+"["+z(b)+"]"},object:function(a){return a.length==0?"{}":"{"+e+j(function(){return W(a,function(a){if(a.length==3)return h(t(a[0],a[1][2],a[1][3],a[2]));var d=a[0],e=z(a[1]);b.quote_keys?d=Q(d):(typeof d=="number"||!c&&+d+""==d)&&parseFloat(d)>=0?d=q(+d):V(d)||(d=Q(d));return h(k(c&&b.space_colon?[d,":",e]:[d+":",e]))}).join(","+e)})+e+h("}")},regexp:function(a,b){return"/"+a+"/"+b},array:function(a){return a.length==0?"[]":k(["[",l(W(a,function(a){return!c&&a[0]=="atom"&&a[1]=="undefined"?"":m(a,"seq")})),"]"])},stat:function(a){return z(a).replace(/;*\s*$/,";")},seq:function(){return l(W(J(arguments),z))},label:function(a,b){return k([g(a),":",z(b)])},"with":function(a,b){return k(["with","("+z(a)+")",z(b)])},atom:function(a){return g(a)}},y=[];return z(a)}function Q(a){var b=0,c=0;a=a.replace(/[\\\b\f\n\r\t\x22\x27]/g,function(a){switch(a){case"\\":return"\\\\";case"\b":return"\\b";case"\f":return"\\f";case"\n":return"\\n";case"\r":return"\\r";case"\t":return"\\t";case'"':++b;return'"';case"'":++c;return"'"}return a});return b>c?"'"+a.replace(/\x27/g,"\\'")+"'":'"'+a.replace(/\x22/g,'\\"')+'"'}function O(a){return!a||a[0]=="block"&&(!a[1]||a[1].length==0)}function N(){function g(a,b){var c={},e;for(e in a)M(a,e)&&(c[e]=d[e],d[e]=a[e]);var f=b();for(e in c)M(c,e)&&(c[e]?d[e]=c[e]:delete d[e]);return f}function f(a){if(a==null)return null;try{e.push(a);var b=a[0],f=d[b];if(f){var g=f.apply(a,a.slice(1));if(g!=null)return g}f=c[b];return f.apply(a,a.slice(1))}finally{e.pop()}}function b(a){var b=[this[0]];a!=null&&b.push(W(a,f));return b}function a(a){return[this[0],W(a,function(a){var b=[a[0]];a.length>1&&(b[1]=f(a[1]));return b})]}var c={string:function(a){return[this[0],a]},num:function(a){return[this[0],a]},name:function(a){return[this[0],a]},toplevel:function(a){return[this[0],W(a,f)]},block:b,splice:b,"var":a,"const":a,"try":function(a,b,c){return[this[0],W(a,f),b!=null?[b[0],W(b[1],f)]:null,c!=null?W(c,f):null]},"throw":function(a){return[this[0],f(a)]},"new":function(a,b){return[this[0],f(a),W(b,f)]},"switch":function(a,b){return[this[0],f(a),W(b,function(a){return[a[0]?f(a[0]):null,W(a[1],f)]})]},"break":function(a){return[this[0],a]},"continue":function(a){return[this[0],a]},conditional:function(a,b,c){return[this[0],f(a),f(b),f(c)]},assign:function(a,b,c){return[this[0],a,f(b),f(c)]},dot:function(a){return[this[0],f(a)].concat(J(arguments,1))},call:function(a,b){return[this[0],f(a),W(b,f)]},"function":function(a,b,c){return[this[0],a,b.slice(),W(c,f)]},defun:function(a,b,c){return[this[0],a,b.slice(),W(c,f)]},"if":function(a,b,c){return[this[0],f(a),f(b),f(c)]},"for":function(a,b,c,d){return[this[0],f(a),f(b),f(c),f(d)]},"for-in":function(a,b,c,d){return[this[0],f(a),f(b),f(c),f(d)]},"while":function(a,b){return[this[0],f(a),f(b)]},"do":function(a,b){return[this[0],f(a),f(b)]},"return":function(a){return[this[0],f(a)]},binary:function(a,b,c){return[this[0],a,f(b),f(c)]},"unary-prefix":function(a,b){return[this[0],a,f(b)]},"unary-postfix":function(a,b){return[this[0],a,f(b)]},sub:function(a,b){return[this[0],f(a),f(b)]},object:function(a){return[this[0],W(a,function(a){return a.length==2?[a[0],f(a[1])]:[a[0],f(a[1]),a[2]]})]},regexp:function(a,b){return[this[0],a,b]},array:function(a){return[this[0],W(a,f)]},stat:function(a){return[this[0],f(a)]},seq:function(){return[this[0]].concat(W(J(arguments),f))},label:function(a,b){return[this[0],a,f(b)]},"with":function(a,b){return[this[0],f(a),f(b)]},atom:function(a){return[this[0],a]}},d={},e=[];return{walk:f,with_walkers:g,parent:function(){return e[e.length-2]},stack:function(){return e}}}function M(a,b){return Object.prototype.hasOwnProperty.call(a,b)}function L(a,b){for(var c=b.length;--c>=0;)if(b[c]===a)return!0;return!1}function K(a){return a.split("")}function J(a,b){return Array.prototype.slice.call(a,b||0)}function I(a){var b={};for(var c=0;c<a.length;++c)b[a[c]]=!0;return b}function H(a){a instanceof Function&&(a=a());for(var b=1,c=arguments.length;--c>0;++b)arguments[b]();return a}function G(a){var b=J(arguments,1);return function(){return a.apply(this,b.concat(J(arguments)))}}function F(a,b,c){function bk(a){try{++d.in_loop;return a()}finally{--d.in_loop}}function bi(a){var b=bg(a),c=d.token.value;if(e("operator")&&M(A,c)){if(bh(b)){g();return p("assign",A[c],b,bi(a))}i("Invalid assignment")}return b}function bh(a){if(!b)return!0;switch(a[0]){case"dot":case"sub":case"new":case"call":return!0;case"name":return a[1]!="this"}}function bg(a){var b=bf(a);if(e("operator","?")){g();var c=bj(!1);m(":");return p("conditional",b,c,bj(!1,a))}return b}function bf(a){return be(Y(!0),0,a)}function be(a,b,c){var f=e("operator")?d.token.value:null;f&&f=="in"&&c&&(f=null);var h=f!=null?B[f]:null;if(h!=null&&h>b){g();var i=be(Y(!0),h,c);return be(p("binary",f,a,i),b,c)}return a}function bd(a,b,c){(b=="++"||b=="--")&&!bh(c)&&i("Invalid use of "+b+" operator");return p(a,b,c)}function bc(a,b){if(e("punc",".")){g();return bc(p("dot",a,bb()),b)}if(e("punc","[")){g();return bc(p("sub",a,H(bj,G(m,"]"))),b)}if(b&&e("punc","(")){g();return bc(p("call",a,Z(")")),!0)}return b&&e("operator")&&M(z,d.token.value)?H(G(bd,"unary-postfix",d.token.value,a),g):a}function bb(){switch(d.token.type){case"name":case"operator":case"keyword":case"atom":return H(d.token.value,g);default:k()}}function ba(){switch(d.token.type){case"num":case"string":return H(d.token.value,g)}return bb()}function _(){var a=!0,c=[];while(!e("punc","}")){a?a=!1:m(",");if(!b&&e("punc","}"))break;var f=d.token.type,h=ba();f!="name"||h!="get"&&h!="set"||!!e("punc",":")?(m(":"),c.push([h,bj(!1)])):c.push([bb(),P(!1),h])}g();return p("object",c)}function $(){return p("array",Z("]",!b,!0))}function Z(a,b,c){var d=!0,f=[];while(!e("punc",a)){d?d=!1:m(",");if(b&&e("punc",a))break;e("punc",",")&&c?f.push(["atom","undefined"]):f.push(bj(!1))}g();return f}function X(){var a=Y(!1),b;e("punc","(")?(g(),b=Z(")")):b=[];return bc(p("new",a,b),!0)}function W(){return p("const",U())}function V(a){return p("var",U(a))}function U(a){var b=[];for(;;){e("name")||k();var c=d.token.value;g(),e("operator","=")?(g(),b.push([c,bj(!1,a)])):b.push([c]);if(!e("punc",","))break;g()}return b}function T(){var a=R(),b,c;if(e("keyword","catch")){g(),m("("),e("name")||i("Name expected");var f=d.token.value;g(),m(")"),b=[f,R()]}e("keyword","finally")&&(g(),c=R()),!b&&!c&&i("Missing catch/finally blocks");return p("try",a,b,c)}function R(){m("{");var a=[];while(!e("punc","}"))e("eof")&&k(),a.push(t());g();return a}function Q(){var a=q(),b=t(),c;e("keyword","else")&&(g(),c=t());return p("if",a,b,c)}function O(a){var b=a[0]=="var"?p("name",a[1][0]):a;g();var c=bj();m(")");return p("for-in",a,b,c,bk(t))}function N(a){m(";");var b=e("punc",";")?null:bj();m(";");var c=e("punc",")")?null:bj();m(")");return p("for",a,b,c,bk(t))}function K(){m("(");var a=null;if(!e("punc",";")){a=e("keyword","var")?(g(),V(!0)):bj(!0,!0);if(e("operator","in"))return O(a)}return N(a)}function I(a){var b;n()||(b=e("name")?d.token.value:null),b!=null?(g(),L(b,d.labels)||i("Label "+b+" without matching loop or statement")):d.in_loop==0&&i(a+" not inside a loop or switch"),o();return p(a,b)}function F(){return p("stat",H(bj,o))}function w(a){d.labels.push(a);var c=d.token,e=t();b&&!M(C,e[0])&&k(c),d.labels.pop();return p("label",a,e)}function s(a){return c?function(){var b=d.token,c=a.apply(this,arguments);c[0]=r(c[0],b,h());return c}:a}function r(a,b,c){return a instanceof E?a:new E(a,b,c)}function q(){m("(");var a=bj();m(")");return a}function p(){return J(arguments)}function o(){e("punc",";")?g():n()||k()}function n(){return!b&&(d.token.nlb||e("eof")||e("punc","}"))}function m(a){return l("punc",a)}function l(a,b){if(e(a,b))return g();j(d.token,"Unexpected token "+d.token.type+", expected "+a)}function k(a){a==null&&(a=d.token),j(a,"Unexpected token: "+a.type+" ("+a.value+")")}function j(a,b){i(b,a.line,a.col)}function i(a,b,c,e){var f=d.input.context();u(a,b!=null?b:f.tokline,c!=null?c:f.tokcol,e!=null?e:f.tokpos)}function h(){return d.prev}function g(){d.prev=d.token,d.peeked?(d.token=d.peeked,d.peeked=null):d.token=d.input();return d.token}function f(){return d.peeked||(d.peeked=d.input())}function e(a,b){return v(d.token,a,b)}var d={input:typeof a=="string"?x(a,!0):a,token:null,prev:null,peeked:null,in_function:0,in_loop:0,labels:[]};d.token=g();var t=s(function(){e("operator","/")&&(d.peeked=null,d.token=d.input(!0));switch(d.token.type){case"num":case"string":case"regexp":case"operator":case"atom":return F();case"name":return v(f(),"punc",":")?w(H(d.token.value,g,g)):F();case"punc":switch(d.token.value){case"{":return p("block",R());case"[":case"(":return F();case";":g();return p("block");default:k()};case"keyword":switch(H(d.token.value,g)){case"break":return I("break");case"continue":return I("continue");case"debugger":o();return p("debugger");case"do":return function(a){l("keyword","while");return p("do",H(q,o),a)}(bk(t));case"for":return K();case"function":return P(!0);case"if":return Q();case"return":d.in_function==0&&i("'return' outside of function");return p("return",e("punc",";")?(g(),null):n()?null:H(bj,o));case"switch":return p("switch",q(),S());case"throw":return p("throw",H(bj,o));case"try":return T();case"var":return H(V,o);case"const":return H(W,o);case"while":return p("while",q(),bk(t));case"with":return p("with",q(),t());default:k()}}}),P=s(function(a){var b=e("name")?H(d.token.value,g):null;a&&!b&&k(),m("(");return p(a?"defun":"function",b,function(a,b){while(!e("punc",")"))a?a=!1:m(","),e("name")||k(),b.push(d.token.value),g();g();return b}(!0,[]),function(){++d.in_function;var a=d.in_loop;d.in_loop=0;var b=R();--d.in_function,d.in_loop=a;return b}())}),S=G(bk,function(){m("{");var a=[],b=null;while(!e("punc","}"))e("eof")&&k(),e("keyword","case")?(g(),b=[],a.push([bj(),b]),m(":")):e("keyword","default")?(g(),m(":"),b=[],a.push([null,b])):(b||k(),b.push(t()));g();return a}),Y=s(function(a){if(e("operator","new")){g();return X()}if(e("operator")&&M(y,d.token.value))return bd("unary-prefix",H(d.token.value,g),Y(a));if(e("punc")){switch(d.token.value){case"(":g();return bc(H(bj,G(m,")")),a);case"[":g();return bc($(),a);case"{":g();return bc(_(),a)}k()}if(e("keyword","function")){g();return bc(P(!1),a)}if(M(D,d.token.type)){var b=d.token.type=="regexp"?p("regexp",d.token.value[0],d.token.value[1]):p(d.token.type,d.token.value);return bc(H(b,g),a)}k()}),bj=s(function(a,b){arguments.length==0&&(a=!0);var c=bi(b);if(a&&e("punc",",")){g();return p("seq",c,bj(!0,b))}return c});return p("toplevel",function(a){while(!e("eof"))a.push(t());return a}([]))}function E(a,b,c){this.name=a,this.start=b,this.end=c}function x(b){function P(a){if(a)return I();y(),v();var b=g();if(!b)return x("eof");if(o(b))return C();if(b=='"'||b=="'")return F();if(M(l,b))return x("punc",h());if(b==".")return L();if(b=="/")return K();if(M(e,b))return J();if(b=="\\"||q(b))return N();B("Unexpected character '"+b+"'")}function O(a,b){try{return b()}catch(c){if(c===w)B(a);else throw c}}function N(){var b=A(r);return M(a,b)?M(i,b)?x("operator",b):M(d,b)?x("atom",b):x("keyword",b):x("name",b)}function L(){h();return o(g())?C("."):x("punc",".")}function K(){h();var a=f.regex_allowed;switch(g()){case"/":f.comments_before.push(G()),f.regex_allowed=a;return P();case"*":f.comments_before.push(H()),f.regex_allowed=a;return P()}return f.regex_allowed?I():J("/")}function J(a){function b(a){if(!g())return a;var c=a+g();if(M(i,c)){h();return b(c)}return a}return x("operator",b(a||h()))}function I(){return O("Unterminated regular expression",function(){var a=!1,b="",c,d=!1;while(c=h(!0))if(a)b+="\\"+c,a=!1;else if(c=="[")d=!0,b+=c;else if(c=="]"&&d)d=!1,b+=c;else{if(c=="/"&&!d)break;c=="\\"?a=!0:b+=c}var e=A(function(a){return M(m,a)});return x("regexp",[b,e])})}function H(){h();return O("Unterminated multiline comment",function(){var a=t("*/",!0),b=f.text.substring(f.pos,a),c=x("comment2",b,!0);f.pos=a+2,f.line+=b.split("\n").length-1,f.newline_before=b.indexOf("\n")>=0;return c})}function G(){h();var a=t("\n"),b;a==-1?(b=f.text.substr(f.pos),f.pos=f.text.length):(b=f.text.substring(f.pos,a),f.pos=a);return x("comment1",b,!0)}function F(){return O("Unterminated string constant",function(){var a=h(),b="";for(;;){var c=h(!0);if(c=="\\")c=D();else if(c==a)break;b+=c}return x("string",b)})}function E(a){var b=0;for(;a>0;--a){var c=parseInt(h(!0),16);isNaN(c)&&B("Invalid hex-character pattern in string"),b=b<<4|c}return b}function D(){var a=h(!0);switch(a){case"n":return"\n";case"r":return"\r";case"t":return"\t";case"b":return"\b";case"v":return"";case"f":return"\f";case"0":return" ";case"x":return String.fromCharCode(E(2));case"u":return String.fromCharCode(E(4));case"\n":return"";default:return a}}function C(a){var b=!1,c=!1,d=!1,e=a==".",f=A(function(f,g){if(f=="x"||f=="X")return d?!1:d=!0;if(!d&&(f=="E"||f=="e"))return b?!1:b=c=!0;if(f=="-")return c||g==0&&!a?!0:!1;if(f=="+")return c;c=!1;if(f==".")return!e&&!d?e=!0:!1;return p(f)});a&&(f=a+f);var g=s(f);if(!isNaN(g))return x("num",g);B("Invalid syntax: "+f)}function B(a){u(a,f.tokline,f.tokcol,f.tokpos)}function A(a){var b="",c=g(),d=0;while(c&&a(c,d++))b+=h(),c=g();return b}function y(){while(M(j,g()))h()}function x(a,b,d){f.regex_allowed=a=="operator"&&!M(z,b)||a=="keyword"&&M(c,b)||a=="punc"&&M(k,b);var e={type:a,value:b,line:f.tokline,col:f.tokcol,pos:f.tokpos,nlb:f.newline_before};d||(e.comments_before=f.comments_before,f.comments_before=[]),f.newline_before=!1;return e}function v(){f.tokline=f.line,f.tokcol=f.col,f.tokpos=f.pos}function t(a,b){var c=f.text.indexOf(a,f.pos);if(b&&c==-1)throw w;return c}function n(){return!f.peek()}function h(a){var b=f.text.charAt(f.pos++);if(a&&!b)throw w;b=="\n"?(f.newline_before=!0,++f.line,f.col=0):++f.col;return b}function g(){return f.text.charAt(f.pos)}var f={text:b.replace(/\r\n?|[\n\u2028\u2029]/g,"\n").replace(/^\uFEFF/,""),pos:0,tokpos:0,line:0,tokline:0,col:0,tokcol:0,newline_before:!1,regex_allowed:!1,comments_before:[]};P.context=function(a){a&&(f=a);return f};return P}function v(a,b,c){return a.type==b&&(c==null||a.value==c)}function u(a,b,c,d){throw new t(a,b,c,d)}function t(a,b,c,d){this.message=a,this.line=b,this.col=c,this.pos=d}function s(a){if(f.test(a))return parseInt(a.substr(2),16);if(g.test(a))return parseInt(a.substr(1),8);if(h.test(a))return parseFloat(a)}function r(a){return q(a)||o(a)}function q(a){return a=="$"||a=="_"||n(a)}function p(a){return o(a)||n(a)}function o(a){a=a.charCodeAt(0);return a>=48&&a<=57}function n(a){a=a.charCodeAt(0);return a>=65&&a<=90||a>=97&&a<=122}var a=I(["break","case","catch","const","continue","default","delete","do","else","finally","for","function","if","in","instanceof","new","return","switch","throw","try","typeof","var","void","while","with"]),b=I(["abstract","boolean","byte","char","class","debugger","double","enum","export","extends","final","float","goto","implements","import","int","interface","long","native","package","private","protected","public","short","static","super","synchronized","throws","transient","volatile"]),c=I(["return","new","delete","throw","else","case"]),d=I(["false","null","true","undefined"]),e=I(K("+-*&%=<>!?|~^")),f=/^0x[0-9a-f]+$/i,g=/^0[0-7]+$/,h=/^\d*\.?\d*(?:e[+-]?\d*(?:\d\.?|\.?\d)\d*)?$/i,i=I(["in","instanceof","typeof","new","void","delete","++","--","+","-","!","~","&","|","^","*","/","%",">>","<<",">>>","<",">","<=",">=","==","===","!=","!==","?","=","+=","-=","/=","*=","%=",">>=","<<=",">>>=","|=","^=","&=","&&","||"]),j=I(K(" \n\r\t")),k=I(K("[{}(,.;:")),l=I(K("[]{}(),;:")),m=I(K("gmsiy"));t.prototype.toString=function(){return this.message+" (line: "+this.line+", col: "+this.col+", pos: "+this.pos+")"};var w={},y=I(["typeof","void","delete","--","++","!","~","-","+"]),z=I(["--","++"]),A=function(a,b,c){while(c<a.length)b[a[c]]=a[c].substr(0,a[c].length-1),c++;return b}(["+=","-=","/=","*=","%=",">>=","<<=",">>>=","|=","^=","&="],{"=":!0},0),B=function(a,b){for(var c=0,d=1;c<a.length;++c,++d){var e=a[c];for(var f=0;f<e.length;++f)b[e[f]]=d}return b}([["||"],["&&"],["|"],["^"],["&"],["==","===","!=","!=="],["<",">","<=",">=","in","instanceof"],[">>","<<",">>>"],["+","-"],["*","/","%"]],{}),C=I(["for","do","while","switch"]),D=I(["atom","num","string","regexp","name"]);E.prototype.toString=function(){return this.name};var P=I(["name","array","object","string","dot","sub","call","regexp"]),R=I(["if","while","do","for","for-in","with"]);return{parse:F,gen_code:S,tokenizer:x,ast_walker:N}}
7573
7574 // Math Operators
7575
7576 var operators = {
7577 '+': 'add',
7578 '-': 'subtract',
7579 '*': 'multiply',
7580 '/': 'divide',
7581 '%': 'modulo',
7582 '==': 'equals',
7583 '!=': 'equals'
7584 };
7585
7586 function $eval(left, operator, right) {
7587 var handler = operators[operator];
7588 if (left && left[handler]) {
7589 var res = left[handler](right);
7590 return operator == '!=' ? !res : res;
7591 }
7592 switch (operator) {
7593 case '+': return left + right;
7594 case '-': return left - right;
7595 case '*': return left * right;
7596 case '/': return left / right;
7597 case '%': return left % right;
7598 case '==': return left == right;
7599 case '!=': return left != right;
7600 default:
7601 throw new Error('Implement Operator: ' + operator);
7602 }
7603 };
7604
7605 // Sign Operators
7606
7607 var signOperators = {
7608 '-': 'negate'
7609 };
7610
7611 function $sign(operator, value) {
7612 var handler = signOperators[operator];
7613 if (value && value[handler]) {
7614 return value[handler]();
7615 }
7616 switch (operator) {
7617 case '+': return +value;
7618 case '-': return -value;
7619 default:
7620 throw new Error('Implement Sign Operator: ' + operator);
7621 }
7622 }
7623
7624 // AST Helpers
7625
7626 function isDynamic(exp) {
7627 var type = exp[0];
7628 return type != 'num' && type != 'string';
7629 }
7630
7631 function handleOperator(operator, left, right) {
7632 // Only replace operators with calls to $operator if the left hand side
7633 // is potentially an object.
7634 if (operators[operator] && isDynamic(left)) {
7635 // Replace with call to $operator(left, operator, right):
7636 return ['call', ['name', '$eval'],
7637 [left, ['string', operator], right]];
7638 }
7639 }
7640
7641 /**
7642 * Compiles PaperScript code into JavaScript code.
7643 *
7644 * @name PaperScript.compile
7645 * @function
7646 * @param {String} code The PaperScript code.
7647 * @return {String} The compiled PaperScript as JavaScript code.
7648 */
7649 function compile(code) {
7650 // Use parse-js to translate the code into a AST structure which is then
7651 // walked and parsed for operators to overload. The resulting AST is
7652 // translated back to code and evaluated.
7653 var ast = parse_js.parse(code),
7654 walker = parse_js.ast_walker(),
7655 walk = walker.walk;
7656
7657 ast = walker.with_walkers({
7658 'binary': function(operator, left, right) {
7659 // Handle simple mathematical operators here:
7660 return handleOperator(operator, left = walk(left),
7661 right = walk(right))
7662 // Always return something since we're walking left and
7663 || [this[0], operator, left, right];
7664 },
7665
7666 'assign': function(operator, left, right) {
7667 var res = handleOperator(operator, left = walk(left),
7668 right = walk(right));
7669 if (res)
7670 return [this[0], true, left, res];
7671 return [this[0], operator, left, right];
7672 },
7673
7674 'unary-prefix': function(operator, exp) {
7675 if (signOperators[operator] && isDynamic(exp)) {
7676 return ['call', ['name', '$sign'],
7677 [['string', operator], walk(exp)]];
7678 }
7679 }
7680 }, function() {
7681 return walk(ast);
7682 });
7683
7684 return parse_js.gen_code(ast, {
7685 beautify: true
7686 });
7687 }
7688
7689 function evaluate(code, scope) {
7690 paper = scope;
7691 var view = scope.view,
7692 tool = /on(?:Key|Mouse)(?:Up|Down|Move|Drag)/.test(code)
7693 && new Tool(),
7694 res;
7695 with (scope) {
7696 (function() {
7697 var onEditOptions, onSelect, onDeselect, onReselect, onMouseDown,
7698 onMouseUp, onMouseDrag, onMouseMove, onKeyDown, onKeyUp,
7699 onFrame, onResize,
7700 handlers = [ 'onEditOptions', 'onSelect', 'onDeselect',
7701 'onReselect', 'onMouseDown', 'onMouseUp', 'onMouseDrag',
7702 'onMouseMove', 'onKeyDown', 'onKeyUp'];
7703 res = eval(compile(code));
7704 if (tool) {
7705 Base.each(handlers, function(key) {
7706 tool[key] = eval(key);
7707 });
7708 }
7709 if (view) {
7710 view.onResize = onResize;
7711 view.setOnFrame(onFrame);
7712 view.draw();
7713 }
7714 }).call(scope);
7715 }
7716 return res;
7717 }
7718
7719 function request(url, scope) {
7720 var xhr = new (window.ActiveXObject || XMLHttpRequest)(
7721 'Microsoft.XMLHTTP');
7722 xhr.open('GET', url, true);
7723 if (xhr.overrideMimeType) {
7724 xhr.overrideMimeType('text/plain');
7725 }
7726 xhr.onreadystatechange = function() {
7727 if (xhr.readyState === 4) {
7728 return evaluate(xhr.responseText, scope);
7729 }
7730 };
7731 return xhr.send(null);
7732 }
7733
7734 function load() {
7735 var scripts = document.getElementsByTagName('script');
7736 for (var i = 0, l = scripts.length; i < l; i++) {
7737 var script = scripts[i];
7738 if (/^text\/(?:x-|)paperscript$/.test(script.type)
7739 && !script.getAttribute('data-paper-loaded')) {
7740 var scope = new PaperScope(script);
7741 scope.setup(PaperScript.getAttribute(script, 'canvas'));
7742 if (script.src) {
7743 request(script.src, scope);
7744 } else {
7745 evaluate(script.innerHTML, scope);
7746 }
7747 script.setAttribute('data-paper-loaded', true);
7748 }
7749 }
7750 }
7751
7752 DomEvent.add(window, { load: load });
7753
7754 function handleAttribute(name) {
7755 name += 'Attribute';
7756 return function(el, attr) {
7757 return el[name](attr) || el[name]('data-paper-' + attr);
7758 };
7759 }
7760
7761 return {
7762 compile: compile,
7763 evaluate: evaluate,
7764 load: load,
7765 getAttribute: handleAttribute('get'),
7766 hasAttribute: handleAttribute('has')
7767 };
7768
7769};
7770
7771this.load = PaperScript.load;
7772
7773Base.each(this, function(val, key) {
7774 if (val && val.prototype instanceof Base) {
7775 val._name = key;
7776 }
7777});
7778
7779this.enumerable = true;
7780return new (PaperScope.inject(this));
7781};