diff -r e4afd8643576 -r 239d372644a0 client/lib/paper.js --- a/client/lib/paper.js Tue Sep 09 17:49:31 2014 +0200 +++ b/client/lib/paper.js Wed Sep 10 16:12:07 2014 +0200 @@ -1,28 +1,28 @@ /*! - * Paper.js v0.9.15 - The Swiss Army Knife of Vector Graphics Scripting. + * Paper.js v0.9.20 - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * - * Copyright (c) 2011 - 2013, Juerg Lehni & Jonathan Puckey - * http://lehni.org/ & http://jonathanpuckey.com/ + * Copyright (c) 2011 - 2014, Juerg Lehni & Jonathan Puckey + * http://scratchdisk.com/ & http://jonathanpuckey.com/ * * Distributed under the MIT license. See LICENSE file for details. * * All rights reserved. * - * Date: Sun Dec 1 23:54:52 2013 +0100 + * Date: Mon Aug 25 14:21:13 2014 +0200 * *** * - * straps.js - Class inheritance library with support for bean-style accessors + * Straps.js - Class inheritance library with support for bean-style accessors * * Copyright (c) 2006 - 2013 Juerg Lehni - * http://lehni.org/ + * http://scratchdisk.com/ * * Distributed under the MIT license. * *** * - * acorn.js + * Acorn.js * http://marijnhaverbeke.nl/acorn/ * * Acorn is a tiny, fast JavaScript parser written in JavaScript, @@ -33,8 +33,7 @@ var paper = new function(undefined) { var Base = new function() { - var hidden = /^(statics|generics|preserve|enumerable|prototype|toString|valueOf)$/, - slice = [].slice, + var hidden = /^(statics|enumerable|beans|preserve)$/, forEach = [].forEach || function(iter, bind) { for (var i = 0, l = this.length; i < l; i++) @@ -79,28 +78,28 @@ return _define(obj, name, desc); }; - function inject(dest, src, enumerable, base, preserve, generics) { - var beans; - - function field(name, val, dontCheck, generics) { - var val = val || (val = describe(src, name)) + function inject(dest, src, enumerable, beans, preserve) { + var beansNames = {}; + + function field(name, val) { + val = val || (val = describe(src, name)) && (val.get ? val : val.value); if (typeof val === 'string' && val[0] === '#') val = dest[val.substring(1)] || val; var isFunc = typeof val === 'function', res = val, prev = preserve || isFunc - ? (val && val.get ? name in dest : dest[name]) : null, + ? (val && val.get ? name in dest : dest[name]) + : null, bean; - if ((dontCheck || val !== undefined && src.hasOwnProperty(name)) - && (!preserve || !prev)) { + if (!preserve || !prev) { if (isFunc && prev) val.base = prev; - if (isFunc && beans && val.length === 0 - && (bean = name.match(/^(get|is)(([A-Z])(.*))$/))) - beans.push([ bean[3].toLowerCase() + bean[4], bean[2] ]); + if (isFunc && beans !== false + && (bean = name.match(/^([gs]et|is)(([A-Z])(.*))$/))) + beansNames[bean[3].toLowerCase() + bean[4]] = bean[2]; if (!res || isFunc || !res.get || typeof res.get !== 'function' - || res.get.length !== 0) + || !Base.isPlainObject(res)) res = { value: res, writable: true }; if ((describe(dest, name) || { configurable: true }).configurable) { @@ -109,27 +108,18 @@ } define(dest, name, res); } - if (generics && isFunc && (!preserve || !generics[name])) { - generics[name] = function(bind) { - return bind && dest[name].apply(bind, - slice.call(arguments, 1)); - }; - } } if (src) { - beans = []; - for (var name in src) + for (var name in src) { if (src.hasOwnProperty(name) && !hidden.test(name)) - field(name, null, true, generics); - field('toString'); - field('valueOf'); - for (var i = 0, l = beans.length; i < l; i++) { - var bean = beans[i], - part = bean[1]; - field(bean[0], { - get: dest['get' + part] || dest['is' + part], - set: dest['set' + part] - }, true); + field(name); + } + for (var name in beansNames) { + var part = beansNames[name], + set = dest['set' + part], + get = dest['get' + part] || set && dest['is' + part]; + if (get && (beans === true || get.length === 0)) + field(name, { get: get, set: set }); } } return dest; @@ -144,30 +134,25 @@ return bind; } - function copy(dest, source) { - for (var i in source) - if (source.hasOwnProperty(i)) - dest[i] = source[i]; - return dest; - } - - function clone(obj) { - return copy(new obj.constructor(), obj); + function set(obj, props) { + for (var i in props) + if (props.hasOwnProperty(i)) + obj[i] = props[i]; + return obj; } return inject(function Base() { for (var i = 0, l = arguments.length; i < l; i++) - copy(this, arguments[i]); + set(this, arguments[i]); }, { inject: function(src) { if (src) { - var proto = this.prototype, - base = Object.getPrototypeOf(proto).constructor, - statics = src.statics === true ? src : src.statics; - if (statics != src) - inject(proto, src, src.enumerable, base && base.prototype, - src.preserve, src.generics && this); - inject(this, statics, true, base, src.preserve); + var statics = src.statics === true ? src : src.statics, + beans = src.beans, + preserve = src.preserve; + if (statics !== src) + inject(this.prototype, src, src.enumerable, beans, preserve); + inject(this, statics, true, beans, preserve); } for (var i = 1, l = arguments.length; i < l; i++) this.inject(arguments[i]); @@ -184,6 +169,7 @@ base.apply(this, arguments); }; ctor.prototype = create(this.prototype); + ctor.base = base; define(ctor.prototype, 'constructor', { value: ctor, writable: true, configurable: true }); inject(ctor, this, true); @@ -191,8 +177,11 @@ } }, true).inject({ inject: function() { - for (var i = 0, l = arguments.length; i < l; i++) - inject(this, arguments[i], arguments[i].enumerable); + for (var i = 0, l = arguments.length; i < l; i++) { + var src = arguments[i]; + if (src) + inject(this, src, src.enumerable, src.beans, src.preserve); + } return this; }, @@ -205,6 +194,10 @@ return each(this, iter, bind); }, + set: function(props) { + return set(this, props); + }, + clone: function() { return new this.constructor(this); }, @@ -214,10 +207,10 @@ create: create, define: define, describe: describe, - copy: copy, + set: set, clone: function(obj) { - return copy(new obj.constructor(), obj); + return set(new obj.constructor(), obj); }, isPlainObject: function(obj) { @@ -230,7 +223,6 @@ for (var i = 0, l = arguments.length; i < l; i++) if (arguments[i] !== undefined) return arguments[i]; - return null; } } }); @@ -239,9 +231,17 @@ if (typeof module !== 'undefined') module.exports = Base; +if (!Array.isArray) { + Array.isArray = function(obj) { + return Object.prototype.toString.call(obj) === '[object Array]'; + }; +} + +if (!document.head) { + document.head = document.getElementsByTagName('head')[0]; +} + Base.inject({ - generics: true, - toString: function() { return this._id != null ? (this._class || 'Object') + (this._name @@ -265,8 +265,8 @@ return Base.serialize(this); }, - _set: function(props, exclude) { - if (props && Base.isPlainObject(props)) { + _set: function(props, exclude, dontCheck) { + if (props && (dontCheck || Base.isPlainObject(props))) { var orig = props._filtering || props; for (var key in orig) { if (key in this && orig.hasOwnProperty(key) @@ -282,7 +282,9 @@ statics: { - exports: {}, + exports: { + enumerable: true + }, extend: function extend() { var res = extend.base.apply(this, arguments), @@ -319,7 +321,8 @@ if (!checkKeys(obj1, obj2) || !checkKeys(obj2, obj1)) return false; for (var i in obj1) { - if (obj1.hasOwnProperty(i) && !Base.equals(obj1[i], obj2[i])) + if (obj1.hasOwnProperty(i) + && !Base.equals(obj1[i], obj2[i])) return false; } return true; @@ -327,58 +330,57 @@ return false; }, - read: function(list, start, length, options) { + read: function(list, start, options, length) { if (this === Base) { var value = this.peek(list, start); - list._index++; - list.__read = 1; + list.__index++; return value; } var proto = this.prototype, readIndex = proto._readIndex, - index = start || readIndex && list._index || 0; + index = start || readIndex && list.__index || 0; if (!length) length = list.length - index; var obj = list[index]; if (obj instanceof this || options && options.readNull && obj == null && length <= 1) { if (readIndex) - list._index = index + 1; + list.__index = index + 1; return obj && options && options.clone ? obj.clone() : obj; } obj = Base.create(this.prototype); if (readIndex) obj.__read = true; - if (options) - obj.__options = options; obj = obj.initialize.apply(obj, index > 0 || length < list.length ? Array.prototype.slice.call(list, index, index + length) : list) || obj; if (readIndex) { - list._index = index + obj.__read; - list.__read = obj.__read; - delete obj.__read; - if (options) - delete obj.__options; + list.__index = index + obj.__read; + obj.__read = undefined; } return obj; }, peek: function(list, start) { - return list[list._index = start || list._index || 0]; + return list[list.__index = start || list.__index || 0]; + }, + + remain: function(list) { + return list.length - (list.__index || 0); }, readAll: function(list, start, options) { - var res = [], entry; + var res = [], + entry; for (var i = start || 0, l = list.length; i < l; i++) { res.push(Array.isArray(entry = list[i]) - ? this.read(entry, 0, 0, options) - : this.read(list, i, 1, options)); + ? this.read(entry, 0, options) + : this.read(list, i, options, 1)); } return res; }, - readNamed: function(list, name, start, length, options) { + readNamed: function(list, name, start, options, length) { var value = this.getNamed(list, name), hasObject = value !== undefined; if (hasObject) { @@ -389,7 +391,7 @@ } filtered[name] = undefined; } - return this.read(hasObject ? [value] : list, start, length, options); + return this.read(hasObject ? [value] : list, start, options, length); }, getNamed: function(list, name) { @@ -404,8 +406,9 @@ return !!this.getNamed(list, name); }, - isPlainValue: function(obj) { - return this.isPlainObject(obj) || Array.isArray(obj); + isPlainValue: function(obj, asString) { + return this.isPlainObject(obj) || Array.isArray(obj) + || asString && typeof obj === 'string'; }, serialize: function(obj, options, compact, dictionary) { @@ -497,7 +500,10 @@ }, exportJSON: function(obj, options) { - return JSON.stringify(Base.serialize(obj, options)); + var json = Base.serialize(obj, options); + return options && options.asString === false + ? json + : JSON.stringify(json); }, importJSON: function(json, target) { @@ -509,7 +515,7 @@ : Base.create(type.prototype), isTarget = obj === target; if (args.length === 1 && obj instanceof Item - && (!(obj instanceof Layer) || isTarget)) { + && (isTarget || !(obj instanceof Layer))) { var arg = args[0]; if (Base.isPlainObject(arg)) arg.insert = false; @@ -538,7 +544,7 @@ args.push.apply(args, items); var removed = list.splice.apply(list, args); for (var i = 0, l = removed.length; i < l; i++) - delete removed[i]._index; + removed[i]._index = undefined; for (var i = index + amount, l = list.length; i < l; i++) list[i]._index = i; return removed; @@ -575,7 +581,7 @@ if (entry) { var handlers = this._handlers = this._handlers || {}; handlers = handlers[type] = handlers[type] || []; - if (handlers.indexOf(func) == -1) { + if (handlers.indexOf(func) == -1) { handlers.push(func); if (entry.install && handlers.length == 1) entry.install.call(this, type); @@ -617,26 +623,13 @@ if (!handlers) return false; var args = [].slice.call(arguments, 1), - PaperScript = paper.PaperScript, - handleException = PaperScript && PaperScript.handleException, that = this; - - function callHandlers() { - for (var i in handlers) { - if (handlers[i].apply(that, args) === false - && event && event.stop) - event.stop(); - } - } - - if (handleException) { - try { - callHandlers(); - } catch (e) { - handleException(e); - } - } else { - callHandlers(); + for (var i = 0, l = handlers.length; i < l; i++) { + if (handlers[i].apply(that, args) === false + && event && event.stop) { + event.stop(); + break; + } } return true; }, @@ -649,6 +642,19 @@ off: '#detach', trigger: '#fire', + _installEvents: function(install) { + var handlers = this._handlers, + key = install ? 'install' : 'uninstall'; + for (var type in handlers) { + if (handlers[type].length > 0) { + var entry = this._eventTypes[type], + func = entry[key]; + if (func) + func.call(this, type); + } + } + }, + statics: { inject: function inject() { for (var i = 0, l = arguments.length; i < l; i++) { @@ -667,11 +673,11 @@ return this[name]; }; src['set' + part] = function(func) { - if (func) { + var prev = this[name]; + if (prev) + this.detach(type, prev); + if (func) this.attach(type, func); - } else if (this[name]) { - this.detach(type, this[name]); - } this[name] = func; }; }); @@ -687,16 +693,18 @@ var PaperScope = Base.extend({ _class: 'PaperScope', - initialize: function PaperScope(script) { + initialize: function PaperScope() { paper = this; + this.settings = new Base({ + applyMatrix: true, + handleSize: 4, + hitTolerance: 0 + }); this.project = null; this.projects = []; this.tools = []; this.palettes = []; - this._id = script && (script.getAttribute('id') || script.src) - || ('paperscope-' + (PaperScope._id++)); - if (script) - script.setAttribute('id', this._id); + this._id = PaperScope._id++; PaperScope._scopes[this._id] = this; if (!this.support) { var ctx = CanvasProvider.getContext(1, 1); @@ -708,26 +716,19 @@ } }, - version: '0.9.15', + version: '0.9.20', getView: function() { - return this.project && this.project.view; - }, - - getTool: function() { - if (!this._tool) - this._tool = new Tool(); - return this._tool; + return this.project && this.project.getView(); }, getPaper: function() { return this; }, - evaluate: function(code) { - var res = paper.PaperScript.evaluate(code, this); + execute: function(code, url, options) { + paper.PaperScript.execute(code, this, url, options); View.updateFocus(); - return res; }, install: function(scope) { @@ -740,15 +741,14 @@ } }); }); - for (var key in this) { - if (!/^(version|_id)/.test(key)) + for (var key in this) + if (!/^_/.test(key) && this[key]) scope[key] = this[key]; - } - }, - - setup: function(canvas) { + }, + + setup: function(element) { paper = this; - this.project = new Project(canvas); + this.project = new Project(element); return this; }, @@ -783,8 +783,6 @@ _id: 0, get: function(id) { - if (typeof id === 'object') - id = id.getAttribute('id'); return this._scopes[id] || null; }, @@ -839,6 +837,10 @@ return Math.round(val * this.multiplier) / this.multiplier; }, + pair: function(val1, val2, separator) { + return this.number(val1) + (separator || ',') + this.number(val2); + }, + point: function(val, separator) { return this.number(val.x) + (separator || ',') + this.number(val.y); }, @@ -898,15 +900,29 @@ sqrt = Math.sqrt, pow = Math.pow, cos = Math.cos, - PI = Math.PI; + PI = Math.PI, + TOLERANCE = 10e-6, + EPSILON = 10e-12; + + function setupRoots(roots, min, max) { + var unbound = min === undefined, + minE = min - EPSILON, + maxE = max + EPSILON, + count = 0; + return function(root) { + if (unbound || root > minE && root < maxE) + roots[count++] = root < min ? min : root > max ? max : root; + return count; + }; + } return { - TOLERANCE: 10e-6, - EPSILON: 10e-12, + TOLERANCE: TOLERANCE, + EPSILON: EPSILON, KAPPA: 4 * (sqrt(2) - 1) / 3, isZero: function(val) { - return abs(val) <= Numerical.EPSILON; + return abs(val) <= EPSILON; }, integrate: function(f, a, b, n) { @@ -916,7 +932,7 @@ B = A + a, i = 0, m = (n + 1) >> 1, - sum = n & 1 ? w[i++] * f(B) : 0; + sum = n & 1 ? w[i++] * f(B) : 0; while (i < m) { var Ax = A * x[i]; sum += w[i++] * (f(B + Ax) + f(B - Ax)); @@ -927,10 +943,10 @@ findRoot: function(f, df, x, a, b, n, tolerance) { for (var i = 0; i < n; i++) { var fx = f(x), - dx = fx / df(x); + dx = fx / df(x), + nx = x - dx; if (abs(dx) < tolerance) - return x; - var nx = x - dx; + return nx; if (fx > 0) { b = x; x = nx <= a ? 0.5 * (a + b) : nx; @@ -939,72 +955,52 @@ x = nx >= b ? 0.5 * (a + b) : nx; } } + return x; }, solveQuadratic: function(a, b, c, roots, min, max) { - var epsilon = Numerical.EPSILON, - unbound = min === undefined, - minE = min - epsilon, - maxE = max + epsilon, - count = 0; - - function add(root) { - if (unbound || root > minE && root < maxE) - roots[count++] = root < min ? min : root > max ? max : root; - return count; - } - - if (abs(a) < epsilon) { - if (abs(b) >= epsilon) + var add = setupRoots(roots, min, max); + + if (abs(a) < EPSILON) { + if (abs(b) >= EPSILON) return add(-c / b); - return abs(c) < epsilon ? -1 : 0; + return abs(c) < EPSILON ? -1 : 0; } var p = b / (2 * a); var q = c / a; var p2 = p * p; - if (p2 < q - epsilon) + if (p2 < q - EPSILON) return 0; - var s = p2 > q ? sqrt(p2 - q) : 0; - add (s - p); + var s = p2 > q ? sqrt(p2 - q) : 0, + count = add(s - p); if (s > 0) - add(-s - p); + count = add(-s - p); return count; }, solveCubic: function(a, b, c, d, roots, min, max) { - var epsilon = Numerical.EPSILON; - if (abs(a) < epsilon) + if (abs(a) < EPSILON) return Numerical.solveQuadratic(b, c, d, roots, min, max); - var unbound = min === undefined, - minE = min - epsilon, - maxE = max + epsilon, - count = 0; - - function add(root) { - if (unbound || root > minE && root < maxE) - roots[count++] = root < min ? min : root > max ? max : root; - return count; - } - b /= a; c /= a; d /= a; - var bb = b * b, + var add = setupRoots(roots, min, max), + bb = b * b, p = (bb - 3 * c) / 9, q = (2 * bb * b - 9 * b * c + 27 * d) / 54, ppp = p * p * p, D = q * q - ppp; b /= 3; - if (abs(D) < epsilon) { - if (abs(q) < epsilon) + if (abs(D) < EPSILON) { + if (abs(q) < EPSILON) return add(-b); var sqp = sqrt(p), snq = q > 0 ? 1 : -1; add(-snq * 2 * sqp - b); return add(snq * sqp - b); } - if (D < 0) { + if (D < 0) { var sqp = sqrt(p), phi = Math.acos(q / (sqp * sqp * sqp)) / 3, t = -2 * sqp, @@ -1066,10 +1062,11 @@ }, equals: function(point) { - return point === this || point && (this.x === point.x - && this.y === point.y - || Array.isArray(point) && this.x === point[0] - && this.y === point[1]) || false; + return this === point || point + && (this.x === point.x && this.y === point.y + || Array.isArray(point) + && this.x === point[0] && this.y === point[1]) + || false; }, clone: function() { @@ -1086,50 +1083,8 @@ return [f.number(this.x), f.number(this.y)]; }, - add: function(point) { - point = Point.read(arguments); - return new Point(this.x + point.x, this.y + point.y); - }, - - subtract: function(point) { - point = Point.read(arguments); - return new Point(this.x - point.x, this.y - point.y); - }, - - multiply: function(point) { - point = Point.read(arguments); - return new Point(this.x * point.x, this.y * point.y); - }, - - divide: function(point) { - point = Point.read(arguments); - return new Point(this.x / point.x, this.y / point.y); - }, - - modulo: function(point) { - point = Point.read(arguments); - return new Point(this.x % point.x, this.y % point.y); - }, - - negate: function() { - return new Point(-this.x, -this.y); - }, - - transform: function(matrix) { - return matrix ? matrix._transformPoint(this) : this; - }, - - getDistance: function(point, squared) { - point = Point.read(arguments); - var x = point.x - this.x, - y = point.y - this.y, - d = x * x + y * y; - return squared ? d : Math.sqrt(d); - }, - getLength: function() { - var length = this.x * this.x + this.y * this.y; - return arguments.length && arguments[0] ? length : Math.sqrt(length); + return Math.sqrt(this.x * this.x + this.y * this.y); }, setLength: function(length) { @@ -1148,37 +1103,20 @@ this.y * scale ); } - return this; - }, - - normalize: function(length) { - if (length === undefined) - length = 1; - var current = this.getLength(), - scale = current !== 0 ? length / current : 0, - point = new Point(this.x * scale, this.y * scale); - point._angle = this._angle; - return point; - }, - + }, getAngle: function() { - return this.getAngleInRadians(arguments[0]) * 180 / Math.PI; + return this.getAngleInRadians.apply(this, arguments) * 180 / Math.PI; }, setAngle: function(angle) { - angle = this._angle = angle * Math.PI / 180; - if (!this.isZero()) { - var length = this.getLength(); - this.set( - Math.cos(angle) * length, - Math.sin(angle) * length - ); - } - return this; - }, + this.setAngleInRadians.call(this, angle * Math.PI / 180); + }, + + getAngleInDegrees: '#getAngle', + setAngleInDegrees: '#setAngle', getAngleInRadians: function() { - if (arguments[0] === undefined) { + if (!arguments.length) { return this.isZero() ? this._angle || 0 : this._angle = Math.atan2(this.y, this.x); @@ -1188,24 +1126,54 @@ if (Numerical.isZero(div)) { return NaN; } else { - return Math.acos(this.dot(point) / div); - } - } - }, - - getAngleInDegrees: function() { - return this.getAngle(arguments[0]); + var a = this.dot(point) / div; + return Math.acos(a < -1 ? -1 : a > 1 ? 1 : a); + } + } + }, + + setAngleInRadians: function(angle) { + this._angle = angle; + if (!this.isZero()) { + var length = this.getLength(); + this.set( + Math.cos(angle) * length, + Math.sin(angle) * length + ); + } }, getQuadrant: function() { return this.x >= 0 ? this.y >= 0 ? 1 : 4 : this.y >= 0 ? 2 : 3; - }, - - getDirectedAngle: function(point) { - point = Point.read(arguments); + } +}, { + beans: false, + + getDirectedAngle: function() { + var point = Point.read(arguments); return Math.atan2(this.cross(point), this.dot(point)) * 180 / Math.PI; }, + getDistance: function() { + var point = Point.read(arguments), + x = point.x - this.x, + y = point.y - this.y, + d = x * x + y * y, + squared = Base.read(arguments); + return squared ? d : Math.sqrt(d); + }, + + normalize: function(length) { + if (length === undefined) + length = 1; + var current = this.getLength(), + scale = current !== 0 ? length / current : 0, + point = new Point(this.x * scale, this.y * scale); + if (scale >= 0) + point._angle = this._angle; + return point; + }, + rotate: function(angle, center) { if (angle === 0) return this.clone(); @@ -1215,11 +1183,44 @@ c = Math.cos(angle); point = new Point( point.x * c - point.y * s, - point.y * c + point.x * s + point.x * s + point.y * c ); return center ? point.add(center) : point; }, + transform: function(matrix) { + return matrix ? matrix._transformPoint(this) : this; + }, + + add: function() { + var point = Point.read(arguments); + return new Point(this.x + point.x, this.y + point.y); + }, + + subtract: function() { + var point = Point.read(arguments); + return new Point(this.x - point.x, this.y - point.y); + }, + + multiply: function() { + var point = Point.read(arguments); + return new Point(this.x * point.x, this.y * point.y); + }, + + divide: function() { + var point = Point.read(arguments); + return new Point(this.x / point.x, this.y / point.y); + }, + + modulo: function() { + var point = Point.read(arguments); + return new Point(this.x % point.x, this.y % point.y); + }, + + negate: function() { + return new Point(-this.x, -this.y); + }, + isInside: function(rect) { return rect.contains(this); }, @@ -1229,11 +1230,11 @@ }, isColinear: function(point) { - return this.cross(point) < 0.00001; + return Math.abs(this.cross(point)) < 0.00001; }, isOrthogonal: function(point) { - return this.dot(point) < 0.00001; + return Math.abs(this.dot(point)) < 0.00001; }, isZero: function() { @@ -1244,18 +1245,18 @@ return isNaN(this.x) || isNaN(this.y); }, - dot: function(point) { - point = Point.read(arguments); + dot: function() { + var point = Point.read(arguments); return this.x * point.x + this.y * point.y; }, - cross: function(point) { - point = Point.read(arguments); + cross: function() { + var point = Point.read(arguments); return this.x * point.y - this.y * point.x; }, - project: function(point) { - point = Point.read(arguments); + project: function() { + var point = Point.read(arguments); if (point.isZero()) { return new Point(0, 0); } else { @@ -1269,7 +1270,7 @@ statics: { min: function() { - var point1 = Point.read(arguments); + var point1 = Point.read(arguments), point2 = Point.read(arguments); return new Point( Math.min(point1.x, point2.x), @@ -1278,7 +1279,7 @@ }, max: function() { - var point1 = Point.read(arguments); + var point1 = Point.read(arguments), point2 = Point.read(arguments); return new Point( Math.max(point1.x, point2.x), @@ -1397,28 +1398,28 @@ f.number(this.height)]; }, - add: function(size) { - size = Size.read(arguments); + add: function() { + var size = Size.read(arguments); return new Size(this.width + size.width, this.height + size.height); }, - subtract: function(size) { - size = Size.read(arguments); + subtract: function() { + var size = Size.read(arguments); return new Size(this.width - size.width, this.height - size.height); }, - multiply: function(size) { - size = Size.read(arguments); + multiply: function() { + var size = Size.read(arguments); return new Size(this.width * size.width, this.height * size.height); }, - divide: function(size) { - size = Size.read(arguments); + divide: function() { + var size = Size.read(arguments); return new Size(this.width / size.width, this.height / size.height); }, - modulo: function(size) { - size = Size.read(arguments); + modulo: function() { + var size = Size.read(arguments); return new Size(this.width % size.width, this.height % size.height); }, @@ -1496,6 +1497,7 @@ var Rectangle = Base.extend({ _class: 'Rectangle', _readIndex: true, + beans: true, initialize: function Rectangle(arg0, arg1, arg2, arg3) { var type = typeof arg0, @@ -1550,7 +1552,7 @@ this.width = size.width; this.height = size.height; } - read = arguments._index; + read = arguments.__index; } if (this.__read) this.__read = read; @@ -1569,11 +1571,12 @@ }, equals: function(rect) { - if (Base.isPlainValue(rect)) - rect = Rectangle.read(arguments); - return rect === this - || rect && this.x === rect.x && this.y === rect.y - && this.width === rect.width && this.height === rect.height + var rt = Base.isPlainValue(rect) + ? Rectangle.read(arguments) + : rect; + return rt === this + || rt && this.x === rt.x && this.y === rt.y + && this.width === rt.width && this.height === rt.height || false; }, @@ -1594,24 +1597,24 @@ f.number(this.height)]; }, - getPoint: function() { - return new (arguments[0] ? Point : LinkedPoint) - (this.x, this.y, this, 'setPoint'); - }, - - setPoint: function(point) { - point = Point.read(arguments); + getPoint: function(_dontLink) { + var ctor = _dontLink ? Point : LinkedPoint; + return new ctor(this.x, this.y, this, 'setPoint'); + }, + + setPoint: function() { + var point = Point.read(arguments); this.x = point.x; this.y = point.y; }, - getSize: function() { - return new (arguments[0] ? Size : LinkedSize) - (this.width, this.height, this, 'setSize'); - }, - - setSize: function(size) { - size = Size.read(arguments); + getSize: function(_dontLink) { + var ctor = _dontLink ? Size : LinkedSize; + return new ctor(this.width, this.height, this, 'setSize'); + }, + + setSize: function() { + var size = Size.read(arguments); if (this._fixX) this.x += (this.width - size.width) * this._fixX; if (this._fixY) @@ -1690,20 +1693,24 @@ this._fixY = 0.5; }, - getCenter: function() { - return new (arguments[0] ? Point : LinkedPoint) - (this.getCenterX(), this.getCenterY(), this, 'setCenter'); - }, - - setCenter: function(point) { - point = Point.read(arguments); + getCenter: function(_dontLink) { + var ctor = _dontLink ? Point : LinkedPoint; + return new ctor(this.getCenterX(), this.getCenterY(), this, 'setCenter'); + }, + + setCenter: function() { + var point = Point.read(arguments); this.setCenterX(point.x); this.setCenterY(point.y); return this; }, + getArea: function() { + return this.width * this.height; + }, + isEmpty: function() { - return this.width == 0 || this.height == 0; + return this.width === 0 || this.height === 0; }, contains: function(arg) { @@ -1729,42 +1736,42 @@ && y + rect.height <= this.y + this.height; }, - intersects: function(rect) { - rect = Rectangle.read(arguments); + intersects: function() { + var rect = Rectangle.read(arguments); return rect.x + rect.width > this.x && rect.y + rect.height > this.y && rect.x < this.x + this.width && rect.y < this.y + this.height; }, - touches: function(rect) { - rect = Rectangle.read(arguments); + touches: function() { + var rect = Rectangle.read(arguments); return rect.x + rect.width >= this.x && rect.y + rect.height >= this.y && rect.x <= this.x + this.width && rect.y <= this.y + this.height; }, - intersect: function(rect) { - rect = Rectangle.read(arguments); - var x1 = Math.max(this.x, rect.x), + intersect: function() { + var rect = Rectangle.read(arguments), + x1 = Math.max(this.x, rect.x), y1 = Math.max(this.y, rect.y), x2 = Math.min(this.x + this.width, rect.x + rect.width), y2 = Math.min(this.y + this.height, rect.y + rect.height); return new Rectangle(x1, y1, x2 - x1, y2 - y1); }, - unite: function(rect) { - rect = Rectangle.read(arguments); - var x1 = Math.min(this.x, rect.x), + unite: function() { + var rect = Rectangle.read(arguments), + x1 = Math.min(this.x, rect.x), y1 = Math.min(this.y, rect.y), x2 = Math.max(this.x + this.width, rect.x + rect.width), y2 = Math.max(this.y + this.height, rect.y + rect.height); return new Rectangle(x1, y1, x2 - x1, y2 - y1); }, - include: function(point) { - point = Point.read(arguments); + include: function() { + var point = Point.read(arguments); var x1 = Math.min(this.x, point.x), y1 = Math.min(this.y, point.y), x2 = Math.max(this.x + this.width, point.x), @@ -1772,9 +1779,10 @@ return new Rectangle(x1, y1, x2 - x1, y2 - y1); }, - expand: function(hor, ver) { - if (ver === undefined) - ver = hor; + expand: function() { + var amount = Size.read(arguments), + hor = amount.width, + ver = amount.height; return new Rectangle(this.x - hor / 2, this.y - ver / 2, this.width + hor, this.height + ver); }, @@ -1783,37 +1791,38 @@ return this.expand(this.width * hor - this.width, this.height * (ver === undefined ? hor : ver) - this.height); } -}, new function() { - return Base.each([ - ['Top', 'Left'], ['Top', 'Right'], - ['Bottom', 'Left'], ['Bottom', 'Right'], - ['Left', 'Center'], ['Top', 'Center'], - ['Right', 'Center'], ['Bottom', 'Center'] - ], - function(parts, index) { - var part = parts.join(''); - var xFirst = /^[RL]/.test(part); - if (index >= 4) - parts[1] += xFirst ? 'Y' : 'X'; - var x = parts[xFirst ? 0 : 1], - y = parts[xFirst ? 1 : 0], - getX = 'get' + x, - getY = 'get' + y, - setX = 'set' + x, - setY = 'set' + y, - get = 'get' + part, - set = 'set' + part; - this[get] = function() { - return new (arguments[0] ? Point : LinkedPoint) - (this[getX](), this[getY](), this, set); - }; - this[set] = function(point) { - point = Point.read(arguments); - this[setX](point.x); - this[setY](point.y); - }; - }, {}); -}); +}, Base.each([ + ['Top', 'Left'], ['Top', 'Right'], + ['Bottom', 'Left'], ['Bottom', 'Right'], + ['Left', 'Center'], ['Top', 'Center'], + ['Right', 'Center'], ['Bottom', 'Center'] + ], + function(parts, index) { + var part = parts.join(''); + var xFirst = /^[RL]/.test(part); + if (index >= 4) + parts[1] += xFirst ? 'Y' : 'X'; + var x = parts[xFirst ? 0 : 1], + y = parts[xFirst ? 1 : 0], + getX = 'get' + x, + getY = 'get' + y, + setX = 'set' + x, + setY = 'set' + y, + get = 'get' + part, + set = 'set' + part; + this[get] = function(_dontLink) { + var ctor = _dontLink ? Point : LinkedPoint; + return new ctor(this[getX](), this[getY](), this, set); + }; + this[set] = function() { + var point = Point.read(arguments); + this[setX](point.x); + this[setY](point.y); + }; + }, { + beans: true + } +)); var LinkedRectangle = Rectangle.extend({ initialize: function Rectangle(x, y, width, height, owner, setter) { @@ -1855,7 +1864,7 @@ this[name] = function() { this._dontNotify = true; proto[name].apply(this, arguments); - delete this._dontNotify; + this._dontNotify = false; this._owner[this._setter](this); }; }, { @@ -1916,8 +1925,14 @@ }, _changed: function() { - if (this._owner) - this._owner._changed(5); + var owner = this._owner; + if (owner) { + if (owner._applyMatrix) { + owner.transform(null, true); + } else { + owner._changed(9); + } + } }, clone: function() { @@ -1940,16 +1955,36 @@ f.number(this._ty)].join(', ') + ']]'; }, - reset: function() { + reset: function(_dontNotify) { this._a = this._d = 1; this._c = this._b = this._tx = this._ty = 0; + if (!_dontNotify) + this._changed(); + return this; + }, + + apply: function() { + var owner = this._owner; + if (owner) { + owner.transform(null, true); + return this.isIdentity(); + } + return false; + }, + + translate: function() { + var point = Point.read(arguments), + x = point.x, + y = point.y; + this._tx += x * this._a + y * this._b; + this._ty += x * this._c + y * this._d; this._changed(); return this; }, scale: function() { var scale = Point.read(arguments), - center = Point.read(arguments, 0, 0, { readNull: true }); + center = Point.read(arguments, 0, { readNull: true }); if (center) this.translate(center); this._a *= scale.x; @@ -1962,20 +1997,10 @@ return this; }, - translate: function(point) { - point = Point.read(arguments); - var x = point.x, - y = point.y; - this._tx += x * this._a + y * this._b; - this._ty += x * this._c + y * this._d; - this._changed(); - return this; - }, - - rotate: function(angle, center) { - center = Point.read(arguments, 1); - angle = angle * Math.PI / 180; - var x = center.x, + rotate: function(angle ) { + angle *= Math.PI / 180; + var center = Point.read(arguments, 1), + x = center.x, y = center.y, cos = Math.cos(angle), sin = Math.sin(angle), @@ -1996,59 +2021,106 @@ }, shear: function() { - var point = Point.read(arguments), - center = Point.read(arguments, 0, 0, { readNull: true }); + var shear = Point.read(arguments), + center = Point.read(arguments, 0, { readNull: true }); if (center) this.translate(center); var a = this._a, c = this._c; - this._a += point.y * this._b; - this._c += point.y * this._d; - this._b += point.x * a; - this._d += point.x * c; + this._a += shear.y * this._b; + this._c += shear.y * this._d; + this._b += shear.x * a; + this._d += shear.x * c; if (center) this.translate(center.negate()); this._changed(); return this; }, + skew: function() { + var skew = Point.read(arguments), + center = Point.read(arguments, 0, { readNull: true }), + toRadians = Math.PI / 180, + shear = new Point(Math.tan(skew.x * toRadians), + Math.tan(skew.y * toRadians)); + return this.shear(shear, center); + }, + concatenate: function(mx) { - var a = this._a, - b = this._b, - c = this._c, - d = this._d; - this._a = mx._a * a + mx._c * b; - this._b = mx._b * a + mx._d * b; - this._c = mx._a * c + mx._c * d; - this._d = mx._b * c + mx._d * d; - this._tx += mx._tx * a + mx._ty * b; - this._ty += mx._tx * c + mx._ty * d; + var a1 = this._a, + b1 = this._b, + c1 = this._c, + d1 = this._d, + a2 = mx._a, + b2 = mx._b, + c2 = mx._c, + d2 = mx._d, + tx2 = mx._tx, + ty2 = mx._ty; + this._a = a2 * a1 + c2 * b1; + this._b = b2 * a1 + d2 * b1; + this._c = a2 * c1 + c2 * d1; + this._d = b2 * c1 + d2 * d1; + this._tx += tx2 * a1 + ty2 * b1; + this._ty += tx2 * c1 + ty2 * d1; this._changed(); return this; }, preConcatenate: function(mx) { - var a = this._a, - b = this._b, - c = this._c, - d = this._d, - tx = this._tx, - ty = this._ty; - this._a = mx._a * a + mx._b * c; - this._b = mx._a * b + mx._b * d; - this._c = mx._c * a + mx._d * c; - this._d = mx._c * b + mx._d * d; - this._tx = mx._a * tx + mx._b * ty + mx._tx; - this._ty = mx._c * tx + mx._d * ty + mx._ty; + var a1 = this._a, + b1 = this._b, + c1 = this._c, + d1 = this._d, + tx1 = this._tx, + ty1 = this._ty, + a2 = mx._a, + b2 = mx._b, + c2 = mx._c, + d2 = mx._d, + tx2 = mx._tx, + ty2 = mx._ty; + this._a = a2 * a1 + b2 * c1; + this._b = a2 * b1 + b2 * d1; + this._c = c2 * a1 + d2 * c1; + this._d = c2 * b1 + d2 * d1; + this._tx = a2 * tx1 + b2 * ty1 + tx2; + this._ty = c2 * tx1 + d2 * ty1 + ty2; this._changed(); return this; }, + chain: function(mx) { + var a1 = this._a, + b1 = this._b, + c1 = this._c, + d1 = this._d, + tx1 = this._tx, + ty1 = this._ty, + a2 = mx._a, + b2 = mx._b, + c2 = mx._c, + d2 = mx._d, + tx2 = mx._tx, + ty2 = mx._ty; + return new Matrix( + a2 * a1 + c2 * b1, + a2 * c1 + c2 * d1, + b2 * a1 + d2 * b1, + b2 * c1 + d2 * d1, + tx1 + tx2 * a1 + ty2 * b1, + ty1 + tx2 * c1 + ty2 * d1); + }, + isIdentity: function() { return this._a === 1 && this._c === 0 && this._b === 0 && this._d === 1 && this._tx === 0 && this._ty === 0; }, + orNullIfIdentity: function() { + return this.isIdentity() ? null : this; + }, + isInvertible: function() { return !!this._getDeterminant(); }, @@ -2057,10 +2129,10 @@ return !this._getDeterminant(); }, - transform: function( src, srcOffset, dst, dstOffset, count) { - return arguments.length < 5 + transform: function( src, dst, count) { + return arguments.length < 3 ? this._transformPoint(Point.read(arguments)) - : this._transformCoordinates(src, srcOffset, dst, dstOffset, count); + : this._transformCoordinates(src, dst, count); }, _transformPoint: function(point, dest, _dontNotify) { @@ -2075,10 +2147,10 @@ ); }, - _transformCoordinates: function(src, srcOffset, dst, dstOffset, count) { - var i = srcOffset, - j = dstOffset, - max = i + 2 * count; + _transformCoordinates: function(src, dst, count) { + var i = 0, + j = 0, + max = 2 * count; while (i < max) { var x = src[i++], y = src[i++]; @@ -2094,7 +2166,7 @@ x2 = x1 + rect.width, y2 = y1 + rect.height, coords = [ x1, y1, x2, y1, x2, y2, x1, y2 ]; - return this._transformCoordinates(coords, 0, coords, 0, 4); + return this._transformCoordinates(coords, coords, 4); }, _transformBounds: function(bounds, dest, _dontNotify) { @@ -2167,7 +2239,6 @@ } return { - translation: this.getTranslation(), scaling: new Point(scaleX, scaleY), rotation: -Math.atan2(b, a) * 180 / Math.PI, shearing: shear @@ -2182,36 +2253,14 @@ return new Point(this._tx, this._ty); }, - setTranslation: function() { - var point = Point.read(arguments); - this._tx = point.x; - this._ty = point.y; - this._changed(); - }, - getScaling: function() { return (this.decompose() || {}).scaling; }, - setScaling: function() { - var scaling = this.getScaling(); - if (scaling != null) { - var scale = Point.read(arguments); - (this._owner || this).scale( - scale.x / scaling.x, scale.y / scaling.y); - } - }, - getRotation: function() { return (this.decompose() || {}).rotation; }, - setRotation: function(angle) { - var rotation = this.getRotation(); - if (rotation != null) - (this._owner || this).rotate(angle - rotation); - }, - inverted: function() { var det = this._getDeterminant(); return det && new Matrix( @@ -2230,25 +2279,17 @@ applyToContext: function(ctx) { ctx.transform(this._a, this._c, this._b, this._d, this._tx, this._ty); } -}, new function() { - return Base.each({ - scaleX: '_a', - scaleY: '_d', - translateX: '_tx', - translateY: '_ty', - shearX: '_b', - shearY: '_c' - }, function(prop, name) { - name = Base.capitalize(name); - this['get' + name] = function() { - return this[prop]; - }; - this['set' + name] = function(value) { - this[prop] = value; - this._changed(); - }; - }, {}); -}); +}, Base.each(['a', 'c', 'b', 'd', 'tx', 'ty'], function(name) { + var part = Base.capitalize(name), + prop = '_' + name; + this['get' + part] = function() { + return this[prop]; + }; + this['set' + part] = function(value) { + this[prop] = value; + this._changed(); + }; +}, {})); var Line = Base.extend({ _class: 'Line', @@ -2335,9 +2376,9 @@ } var v2x = x - px, v2y = y - py, - ccw = v2x * vy - v2y * vx; + ccw = v2x * vy - v2y * vx; if (ccw === 0) { - ccw = v2x * vx + v2y * vy; + ccw = v2x * vx + v2y * vy; if (ccw > 0) { v2x -= vx; v2y -= vy; @@ -2354,8 +2395,8 @@ vx -= px; vy -= py; } - var m = vy / vx, - b = py - m * px; + var m = vy / vx, + b = py - m * px; return (y - (m * x) - b) / Math.sqrt(m * m + 1); } } @@ -2366,18 +2407,17 @@ _list: 'projects', _reference: 'project', - initialize: function Project(view) { + initialize: function Project(element) { PaperScopeItem.call(this, true); this.layers = []; this.symbols = []; - this._currentStyle = new Style(); + this._currentStyle = new Style(null, null, this); this.activeLayer = new Layer(); - if (view) - this.view = view instanceof View ? view : View.create(view); + this._view = View.create(this, + element || CanvasProvider.getCanvas(1, 1)); this._selectedItems = {}; this._selectedItemCount = 0; - this._drawCount = 0; - this.options = {}; + this._updateVersion = 0; }, _serialize: function(options, dictionary) { @@ -2398,11 +2438,15 @@ remove: function remove() { if (!remove.base.call(this)) return false; - if (this.view) - this.view.remove(); + if (this._view) + this._view.remove(); return true; }, + getView: function() { + return this._view; + }, + getCurrentStyle: function() { return this._currentStyle; }, @@ -2422,7 +2466,7 @@ this.activeLayer = child; } else if (child instanceof Item) { (this.activeLayer - || this.addChild(new Layer({ insert: false }))).addChild(child); + || this.addChild(new Layer(Item.NO_INSERT))).addChild(child); } else { child = null; } @@ -2439,6 +2483,10 @@ return items; }, + getOptions: function() { + return this._scope.settings; + }, + _updateSelection: function(item) { var id = item._id, selectedItems = this._selectedItems; @@ -2465,39 +2513,23 @@ selectedItems[i].setFullySelected(false); }, - hitTest: function(point, options) { - point = Point.read(arguments); - options = HitResult.getOptions(Base.read(arguments)); + hitTest: function() { + var point = Point.read(arguments), + options = HitResult.getOptions(Base.read(arguments)); for (var i = this.layers.length - 1; i >= 0; i--) { - var res = this.layers[i].hitTest(point, options); + var res = this.layers[i]._hitTest(point, options); if (res) return res; } return null; - } -}, new function() { - function getItems(project, match, list) { - var layers = project.layers, - items = list && []; - for (var i = 0, l = layers.length; i < l; i++) { - var res = layers[i][list ? 'getItems' : 'getItem'](match); - if (list) { - items.push.apply(items, res); - } else if (res) - return res; - } - return list ? items : null; - } - - return { - getItems: function(match) { - return getItems(this, match, true); - }, - - getItem: function(match) { - return getItems(this, match, false); - } - }; -}, { + }, + + getItems: function(match) { + return Item._getItems(this.layers, match, true); + }, + + getItem: function(match) { + return Item._getItems(this.layers, match, false); + }, importJSON: function(json) { this.activate(); @@ -2505,51 +2537,29 @@ return Base.importJSON(json, layer && layer.isEmpty() && layer); }, - draw: function(ctx, matrix, ratio) { - this._drawCount++; + draw: function(ctx, matrix, pixelRatio) { + this._updateVersion++; ctx.save(); matrix.applyToContext(ctx); var param = new Base({ offset: new Point(0, 0), - ratio: ratio, - transforms: [matrix], - trackTransforms: true + pixelRatio: pixelRatio, + viewMatrix: matrix.isIdentity() ? null : matrix, + matrices: [new Matrix()], + updateMatrix: true }); - for (var i = 0, l = this.layers.length; i < l; i++) - this.layers[i].draw(ctx, param); + for (var i = 0, layers = this.layers, l = layers.length; i < l; i++) + layers[i].draw(ctx, param); ctx.restore(); if (this._selectedItemCount > 0) { ctx.save(); ctx.strokeWidth = 1; - for (var id in this._selectedItems) { - var item = this._selectedItems[id]; - if (item._drawCount === this._drawCount - && (item._drawSelected || item._boundsSelected)) { - var color = item.getSelectedColor() - || item.getLayer().getSelectedColor(); - ctx.strokeStyle = ctx.fillStyle = color - ? color.toCanvasStyle(ctx) : '#009dec'; - var mx = item._globalMatrix; - if (item._drawSelected) - item._drawSelected(ctx, mx); - if (item._boundsSelected) { - var coords = mx._transformCorners( - item._getBounds('getBounds')); - ctx.beginPath(); - for (var i = 0; i < 8; i++) - ctx[i === 0 ? 'moveTo' : 'lineTo']( - coords[i], coords[++i]); - ctx.closePath(); - ctx.stroke(); - for (var i = 0; i < 8; i++) { - ctx.beginPath(); - ctx.rect(coords[i] - 2, coords[++i] - 2, 4, 4); - ctx.fill(); - } - } - } - } + var items = this._selectedItems, + size = this._scope.settings.handleSize, + version = this._updateVersion; + for (var id in items) + items[id]._drawSelection(ctx, matrix, size, items, version); ctx.restore(); } } @@ -2564,7 +2574,6 @@ this.project.symbols.push(this); if (item) this.setDefinition(item, dontCenter); - this._instances = {}; }, _serialize: function(options, dictionary) { @@ -2575,27 +2584,30 @@ }, _changed: function(flags) { - Base.each(this._instances, function(item) { - item._changed(flags); - }); + if (flags & 8) { + Item._clearBoundsCache(this); + } + if (flags & 1) { + this.project._needsUpdate = true; + } }, getDefinition: function() { return this._definition; }, - setDefinition: function(item ) { + setDefinition: function(item, _dontCenter) { if (item._parentSymbol) item = item.clone(); if (this._definition) - delete this._definition._parentSymbol; + this._definition._parentSymbol = null; this._definition = item; item.remove(); item.setSelected(false); - if (!arguments[1]) + if (!_dontCenter) item.setPosition(new Point()); item._parentSymbol = this; - this._changed(5); + this._changed(9); }, place: function(position) { @@ -2613,21 +2625,22 @@ if (src._serializeFields) src._serializeFields = new Base( this.prototype._serializeFields, src._serializeFields); - var res = extend.base.apply(this, arguments), - proto = res.prototype, - name = proto._class; - if (name) - proto._type = Base.hyphenate(name); - return res; - } + return extend.base.apply(this, arguments); + }, + + NO_INSERT: { insert: false } }, _class: 'Item', - _transformContent: true, + _applyMatrix: true, + _canApplyMatrix: true, _boundsSelected: false, + _selectChildren: false, _serializeFields: { name: null, + applyMatrix: null, matrix: new Matrix(), + pivot: null, locked: false, visible: true, blendMode: 'normal', @@ -2642,21 +2655,29 @@ }, _initialize: function(props, point) { - this._id = Item._id = (Item._id || 0) + 1; - if (!this._project) { - var project = paper.project; - if (props && props.insert === false) { - this._setProject(project); - } else { - (project.activeLayer || new Layer()).addChild(this); - } - } - this._style = new Style(this._project._currentStyle, this); - var matrix = this._matrix = new Matrix(); + var hasProps = props && Base.isPlainObject(props), + internal = hasProps && props.internal === true, + matrix = this._matrix = new Matrix(), + project = paper.project; + if (!internal) + this._id = Item._id = (Item._id || 0) + 1; + this._applyMatrix = this._canApplyMatrix && paper.settings.applyMatrix; if (point) matrix.translate(point); matrix._owner = this; - return props ? this._set(props, { insert: true }) : true; + this._style = new Style(project._currentStyle, this, project); + if (!this._project) { + if (internal || hasProps && props.insert === false) { + this._setProject(project); + } else if (hasProps && props.parent) { + this.setParent(props.parent); + } else { + (project.activeLayer || new Layer()).addChild(this); + } + } + if (hasProps && props !== Item.NO_INSERT) + this._set(props, { insert: true, parent: true }, true); + return hasProps; }, _events: new function() { @@ -2684,7 +2705,7 @@ var mouseEvent = { install: function(type) { - var counters = this._project.view._eventCounters; + var counters = this.getView()._eventCounters; if (counters) { for (var key in mouseFlags) { counters[key] = (counters[key] || 0) @@ -2693,7 +2714,7 @@ } }, uninstall: function(type) { - var counters = this._project.view._eventCounters; + var counters = this.getView()._eventCounters; if (counters) { for (var key in mouseFlags) counters[key] -= mouseFlags[key][type] || 0; @@ -2721,7 +2742,7 @@ }, _animateItem: function(animate) { - this._project.view._animateItem(this, animate); + this.getView()._animateItem(this, animate); }, _serialize: function(options, dictionary) { @@ -2746,24 +2767,23 @@ }, _changed: function(flags) { - var parent = this._parent, - project = this._project, - symbol = this._parentSymbol; - this._drawCount = null; - if (flags & 4) { - delete this._bounds; - delete this._position; - } - if (parent && (flags - & (4 | 8))) { - parent._clearBoundsCache(); + var symbol = this._parentSymbol, + cacheParent = this._parent || symbol, + project = this._project; + if (flags & 8) { + this._bounds = this._position = this._decomposed = + this._globalMatrix = this._currentPath = undefined; + } + if (cacheParent + && (flags & 40)) { + Item._clearBoundsCache(cacheParent); } if (flags & 2) { - this._clearBoundsCache(); + Item._clearBoundsCache(this); } if (project) { if (flags & 1) { - project._needsRedraw = true; + project._needsUpdate = true; } if (project._changes) { var entry = project._changesById[this._id]; @@ -2790,8 +2810,8 @@ return this._id; }, - getType: function() { - return this._type; + getClassName: function() { + return this._class; }, getName: function() { @@ -2805,9 +2825,10 @@ if (name === (+name) + '') throw new Error( 'Names consisting only of numbers are not supported.'); - if (name && this._parent) { - var children = this._parent._children, - namedChildren = this._parent._namedChildren, + var parent = this._parent; + if (name && parent) { + var children = parent._children, + namedChildren = parent._namedChildren, orig = name, i = 1; while (unique && children[name]) @@ -2816,7 +2837,7 @@ children[name] = this; } this._name = name || undefined; - this._changed(32); + this._changed(128); }, getStyle: function() { @@ -2825,18 +2846,6 @@ setStyle: function(style) { this.getStyle().set(style); - }, - - hasFill: function() { - return this.getStyle().hasFill(); - }, - - hasStroke: function() { - return this.getStyle().hasStroke(); - }, - - hasShadow: function() { - return this.getStyle().hasShadow(); } }, Base.each(['locked', 'visible', 'blendMode', 'opacity', 'guide'], function(name) { @@ -2849,10 +2858,12 @@ if (value != this[name]) { this[name] = value; this._changed(name === '_locked' - ? 32 : 33); + ? 128 : 129); } }; -}, {}), { + }, +{}), { + beans: true, _locked: false, @@ -2865,32 +2876,35 @@ _guide: false, isSelected: function() { - if (this._children) { - for (var i = 0, l = this._children.length; i < l; i++) - if (this._children[i].isSelected()) + if (this._selectChildren) { + var children = this._children; + for (var i = 0, l = children.length; i < l; i++) + if (children[i].isSelected()) return true; } return this._selected; }, - setSelected: function(selected ) { - if (this._children && !arguments[1]) { - for (var i = 0, l = this._children.length; i < l; i++) - this._children[i].setSelected(selected); - } - if ((selected = !!selected) != this._selected) { + setSelected: function(selected, noChildren) { + if (!noChildren && this._selectChildren) { + var children = this._children; + for (var i = 0, l = children.length; i < l; i++) + children[i].setSelected(selected); + } + if ((selected = !!selected) ^ this._selected) { this._selected = selected; this._project._updateSelection(this); - this._changed(33); + this._changed(129); } }, _selected: false, isFullySelected: function() { - if (this._children && this._selected) { - for (var i = 0, l = this._children.length; i < l; i++) - if (!this._children[i].isFullySelected()) + var children = this._children; + if (children && this._selected) { + for (var i = 0, l = children.length; i < l; i++) + if (!children[i].isFullySelected()) return false; return true; } @@ -2898,9 +2912,10 @@ }, setFullySelected: function(selected) { - if (this._children) { - for (var i = 0, l = this._children.length; i < l; i++) - this._children[i].setFullySelected(selected); + var children = this._children; + if (children) { + for (var i = 0, l = children.length; i < l; i++) + children[i].setFullySelected(selected); } this.setSelected(selected, true); }, @@ -2916,9 +2931,9 @@ this.setFillColor(null); this.setStrokeColor(null); } - this._changed(33); + this._changed(129); if (this._parent) - this._parent._changed(256); + this._parent._changed(1024); } }, @@ -2931,73 +2946,64 @@ }, setData: function(data) { - this._data = data; - }, - - getPosition: function() { - var pos = this._position - || (this._position = this.getBounds().getCenter(true)); - return new (arguments[0] ? Point : LinkedPoint) - (pos.x, pos.y, this, 'setPosition'); + this._data = data; + }, + + getPosition: function(_dontLink) { + var position = this._position, + ctor = _dontLink ? Point : LinkedPoint; + if (!position) { + var pivot = this._pivot; + position = this._position = pivot + ? this._matrix._transformPoint(pivot) + : this.getBounds().getCenter(true); + } + return new ctor(position.x, position.y, this, 'setPosition'); }, setPosition: function() { this.translate(Point.read(arguments).subtract(this.getPosition(true))); - } -}, Base.each(['getBounds', 'getStrokeBounds', 'getHandleBounds', 'getRoughBounds'], - function(name) { - this[name] = function() { - var getter = this._boundsGetter, - bounds = this._getCachedBounds(typeof getter == 'string' - ? getter : getter && getter[name] || name, arguments[0]); - return name === 'getBounds' + }, + + getPivot: function(_dontLink) { + var pivot = this._pivot; + if (pivot) { + var ctor = _dontLink ? Point : LinkedPoint; + pivot = new ctor(pivot.x, pivot.y, this, 'setPivot'); + } + return pivot; + }, + + setPivot: function() { + this._pivot = Point.read(arguments); + this._position = undefined; + }, + + _pivot: null, + + getRegistration: '#getPivot', + setRegistration: '#setPivot' +}, Base.each(['bounds', 'strokeBounds', 'handleBounds', 'roughBounds', + 'internalBounds', 'internalRoughBounds'], + function(key) { + var getter = 'get' + Base.capitalize(key), + match = key.match(/^internal(.*)$/), + internalGetter = match ? 'get' + match[1] : null; + this[getter] = function(_matrix) { + var boundsGetter = this._boundsGetter, + name = !internalGetter && (typeof boundsGetter === 'string' + ? boundsGetter : boundsGetter && boundsGetter[getter]) + || getter, + bounds = this._getCachedBounds(name, _matrix, this, + internalGetter); + return key === 'bounds' ? new LinkedRectangle(bounds.x, bounds.y, bounds.width, - bounds.height, this, 'setBounds') + bounds.height, this, 'setBounds') : bounds; }; }, { - _getCachedBounds: function(getter, matrix, cacheItem) { - var cache = (!matrix || matrix.equals(this._matrix)) && getter; - if (cacheItem && this._parent) { - var id = cacheItem._id, - ref = this._parent._boundsCache - = this._parent._boundsCache || { - ids: {}, - list: [] - }; - if (!ref.ids[id]) { - ref.list.push(cacheItem); - ref.ids[id] = cacheItem; - } - } - if (cache && this._bounds && this._bounds[cache]) - return this._bounds[cache].clone(); - var identity = this._matrix.isIdentity(); - matrix = !matrix || matrix.isIdentity() - ? identity ? null : this._matrix - : identity ? matrix : matrix.clone().concatenate(this._matrix); - var bounds = this._getBounds(getter, matrix, cache ? this : cacheItem); - if (cache) { - if (!this._bounds) - this._bounds = {}; - this._bounds[cache] = bounds.clone(); - } - return bounds; - }, - - _clearBoundsCache: function() { - if (this._boundsCache) { - for (var i = 0, list = this._boundsCache.list, l = list.length; - i < l; i++) { - var item = list[i]; - delete item._bounds; - if (item != this && item._boundsCache) - item._clearBoundsCache(); - } - delete this._boundsCache; - } - }, + beans: true, _getBounds: function(getter, matrix, cacheItem) { var children = this._children; @@ -3022,9 +3028,9 @@ : new Rectangle(); }, - setBounds: function(rect) { - rect = Rectangle.read(arguments); - var bounds = this.getBounds(), + setBounds: function() { + var rect = Rectangle.read(arguments), + bounds = this.getBounds(), matrix = new Matrix(), center = rect.getCenter(); matrix.translate(center); @@ -3036,53 +3042,164 @@ center = bounds.getCenter(); matrix.translate(-center.x, -center.y); this.transform(matrix); + }, + + _getCachedBounds: function(getter, matrix, cacheItem, internalGetter) { + matrix = matrix && matrix.orNullIfIdentity(); + var _matrix = internalGetter ? null : this._matrix.orNullIfIdentity(), + cache = (!matrix || matrix.equals(_matrix)) && getter; + var cacheParent = this._parent || this._parentSymbol; + if (cacheParent) { + var id = cacheItem._id, + ref = cacheParent._boundsCache = cacheParent._boundsCache || { + ids: {}, + list: [] + }; + if (!ref.ids[id]) { + ref.list.push(cacheItem); + ref.ids[id] = cacheItem; + } + } + if (cache && this._bounds && this._bounds[cache]) + return this._bounds[cache].clone(); + matrix = !matrix + ? _matrix + : _matrix + ? matrix.chain(_matrix) + : matrix; + var bounds = this._getBounds(internalGetter || getter, matrix, + cacheItem); + if (cache) { + if (!this._bounds) + this._bounds = {}; + var cached = this._bounds[cache] = bounds.clone(); + cached._internal = !!internalGetter; + } + return bounds; + }, + + statics: { + _clearBoundsCache: function(item) { + var cache = item._boundsCache; + if (cache) { + item._bounds = item._position = item._boundsCache = undefined; + for (var i = 0, list = cache.list, l = list.length; i < l; i++) { + var other = list[i]; + if (other !== item) { + other._bounds = other._position = undefined; + if (other._boundsCache) + Item._clearBoundsCache(other); + } + } + } + } } }), { + beans: true, + + _decompose: function() { + return this._decomposed = this._matrix.decompose(); + }, + + getRotation: function() { + var decomposed = this._decomposed || this._decompose(); + return decomposed && decomposed.rotation; + }, + + setRotation: function(rotation) { + var current = this.getRotation(); + if (current != null && rotation != null) { + var decomposed = this._decomposed; + this.rotate(rotation - current); + decomposed.rotation = rotation; + this._decomposed = decomposed; + } + }, + + getScaling: function() { + var decomposed = this._decomposed || this._decompose(); + return decomposed && decomposed.scaling; + }, + + setScaling: function() { + var current = this.getScaling(); + if (current != null) { + var scaling = Point.read(arguments, 0, { clone: true }), + decomposed = this._decomposed; + this.scale(scaling.x / current.x, scaling.y / current.y); + decomposed.scaling = scaling; + this._decomposed = decomposed; + } + }, + getMatrix: function() { return this._matrix; }, setMatrix: function(matrix) { this._matrix.initialize(matrix); - if (this._transformContent) - this.applyMatrix(true); - this._changed(5); - }, - - getGlobalMatrix: function() { - return this._drawCount === this._project._drawCount - && this._globalMatrix || null; - }, - - getTransformContent: function() { - return this._transformContent; - }, - - setTransformContent: function(transform) { - this._transformContent = transform; - if (transform) - this.applyMatrix(); - }, - + if (this._applyMatrix) { + this.transform(null, true); + } else { + this._changed(9); + } + }, + + getGlobalMatrix: function(_dontClone) { + var matrix = this._globalMatrix, + updateVersion = this._project._updateVersion; + if (matrix && matrix._updateVersion !== updateVersion) + matrix = null; + if (!matrix) { + matrix = this._globalMatrix = this._matrix.clone(); + var parent = this._parent; + if (parent) + matrix.preConcatenate(parent.getGlobalMatrix(true)); + matrix._updateVersion = updateVersion; + } + return _dontClone ? matrix : matrix.clone(); + }, + + getApplyMatrix: function() { + return this._applyMatrix; + }, + + setApplyMatrix: function(transform) { + if (this._applyMatrix = this._canApplyMatrix && !!transform) + this.transform(null, true); + }, + + getTransformContent: '#getApplyMatrix', + setTransformContent: '#setApplyMatrix', +}, { getProject: function() { return this._project; }, - _setProject: function(project) { - if (this._project != project) { - var hasOnFrame = this.responds('frame'); - if (hasOnFrame) - this._animateItem(false); + _setProject: function(project, installEvents) { + if (this._project !== project) { + if (this._project) + this._installEvents(false); this._project = project; - if (hasOnFrame) - this._animateItem(true); - if (this._children) { - for (var i = 0, l = this._children.length; i < l; i++) { - this._children[i]._setProject(project); - } - } - } + var children = this._children; + for (var i = 0, l = children && children.length; i < l; i++) + children[i]._setProject(project); + installEvents = true; + } + if (installEvents) + this._installEvents(true); + }, + + getView: function() { + return this._project.getView(); + }, + + _installEvents: function _installEvents(install) { + _installEvents.base.call(this, install); + var children = this._children; + for (var i = 0, l = children && children.length; i < l; i++) + children[i]._installEvents(install); }, getLayer: function() { @@ -3132,13 +3249,9 @@ return this._index; }, - isInserted: function() { - return this._parent ? this._parent.isInserted() : false; - }, - equals: function(item) { return item === this || item && this._class === item._class - && this._style.equals(item._style) + && this._style.equals(item._style) && this._matrix.equals(item._matrix) && this._locked === item._locked && this._visible === item._visible @@ -3155,7 +3268,7 @@ }, clone: function(insert) { - return this._clone(new this.constructor({ insert: false }), insert); + return this._clone(new this.constructor(Item.NO_INSERT), insert); }, _clone: function(copy, insert) { @@ -3167,7 +3280,7 @@ if (insert || insert === undefined) copy.insertAbove(this); var keys = ['_locked', '_visible', '_blendMode', '_opacity', - '_clipMask', '_guide']; + '_clipMask', '_guide', '_applyMatrix']; for (var i = 0, l = keys.length; i < l; i++) { var key = keys[i]; if (this.hasOwnProperty(key)) @@ -3187,22 +3300,21 @@ rasterize: function(resolution) { var bounds = this.getStrokeBounds(), - scale = (resolution || 72) / 72, + scale = (resolution || this.getView().getResolution()) / 72, topLeft = bounds.getTopLeft().floor(), - bottomRight = bounds.getBottomRight().ceil() + bottomRight = bounds.getBottomRight().ceil(), size = new Size(bottomRight.subtract(topLeft)), - canvas = CanvasProvider.getCanvas(size), + canvas = CanvasProvider.getCanvas(size.multiply(scale)), ctx = canvas.getContext('2d'), matrix = new Matrix().scale(scale).translate(topLeft.negate()); ctx.save(); matrix.applyToContext(ctx); - this.draw(ctx, new Base({ transforms: [matrix] })); + this.draw(ctx, new Base({ matrices: [matrix] })); ctx.restore(); - var raster = new Raster({ - canvas: canvas, - insert: false - }); - raster.setPosition(topLeft.add(size.divide(2))); + var raster = new Raster(Item.NO_INSERT); + raster.setCanvas(canvas); + raster.transform(new Matrix().translate(topLeft.add(size.divide(2))) + .scale(1 / scale)); raster.insertAbove(this); return raster; }, @@ -3220,33 +3332,52 @@ } return false; } - return point.isInside(this._getBounds('getBounds')); - }, - - hitTest: function(point, options) { - point = Point.read(arguments); - options = HitResult.getOptions(Base.read(arguments)); - - if (this._locked || !this._visible || this._guide && !options.guides) + return point.isInside(this.getInternalBounds()); + }, + + hitTest: function() { + return this._hitTest( + Point.read(arguments), + HitResult.getOptions(Base.read(arguments))); + }, + + _hitTest: function(point, options) { + if (this._locked || !this._visible || this._guide && !options.guides + || this.isEmpty()) return null; - if (!this._children && !this.getRoughBounds() - .expand(2 * options.tolerance)._containsPoint(point)) + var matrix = this._matrix, + parentTotalMatrix = options._totalMatrix, + view = this.getView(), + totalMatrix = options._totalMatrix = parentTotalMatrix + ? parentTotalMatrix.chain(matrix) + : this.getGlobalMatrix().preConcatenate(view._matrix), + tolerancePadding = options._tolerancePadding = new Size( + Path._getPenPadding(1, totalMatrix.inverted()) + ).multiply( + Math.max(options.tolerance, 0.00001) + ); + point = matrix._inverseTransform(point); + + if (!this._children && !this.getInternalRoughBounds() + .expand(tolerancePadding.multiply(2))._containsPoint(point)) return null; - point = this._matrix._inverseTransform(point); - - var that = this, + var checkSelf = !(options.guides && !this._guide + || options.selected && !this._selected + || options.type && options.type !== Base.hyphenate(this._class) + || options.class && !(this instanceof options.class)), + that = this, res; + function checkBounds(type, part) { var pt = bounds['get' + part](); - if (point.getDistance(pt) < options.tolerance) + if (point.subtract(pt).divide(tolerancePadding).length <= 1) return new HitResult(type, that, { name: Base.hyphenate(part), point: pt }); } - if ((options.center || options.bounds) && - !(this instanceof Layer && !this._parent)) { - var bounds = this._getBounds('getBounds'); + if (checkSelf && (options.center || options.bounds) && this._parent) { + var bounds = this.getInternalBounds(); if (options.center) res = checkBounds('center', 'Center'); if (!res && options.bounds) { @@ -3259,24 +3390,27 @@ } } - if ((res || (res = this._children || !(options.guides && !this._guide - || options.selected && !this._selected) - ? this._hitTest(point, options) : null)) - && res.point) { - res.point = that._matrix.transform(res.point); - } + var children = !res && this._children; + if (children) { + var opts = this._getChildHitTestOptions(options); + for (var i = children.length - 1; i >= 0 && !res; i--) + res = children[i]._hitTest(point, opts); + } + if (!res && checkSelf) + res = this._hitTestSelf(point, options); + if (res && res.point) + res.point = matrix.transform(res.point); + options._totalMatrix = parentTotalMatrix; return res; }, - _hitTest: function(point, options) { - var children = this._children; - if (children) { - for (var i = children.length - 1, res; i >= 0; i--) - if (res = children[i].hitTest(point, options)) - return res; - } else if (options.fill && this.hasFill() && this._contains(point)) { + _getChildHitTestOptions: function(options) { + return options; + }, + + _hitTestSelf: function(point, options) { + if (options.fill && this.hasFill() && this._contains(point)) return new HitResult('fill', this); - } }, matches: function(match) { @@ -3299,7 +3433,12 @@ if (match.hasOwnProperty(key)) { var value = this[key], compare = match[key]; - if (compare instanceof RegExp) { + if (value === undefined && key === 'type') + value = Base.hyphenate(this._class); + if (/^(constructor|class)$/.test(key)) { + if (!(this instanceof compare)) + return false; + } else if (compare instanceof RegExp) { if (!compare.test(value)) return false; } else if (typeof compare === 'function') { @@ -3314,39 +3453,38 @@ } } return true; - } -}, new function() { - function getItems(item, match, list) { - var children = item._children, - items = list && []; - for (var i = 0, l = children && children.length; i < l; i++) { - var child = children[i]; - if (child.matches(match)) { + }, + + getItems: function(match) { + return Item._getItems(this._children, match, true); + }, + + getItem: function(match) { + return Item._getItems(this._children, match, false); + }, + + statics: { + _getItems: function _getItems(children, match, list) { + var items = list && []; + for (var i = 0, l = children && children.length; i < l; i++) { + var child = children[i]; + if (child.matches(match)) { + if (list) { + items.push(child); + } else { + return child; + } + } + var res = _getItems(child._children, match, list); if (list) { - items.push(child); - } else { - return child; - } - } - var res = getItems(child, match, list); - if (list) { - items.push.apply(items, res); - } else if (res) { - return res; - } - } - return list ? items : null; - } - - return { - getItems: function(match) { - return getItems(this, match, true); - }, - - getItem: function(match) { - return getItems(this, match, false); - } - }; + items.push.apply(items, res); + } else if (res) { + return res; + } + } + return list ? items : null; + } + } }, { importJSON: function(json) { @@ -3369,26 +3507,31 @@ return this.insertChildren(this._children.length, items, _preserve); }, - insertChildren: function(index, items, _preserve, _type) { + insertChildren: function(index, items, _preserve, _proto) { var children = this._children; if (children && items && items.length > 0) { items = Array.prototype.slice.apply(items); for (var i = items.length - 1; i >= 0; i--) { var item = items[i]; - if (_type && item._type !== _type) + if (_proto && !(item instanceof _proto)) { items.splice(i, 1); - else - item._remove(true); + } else { + item._remove(false, true); + } } Base.splice(children, items, index, 0); + var project = this._project, + notifySelf = project && project._changes; for (var i = 0, l = items.length; i < l; i++) { var item = items[i]; item._parent = this; - item._setProject(this._project); + item._setProject(this._project, true); if (item._name) item.setName(item._name); - } - this._changed(7); + if (notifySelf) + this._changed(5); + } + this._changed(11); } else { items = null; } @@ -3400,7 +3543,7 @@ return null; var index = item._index + (above ? 1 : 0); if (item._parent === this._parent && index > this._index) - index--; + index--; return item._parent.insertChild(index, this, _preserve); }, @@ -3409,8 +3552,8 @@ }, insertBelow: function(item, _preserve) { - return this._insert(false, item, _preserve); - }, + return this._insert(false, item, _preserve); + }, sendToBack: function() { return this._parent.insertChild(0, this); @@ -3432,7 +3575,7 @@ reduce: function() { if (this._children && this._children.length === 1) { - var child = this._children[0]; + var child = this._children[0].reduce(); child.insertAbove(this); child.setStyle(this._style); this.remove(); @@ -3442,33 +3585,41 @@ }, _removeNamed: function() { - var children = this._parent._children, - namedChildren = this._parent._namedChildren, - name = this._name, - namedArray = namedChildren[name], - index = namedArray ? namedArray.indexOf(this) : -1; - if (index == -1) - return; - if (children[name] == this) - delete children[name]; - namedArray.splice(index, 1); - if (namedArray.length) { - children[name] = namedArray[namedArray.length - 1]; - } else { - delete namedChildren[name]; - } - }, - - _remove: function(notify) { - if (this._parent) { + var parent = this._parent; + if (parent) { + var children = parent._children, + namedChildren = parent._namedChildren, + name = this._name, + namedArray = namedChildren[name], + index = namedArray ? namedArray.indexOf(this) : -1; + if (index !== -1) { + if (children[name] == this) + delete children[name]; + namedArray.splice(index, 1); + if (namedArray.length) { + children[name] = namedArray[namedArray.length - 1]; + } else { + delete namedChildren[name]; + } + } + } + }, + + _remove: function(notifySelf, notifyParent) { + var parent = this._parent; + if (parent) { if (this._name) this._removeNamed(); if (this._index != null) - Base.splice(this._parent._children, null, this._index, 1); - if (this.responds('frame')) - this._animateItem(false); - if (notify) - this._parent._changed(7); + Base.splice(parent._children, null, this._index, 1); + this._installEvents(false); + if (notifySelf) { + var project = this._project; + if (project && project._changes) + this._changed(5); + } + if (notifyParent) + parent._changed(11); this._parent = null; return true; } @@ -3476,7 +3627,7 @@ }, remove: function() { - return this._remove(true); + return this._remove(true, true); }, removeChildren: function(from, to) { @@ -3485,10 +3636,11 @@ from = from || 0; to = Base.pick(to, this._children.length); var removed = Base.splice(this._children, null, from, to - from); - for (var i = removed.length - 1; i >= 0; i--) - removed[i]._remove(false); + for (var i = removed.length - 1; i >= 0; i--) { + removed[i]._remove(true, false); + } if (removed.length > 0) - this._changed(7); + this._changed(11); return removed; }, @@ -3499,12 +3651,12 @@ this._children.reverse(); for (var i = 0, l = this._children.length; i < l; i++) this._children[i]._index = i; - this._changed(7); + this._changed(11); } }, isEmpty: function() { - return !this._children || this._children.length == 0; + return !this._children || this._children.length === 0; }, isEditable: function() { @@ -3517,6 +3669,18 @@ return true; }, + hasFill: function() { + return this.getStyle().hasFill(); + }, + + hasStroke: function() { + return this.getStyle().hasStroke(); + }, + + hasShadow: function() { + return this.getStyle().hasShadow(); + }, + _getOrder: function(item) { function getList(item) { var list = []; @@ -3539,6 +3703,10 @@ return this._children && this._children.length > 0; }, + isInserted: function() { + return this._parent ? this._parent.isInserted() : false; + }, + isAbove: function(item) { return this._getOrder(item) === -1; }, @@ -3572,7 +3740,7 @@ var parent = this._parent; while (parent) { if (parent._parent - && /^(group|layer|compound-path)$/.test(parent._type) + && /^(Group|Layer|CompoundPath)$/.test(parent._class) && item.isDescendant(parent)) return true; parent = parent._parent; @@ -3580,90 +3748,87 @@ return false; }, - scale: function(hor, ver , center) { - if (arguments.length < 2 || typeof ver === 'object') { - center = ver; - ver = hor; - } - return this.transform(new Matrix().scale(hor, ver, - center || this.getPosition(true))); - }, - translate: function() { var mx = new Matrix(); return this.transform(mx.translate.apply(mx, arguments)); }, - rotate: function(angle, center) { + rotate: function(angle ) { return this.transform(new Matrix().rotate(angle, + Point.read(arguments, 1, { readNull: true }) + || this.getPosition(true))); + } +}, Base.each(['scale', 'shear', 'skew'], function(name) { + this[name] = function() { + var point = Point.read(arguments), + center = Point.read(arguments, 0, { readNull: true }); + return this.transform(new Matrix()[name](point, center || this.getPosition(true))); - }, - - shear: function(hor, ver, center) { - if (arguments.length < 2 || typeof ver === 'object') { - center = ver; - ver = hor; - } - return this.transform(new Matrix().shear(hor, ver, - center || this.getPosition(true))); - }, - - transform: function(matrix ) { + }; +}, { + +}), { + transform: function(matrix, _applyMatrix) { + if (matrix && matrix.isIdentity()) + matrix = null; + var _matrix = this._matrix, + applyMatrix = (_applyMatrix || this._applyMatrix) + && (!_matrix.isIdentity() || matrix); + if (!matrix && !applyMatrix) + return this; + if (matrix) + _matrix.preConcatenate(matrix); + if (applyMatrix = applyMatrix && this._transformContent(_matrix)) { + var pivot = this._pivot, + style = this._style, + fillColor = style.getFillColor(true), + strokeColor = style.getStrokeColor(true); + if (pivot) + _matrix._transformPoint(pivot, pivot, true); + if (fillColor) + fillColor.transform(_matrix); + if (strokeColor) + strokeColor.transform(_matrix); + _matrix.reset(true); + } var bounds = this._bounds, position = this._position; - this._matrix.preConcatenate(matrix); - if (this._transformContent || arguments[1]) - this.applyMatrix(true); - this._changed(5); - if (bounds && matrix.getRotation() % 90 === 0) { + this._changed(9); + var decomp = bounds && matrix && matrix.decompose(); + if (decomp && !decomp.shearing && decomp.rotation % 90 === 0) { for (var key in bounds) { var rect = bounds[key]; - matrix._transformBounds(rect, rect); + if (applyMatrix || !rect._internal) + matrix._transformBounds(rect, rect); } var getter = this._boundsGetter, rect = bounds[getter && getter.getBounds || getter || 'getBounds']; if (rect) this._position = rect.getCenter(true); this._bounds = bounds; - } else if (position) { + } else if (matrix && position) { this._position = matrix._transformPoint(position, position); } return this; }, - _applyMatrix: function(matrix, applyMatrix) { + _transformContent: function(matrix) { var children = this._children; - if (children && children.length > 0) { + if (children) { for (var i = 0, l = children.length; i < l; i++) - children[i].transform(matrix, applyMatrix); + children[i].transform(matrix, true); return true; } }, - applyMatrix: function(_dontNotify) { - var matrix = this._matrix; - if (this._applyMatrix(matrix, true)) { - var style = this._style, - fillColor = style.getFillColor(true), - strokeColor = style.getStrokeColor(true); - if (fillColor) - fillColor.transform(matrix); - if (strokeColor) - strokeColor.transform(matrix); - matrix.reset(); - } - if (!_dontNotify) - this._changed(5); - }, - globalToLocal: function() { - var matrix = this.getGlobalMatrix(); - return matrix && matrix._transformPoint(Point.read(arguments)); + return this.getGlobalMatrix(true)._inverseTransform( + Point.read(arguments)); }, localToGlobal: function() { - var matrix = this.getGlobalMatrix(); - return matrix && matrix._inverseTransform(Point.read(arguments)); + return this.getGlobalMatrix(true)._transformPoint( + Point.read(arguments)); }, fitBounds: function(rectangle, fill) { @@ -3728,59 +3893,125 @@ } }, - draw: function(ctx, param) { + draw: function(ctx, param, parentStrokeMatrix) { + var updateVersion = this._updateVersion = this._project._updateVersion; if (!this._visible || this._opacity === 0) return; - this._drawCount = this._project._drawCount; - var trackTransforms = param.trackTransforms, - transforms = param.transforms, - parentMatrix = transforms[transforms.length - 1], - globalMatrix = parentMatrix.clone().concatenate(this._matrix); - if (trackTransforms) - transforms.push(this._globalMatrix = globalMatrix); + var matrices = param.matrices, + parentMatrix = matrices[matrices.length - 1], + viewMatrix = param.viewMatrix, + matrix = this._matrix, + globalMatrix = parentMatrix.chain(matrix); + if (!globalMatrix.isInvertible()) + return; + + function getViewMatrix(matrix) { + return viewMatrix ? viewMatrix.chain(matrix) : matrix; + } + + matrices.push(globalMatrix); + if (param.updateMatrix) { + globalMatrix._updateVersion = updateVersion; + this._globalMatrix = globalMatrix; + } + var blendMode = this._blendMode, opacity = this._opacity, normalBlend = blendMode === 'normal', nativeBlend = BlendMode.nativeModes[blendMode], direct = normalBlend && opacity === 1 + || param.dontStart + || param.clip || (nativeBlend || normalBlend && opacity < 1) && this._canComposite(), + pixelRatio = param.pixelRatio, mainCtx, itemOffset, prevOffset; if (!direct) { - var bounds = this.getStrokeBounds(parentMatrix); + var bounds = this.getStrokeBounds(getViewMatrix(parentMatrix)); if (!bounds.width || !bounds.height) return; prevOffset = param.offset; itemOffset = param.offset = bounds.getTopLeft().floor(); mainCtx = ctx; - ctx = CanvasProvider.getContext( - bounds.getSize().ceil().add(new Size(1, 1)), param.ratio); + ctx = CanvasProvider.getContext(bounds.getSize().ceil().add(1) + .multiply(pixelRatio)); + if (pixelRatio !== 1) + ctx.scale(pixelRatio, pixelRatio); } ctx.save(); + var strokeMatrix = parentStrokeMatrix + ? parentStrokeMatrix.chain(matrix) + : !this.getStrokeScaling(true) && getViewMatrix(globalMatrix), + clip = !direct && param.clipItem, + transform = !strokeMatrix || clip; if (direct) { ctx.globalAlpha = opacity; if (nativeBlend) ctx.globalCompositeOperation = blendMode; - } else { + } else if (transform) { ctx.translate(-itemOffset.x, -itemOffset.y); } - (direct ? this._matrix : globalMatrix).applyToContext(ctx); - if (!direct && param.clipItem) + if (transform) + (direct ? matrix : getViewMatrix(globalMatrix)).applyToContext(ctx); + if (clip) param.clipItem.draw(ctx, param.extend({ clip: true })); - this._draw(ctx, param); + if (strokeMatrix) { + ctx.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0); + var offset = param.offset; + if (offset) + ctx.translate(-offset.x, -offset.y); + } + this._draw(ctx, param, strokeMatrix); ctx.restore(); - if (trackTransforms) - transforms.pop(); - if (param.clip) + matrices.pop(); + if (param.clip && !param.dontFinish) ctx.clip(); if (!direct) { BlendMode.process(blendMode, ctx, mainCtx, opacity, - itemOffset.subtract(prevOffset).multiply(param.ratio)); + itemOffset.subtract(prevOffset).multiply(pixelRatio)); CanvasProvider.release(ctx); param.offset = prevOffset; } }, + _isUpdated: function(updateVersion) { + var parent = this._parent; + if (parent instanceof CompoundPath) + return parent._isUpdated(updateVersion); + var updated = this._updateVersion === updateVersion; + if (!updated && parent && parent._visible + && parent._isUpdated(updateVersion)) { + this._updateVersion = updateVersion; + updated = true; + } + return updated; + }, + + _drawSelection: function(ctx, matrix, size, selectedItems, updateVersion) { + if ((this._drawSelected || this._boundsSelected) + && this._isUpdated(updateVersion)) { + var color = this.getSelectedColor(true) + || this.getLayer().getSelectedColor(true), + mx = matrix.chain(this.getGlobalMatrix(true)); + ctx.strokeStyle = ctx.fillStyle = color + ? color.toCanvasStyle(ctx) : '#009dec'; + if (this._drawSelected) + this._drawSelected(ctx, mx, selectedItems); + if (this._boundsSelected) { + var half = size / 2; + coords = mx._transformCorners(this.getInternalBounds()); + ctx.beginPath(); + for (var i = 0; i < 8; i++) + ctx[i === 0 ? 'moveTo' : 'lineTo'](coords[i], coords[++i]); + ctx.closePath(); + ctx.stroke(); + for (var i = 0; i < 8; i++) + ctx.fillRect(coords[i] - half, coords[++i] - half, + size, size); + } + } + }, + _canComposite: function() { return false; } @@ -3808,6 +4039,7 @@ var Group = Item.extend({ _class: 'Group', + _selectChildren: true, _serializeFields: { children: [] }, @@ -3821,24 +4053,25 @@ _changed: function _changed(flags) { _changed.base.call(this, flags); - if (flags & 2 && this._transformContent - && !this._matrix.isIdentity()) { - this.applyMatrix(); - } - if (flags & (2 | 256)) { - delete this._clipItem; + if (flags & 1026) { + this._clipItem = undefined; } }, _getClipItem: function() { - if (this._clipItem !== undefined) - return this._clipItem; - for (var i = 0, l = this._children.length; i < l; i++) { - var child = this._children[i]; - if (child._clipMask) - return this._clipItem = child; - } - return this._clipItem = null; + var clipItem = this._clipItem; + if (clipItem === undefined) { + clipItem = null; + for (var i = 0, l = this._children.length; i < l; i++) { + var child = this._children[i]; + if (child._clipMask) { + clipItem = child; + break; + } + } + this._clipItem = clipItem; + } + return clipItem; }, isClipped: function() { @@ -3852,15 +4085,31 @@ }, _draw: function(ctx, param) { - var clipItem = param.clipItem = this._getClipItem(); - if (clipItem) + var clip = param.clip, + clipItem = !clip && this._getClipItem(), + draw = true; + param = param.extend({ clipItem: clipItem, clip: false }); + if (clip) { + if (this._currentPath) { + ctx.currentPath = this._currentPath; + draw = false; + } else { + ctx.beginPath(); + param.dontStart = param.dontFinish = true; + } + } else if (clipItem) { clipItem.draw(ctx, param.extend({ clip: true })); - for (var i = 0, l = this._children.length; i < l; i++) { - var item = this._children[i]; - if (item !== clipItem) - item.draw(ctx, param); - } - param.clipItem = null; + } + if (draw) { + for (var i = 0, l = this._children.length; i < l; i++) { + var item = this._children[i]; + if (item !== clipItem) + item.draw(ctx, param); + } + } + if (clip) { + this._currentPath = ctx.currentPath; + } } }); @@ -3869,7 +4118,7 @@ initialize: function Layer(arg) { var props = Base.isPlainObject(arg) - ? new Base(arg) + ? new Base(arg) : { children: Array.isArray(arg) ? arg : arguments }, insert = props.insert; props.insert = false; @@ -3888,13 +4137,14 @@ this._project.activeLayer = this.getNextSibling() || this.getPreviousSibling(); Base.splice(this._project.layers, null, this._index, 1); - this._project._needsRedraw = true; + this._installEvents(false); + this._project._needsUpdate = true; return true; } return false; }, - getNextSibling: function getNextSibling() { + getNextSibling: function getNextSibling() { return this._parent ? getNextSibling.base.call(this) : this._project.layers[this._index + 1] || null; }, @@ -3914,10 +4164,10 @@ _insert: function _insert(above, item, _preserve) { if (item instanceof Layer && !item._parent) { - this._remove(true); + this._remove(true, true); Base.splice(item._project.layers, [this], item._index + (above ? 1 : 0), 0); - this._setProject(item._project); + this._setProject(item._project, true); return this; } return _insert.base.call(this, above, item, _preserve); @@ -3926,32 +4176,43 @@ var Shape = Item.extend({ _class: 'Shape', - _transformContent: false, + _applyMatrix: false, + _canApplyMatrix: false, _boundsSelected: true, - - initialize: function Shape(shape, center, size, radius, props) { - this._shape = shape; - this._size = size; - this._radius = radius; - this._initialize(props, center); + _serializeFields: { + type: null, + size: null, + radius: null + }, + + initialize: function Shape(props) { + this._initialize(props); }, _equals: function(item) { - return this._shape === item._shape + return this._type === item._type && this._size.equals(item._size) && Base.equals(this._radius, item._radius); }, clone: function(insert) { - return this._clone(new Shape(this._shape, this.getPosition(true), - this._size.clone(), - this._radius.clone ? this._radius.clone() : this._radius, - { insert: false }), insert); - }, - - getShape: function() { - return this._shape; - }, + var copy = new Shape(Item.NO_INSERT); + copy.setType(this._type); + copy.setSize(this._size); + copy.setRadius(this._radius); + return this._clone(copy, insert); + }, + + getType: function() { + return this._type; + }, + + setType: function(type) { + this._type = type; + }, + + getShape: '#getType', + setShape: '#setType', getSize: function() { var size = this._size; @@ -3959,35 +4220,37 @@ }, setSize: function() { - var shape = this._shape, - size = Size.read(arguments); - if (!this._size.equals(size)) { - var width = size.width, + var size = Size.read(arguments); + if (!this._size) { + this._size = size.clone(); + } else if (!this._size.equals(size)) { + var type = this._type, + width = size.width, height = size.height; - if (shape === 'rectangle') { + if (type === 'rectangle') { var radius = Size.min(this._radius, size.divide(2)); this._radius.set(radius.width, radius.height); - } else if (shape === 'circle') { + } else if (type === 'circle') { width = height = (width + height) / 2; this._radius = width / 2; - } else if (shape === 'ellipse') { + } else if (type === 'ellipse') { this._radius.set(width / 2, height / 2); } this._size.set(width, height); - this._changed(5); + this._changed(9); } }, getRadius: function() { var rad = this._radius; - return this._shape === 'circle' + return this._type === 'circle' ? rad : new LinkedSize(rad.width, rad.height, this, 'setRadius'); }, setRadius: function(radius) { - var shape = this._shape; - if (shape === 'circle') { + var type = this._type; + if (type === 'circle') { if (radius === this._radius) return; var size = radius * 2; @@ -3995,17 +4258,21 @@ this._size.set(size, size); } else { radius = Size.read(arguments); - if (this._radius.equals(radius)) - return; - this._radius.set(radius.width, radius.height); - if (shape === 'rectangle') { - var size = Size.max(this._size, radius.multiply(2)); - this._size.set(size.width, size.height); - } else if (shape === 'ellipse') { - this._size.set(radius.width * 2, radius.height * 2); - } - } - this._changed(5); + if (!this._radius) { + this._radius = radius.clone(); + } else { + if (this._radius.equals(radius)) + return; + this._radius.set(radius.width, radius.height); + if (type === 'rectangle') { + var size = Size.max(this._size, radius.multiply(2)); + this._size.set(size.width, size.height); + } else if (type === 'ellipse') { + this._size.set(radius.width * 2, radius.height * 2); + } + } + } + this._changed(9); }, isEmpty: function() { @@ -4013,7 +4280,7 @@ }, toPath: function(insert) { - var path = new Path[Base.capitalize(this._shape)]({ + var path = new Path[Base.capitalize(this._type)]({ center: new Point(), size: this._size, radius: this._radius, @@ -4026,55 +4293,70 @@ return path; }, - _draw: function(ctx, param) { + _draw: function(ctx, param, strokeMatrix) { var style = this._style, hasFill = style.hasFill(), hasStroke = style.hasStroke(), - clip = param.clip; - if (hasFill || hasStroke || clip) { - var radius = this._radius, - shape = this._shape; - ctx.beginPath(); - if (shape === 'circle') { + dontPaint = param.dontFinish || param.clip, + untransformed = !strokeMatrix; + if (hasFill || hasStroke || dontPaint) { + var type = this._type, + radius = this._radius, + isCircle = type === 'circle'; + if (!param.dontStart) + ctx.beginPath(); + if (untransformed && isCircle) { ctx.arc(0, 0, radius, 0, Math.PI * 2, true); } else { - var rx = radius.width, - ry = radius.height, - kappa = Numerical.KAPPA; - if (shape === 'ellipse') { - var cx = rx * kappa, - cy = ry * kappa; - ctx.moveTo(-rx, 0); - ctx.bezierCurveTo(-rx, -cy, -cx, -ry, 0, -ry); - ctx.bezierCurveTo(cx, -ry, rx, -cy, rx, 0); - ctx.bezierCurveTo(rx, cy, cx, ry, 0, ry); - ctx.bezierCurveTo(-cx, ry, -rx, cy, -rx, 0); - } else { - var size = this._size, - width = size.width, - height = size.height; - if (rx === 0 && ry === 0) { - ctx.rect(-width / 2, -height / 2, width, height); - } else { - kappa = 1 - kappa; - var x = width / 2, - y = height / 2, - cx = rx * kappa, - cy = ry * kappa; - ctx.moveTo(-x, -y + ry); - ctx.bezierCurveTo(-x, -y + cy, -x + cx, -y, -x + rx, -y); - ctx.lineTo(x - rx, -y); - ctx.bezierCurveTo(x - cx, -y, x, -y + cy, x, -y + ry); - ctx.lineTo(x, y - ry); - ctx.bezierCurveTo(x, y - cy, x - cx, y, x - rx, y); - ctx.lineTo(-x + rx, y); - ctx.bezierCurveTo(-x + cx, y, -x, y - cy, -x, y - ry); - } + var rx = isCircle ? radius : radius.width, + ry = isCircle ? radius : radius.height, + size = this._size, + width = size.width, + height = size.height; + if (untransformed && type === 'rect' && rx === 0 && ry === 0) { + ctx.rect(-width / 2, -height / 2, width, height); + } else { + var x = width / 2, + y = height / 2, + kappa = 1 - 0.5522847498307936, + cx = rx * kappa, + cy = ry * kappa, + c = [ + -x, -y + ry, + -x, -y + cy, + -x + cx, -y, + -x + rx, -y, + x - rx, -y, + x - cx, -y, + x, -y + cy, + x, -y + ry, + x, y - ry, + x, y - cy, + x - cx, y, + x - rx, y, + -x + rx, y, + -x + cx, y, + -x, y - cy, + -x, y - ry + ]; + if (strokeMatrix) + strokeMatrix.transform(c, c, 32); + ctx.moveTo(c[0], c[1]); + ctx.bezierCurveTo(c[2], c[3], c[4], c[5], c[6], c[7]); + if (x !== rx) + ctx.lineTo(c[8], c[9]); + ctx.bezierCurveTo(c[10], c[11], c[12], c[13], c[14], c[15]); + if (y !== ry) + ctx.lineTo(c[16], c[17]); + ctx.bezierCurveTo(c[18], c[19], c[20], c[21], c[22], c[23]); + if (x !== rx) + ctx.lineTo(c[24], c[25]); + ctx.bezierCurveTo(c[26], c[27], c[28], c[29], c[30], c[31]); } } ctx.closePath(); } - if (!clip && (hasFill || hasStroke)) { + if (!dontPaint && (hasFill || hasStroke)) { this._setStyles(ctx); if (hasFill) { ctx.fill(style.getWindingRule()); @@ -4096,7 +4378,7 @@ return matrix ? matrix._transformBounds(rect) : rect; } }, -new function() { +new function() { function getCornerCenter(that, point, expand) { var radius = that._radius; @@ -4124,7 +4406,7 @@ return { _contains: function _contains(point) { - if (this._shape === 'rectangle') { + if (this._type === 'rectangle') { var center = getCornerCenter(this, point); return center ? point.subtract(center).divide(this._radius) @@ -4135,13 +4417,13 @@ } }, - _hitTest: function _hitTest(point, options) { + _hitTestSelf: function _hitTestSelf(point, options) { var hit = false; if (this.hasStroke()) { - var shape = this._shape, + var type = this._type, radius = this._radius, strokeWidth = this.getStrokeWidth() + 2 * options.tolerance; - if (shape === 'rectangle') { + if (type === 'rectangle') { var center = getCornerCenter(this, point, strokeWidth); if (center) { var pt = point.subtract(center); @@ -4155,7 +4437,7 @@ && !inner._containsPoint(point); } } else { - if (shape === 'ellipse') + if (type === 'ellipse') radius = getEllipseRadius(point, radius); hit = 2 * Math.abs(point.getLength() - radius) <= strokeWidth; @@ -4163,14 +4445,18 @@ } return hit ? new HitResult('stroke', this) - : _hitTest.base.apply(this, arguments); + : _hitTestSelf.base.apply(this, arguments); } }; }, { statics: new function() { - function createShape(shape, point, size, radius, args) { - return new Shape(shape, point, size, radius, Base.getNamed(args)); + function createShape(type, point, size, radius, args) { + var item = new Shape(Base.getNamed(args)); + item._type = type; + item._size = size; + item._radius = radius; + return item.translate(point); } return { @@ -4190,7 +4476,7 @@ }, Ellipse: function() { - var ellipse = Shape._readEllipse(arguments); + var ellipse = Shape._readEllipse(arguments), radius = ellipse.radius; return createShape('ellipse', ellipse.center, radius.multiply(2), radius, arguments); @@ -4214,7 +4500,8 @@ var Raster = Item.extend({ _class: 'Raster', - _transformContent: false, + _applyMatrix: false, + _canApplyMatrix: false, _boundsGetter: 'getBounds', _boundsSelected: true, _serializeFields: { @@ -4239,15 +4526,17 @@ }, clone: function(insert) { - var param = { insert: false }, - image = this._image; + var copy = new Raster(Item.NO_INSERT), + image = this._image, + canvas = this._canvas; if (image) { - param.image = image; - } else if (this._canvas) { - var canvas = param.canvas = CanvasProvider.getCanvas(this._size); - canvas.getContext('2d').drawImage(this._canvas, 0, 0); - } - return this._clone(new Raster(param), insert); + copy.setImage(image); + } else if (canvas) { + var copyCanvas = CanvasProvider.getCanvas(this._size); + copyCanvas.getContext('2d').drawImage(canvas, 0, 0); + copy.setCanvas(copyCanvas); + } + return this._clone(copy, insert); }, getSize: function() { @@ -4275,10 +4564,10 @@ }, isEmpty: function() { - return this._size.width == 0 && this._size.height == 0; - }, - - getPpi: function() { + return this._size.width === 0 && this._size.height === 0; + }, + + getResolution: function() { var matrix = this._matrix, orig = new Point(0, 0).transform(matrix), u = new Point(1, 0).transform(matrix).subtract(orig), @@ -4289,6 +4578,8 @@ ); }, + getPpi: '#getResolution', + getImage: function() { return this._image; }, @@ -4296,7 +4587,7 @@ setImage: function(image) { if (this._canvas) CanvasProvider.release(this._canvas); - if (image.getContext) { + if (image && image.getContext) { this._image = null; this._canvas = image; } else { @@ -4304,10 +4595,10 @@ this._canvas = null; } this._size = new Size( - image.naturalWidth || image.width, - image.naturalHeight || image.height); + image ? image.naturalWidth || image.width : 0, + image ? image.naturalHeight || image.height : 0); this._context = null; - this._changed(5 | 129); + this._changed(521); }, getCanvas: function() { @@ -4326,12 +4617,12 @@ setCanvas: '#setImage', - getContext: function() { + getContext: function(modify) { if (!this._context) this._context = this.getCanvas().getContext('2d'); - if (arguments[0]) { + if (modify) { this._image = null; - this._changed(129); + this._changed(513); } return this._context; }, @@ -4346,25 +4637,25 @@ setSource: function(src) { var that = this, - image = document.getElementById(src) || new Image(); + image; function loaded() { - var view = that._project.view; - if (view) + var view = that.getView(); + if (view) { paper = view._scope; - that.fire('load'); - if (view) - view.draw(true); - } + that.setImage(image); + that.fire('load'); + view.update(); + } + } + + image = document.getElementById(src) || new Image(); if (image.naturalWidth && image.naturalHeight) { setTimeout(loaded, 0); } else { DomEvent.add(image, { - load: function() { - that.setImage(image); - loaded(); - } + load: loaded }); if (!image.src) image.src = src; @@ -4374,22 +4665,22 @@ getElement: function() { return this._canvas || this._image; - }, - - getSubCanvas: function(rect) { - rect = Rectangle.read(arguments); - var ctx = CanvasProvider.getContext(rect.getSize()); + } +}, { + beans: false, + + getSubCanvas: function() { + var rect = Rectangle.read(arguments), + ctx = CanvasProvider.getContext(rect.getSize()); ctx.drawImage(this.getCanvas(), rect.x, rect.y, rect.width, rect.height, 0, 0, rect.width, rect.height); return ctx.canvas; }, - getSubRaster: function(rect) { - rect = Rectangle.read(arguments); - var raster = new Raster({ - canvas: this.getSubCanvas(rect), - insert: false - }); + getSubRaster: function() { + var rect = Rectangle.read(arguments), + raster = new Raster(Item.NO_INSERT); + raster.setCanvas(this.getSubCanvas(rect)); raster.translate(rect.getCenter().subtract(this.getSize().divide(2))); raster._matrix.preConcatenate(this._matrix); raster.insertAbove(this); @@ -4404,8 +4695,8 @@ return canvas ? canvas.toDataURL() : null; }, - drawImage: function(image, point) { - point = Point.read(arguments, 1); + drawImage: function(image ) { + var point = Point.read(arguments, 1); this.getContext(true).drawImage(image, point.x, point.y); }, @@ -4437,7 +4728,7 @@ .translate(-bounds.x, -bounds.y); matrix.applyToContext(ctx); if (path) - path.draw(ctx, new Base({ clip: true, transforms: [matrix] })); + path.draw(ctx, new Base({ clip: true, matrices: [matrix] })); this._matrix.applyToContext(ctx); ctx.drawImage(this.getElement(), -this._size.width / 2, -this._size.height / 2); @@ -4459,8 +4750,8 @@ return total ? Color.read(channels) : null; }, - getPixel: function(point) { - point = Point.read(arguments); + getPixel: function() { + var point = Point.read(arguments); var data = this.getContext().getImageData(point.x, point.y, 1, 1).data; return new Color('rgb', [data[0] / 255, data[1] / 255, data[2] / 255], data[3] / 255); @@ -4481,21 +4772,21 @@ ctx.putImageData(imageData, point.x, point.y); }, - createImageData: function(size) { - size = Size.read(arguments); + createImageData: function() { + var size = Size.read(arguments); return this.getContext().createImageData(size.width, size.height); }, - getImageData: function(rect) { - rect = Rectangle.read(arguments); + getImageData: function() { + var rect = Rectangle.read(arguments); if (rect.isEmpty()) rect = new Rectangle(this._size); return this.getContext().getImageData(rect.x, rect.y, rect.width, rect.height); }, - setImageData: function(data, point) { - point = Point.read(arguments, 1); + setImageData: function(data ) { + var point = Point.read(arguments, 1); this.getContext(true).putImageData(data, point.x, point.y); }, @@ -4504,7 +4795,7 @@ return matrix ? matrix._transformBounds(rect) : rect; }, - _hitTest: function(point) { + _hitTestSelf: function(point) { if (this._contains(point)) { var that = this; return new HitResult('pixel', that, { @@ -4534,7 +4825,8 @@ var PlacedSymbol = Item.extend({ _class: 'PlacedSymbol', - _transformContent: false, + _applyMatrix: false, + _canApplyMatrix: false, _boundsGetter: { getBounds: 'getStrokeBounds' }, _boundsSelected: true, _serializeFields: { @@ -4556,32 +4848,30 @@ }, setSymbol: function(symbol) { - if (this._symbol) - delete this._symbol._instances[this._id]; this._symbol = symbol; - symbol._instances[this._id] = this; + this._changed(9); }, clone: function(insert) { - return this._clone(new PlacedSymbol({ - symbol: this.symbol, - insert: false - }), insert); + var copy = new PlacedSymbol(Item.NO_INSERT); + copy.setSymbol(this._symbol); + return this._clone(copy, insert); }, isEmpty: function() { return this._symbol._definition.isEmpty(); }, - _getBounds: function(getter, matrix) { - return this.symbol._definition._getCachedBounds(getter, matrix); - }, - - _hitTest: function(point, options, matrix) { - var result = this._symbol._definition._hitTest(point, options, matrix); - if (result) - result.item = this; - return result; + _getBounds: function(getter, matrix, cacheItem) { + return this.symbol._definition._getCachedBounds(getter, matrix, + cacheItem); + }, + + _hitTestSelf: function(point, options) { + var res = this._symbol._definition._hitTest(point, options); + if (res) + res.item = this; + return res; }, _draw: function(ctx, param) { @@ -4604,9 +4894,9 @@ statics: { getOptions: function(options) { - return options && options._merged ? options : new Base({ + return new Base({ type: null, - tolerance: paper.project.options.hitTolerance || 2, + tolerance: paper.settings.hitTolerance, fill: !options, stroke: !options, segments: !options, @@ -4615,8 +4905,7 @@ center: false, bounds: false, guides: false, - selected: false, - _merged: true + selected: false }, options); } } @@ -4624,6 +4913,7 @@ var Segment = Base.extend({ _class: 'Segment', + beans: true, initialize: function Segment(arg0, arg1, arg2, arg3, arg4, arg5) { var count = arguments.length, @@ -4643,7 +4933,7 @@ point = arg0; handleIn = arg1; handleOut = arg2; - } else { + } else { point = arg0 !== undefined ? [ arg0, arg1 ] : null; handleIn = arg2 !== undefined ? [ arg2, arg3 ] : null; handleOut = arg4 !== undefined ? [ arg4, arg5 ] : null; @@ -4655,31 +4945,35 @@ _serialize: function(options) { return Base.serialize(this.isLinear() ? this._point - : [this._point, this._handleIn, this._handleOut], options, true); + : [this._point, this._handleIn, this._handleOut], + options, true); }, _changed: function(point) { - if (!this._path) + var path = this._path; + if (!path) return; - var curve = this._path._curves && this.getCurve(), - other; - if (curve) { - curve._changed(); - if (other = (curve[point == this._point - || point == this._handleIn && curve._segment1 == this - ? 'getPrevious' : 'getNext']())) { - other._changed(); - } - } - this._path._changed(5); + var curves = path._curves, + index = this._index, + curveIn, curveOut; + if (curves) { + if ((!point || point === this._point || point === this._handleIn) + && (curveIn = curves[index - 1] + || path._closed && curves[curves.length - 1])) + curveIn._changed(); + if ((!point || point === this._point || point === this._handleOut) + && (curveOut = curves[index])) + curveOut._changed(); + } + path._changed(25); }, getPoint: function() { return this._point; }, - setPoint: function(point) { - point = Point.read(arguments); + setPoint: function() { + var point = Point.read(arguments); this._point.set(point.x, point.y); }, @@ -4687,8 +4981,8 @@ return this._handleIn; }, - setHandleIn: function(point) { - point = Point.read(arguments); + setHandleIn: function() { + var point = Point.read(arguments); this._handleIn.set(point.x, point.y); }, @@ -4696,8 +4990,8 @@ return this._handleOut; }, - setHandleOut: function(point) { - point = Point.read(arguments); + setHandleOut: function() { + var point = Point.read(arguments); this._handleOut.set(point.x, point.y); }, @@ -4705,9 +4999,12 @@ return this._handleIn.isZero() && this._handleOut.isZero(); }, - setLinear: function() { - this._handleIn.set(0, 0); - this._handleOut.set(0, 0); + setLinear: function(linear) { + if (linear) { + this._handleIn.set(0, 0); + this._handleOut.set(0, 0); + } else { + } }, isColinear: function(segment) { @@ -4732,7 +5029,7 @@ var next = this.getNext(), handle1 = this._handleOut, handle2 = next._handleIn, - kappa = Numerical.KAPPA; + kappa = 0.5522847498307936; if (handle1.isOrthogonal(handle2)) { var from = this._point, to = next._point, @@ -4746,58 +5043,37 @@ return false; }, - _isSelected: function(point) { + _selectionState: 0, + + isSelected: function(_point) { var state = this._selectionState; - return point == this._point ? !!(state & 4) - : point == this._handleIn ? !!(state & 1) - : point == this._handleOut ? !!(state & 2) + return !_point ? !!(state & 7) + : _point === this._point ? !!(state & 4) + : _point === this._handleIn ? !!(state & 1) + : _point === this._handleOut ? !!(state & 2) : false; }, - _setSelected: function(point, selected) { + setSelected: function(selected, _point) { var path = this._path, - selected = !!selected, - state = this._selectionState || 0, - selection = [ - !!(state & 4), - !!(state & 1), - !!(state & 2) - ]; - if (point === this._point) { - if (selected) { - selection[1] = selection[2] = false; - } else { - var previous = this.getPrevious(), - next = this.getNext(); - selection[1] = previous && (previous._point.isSelected() - || previous._handleOut.isSelected()); - selection[2] = next && (next._point.isSelected() - || next._handleIn.isSelected()); - } - selection[0] = selected; + selected = !!selected, + state = this._selectionState, + oldState = state, + flag = !_point ? 7 + : _point === this._point ? 4 + : _point === this._handleIn ? 1 + : _point === this._handleOut ? 2 + : 0; + if (selected) { + state |= flag; } else { - var index = point === this._handleIn ? 1 : 2; - if (selection[index] != selected) { - if (selected) - selection[0] = false; - selection[index] = selected; - } - } - this._selectionState = (selection[0] ? 4 : 0) - | (selection[1] ? 1 : 0) - | (selection[2] ? 2 : 0); - if (path && state != this._selectionState) { - path._updateSelection(this, state, this._selectionState); - path._changed(33); - } - }, - - isSelected: function() { - return this._isSelected(this._point); - }, - - setSelected: function(selected) { - this._setSelected(this._point, selected); + state &= ~flag; + } + this._selectionState = state; + if (path && state !== oldState) { + path._updateSelection(this, oldState, state); + path._changed(129); + } }, getIndex: function() { @@ -4812,7 +5088,8 @@ var path = this._path, index = this._index; if (path) { - if (!path._closed && index == path._segments.length - 1) + if (index > 0 && !path._closed + && index === path._segments.length - 1) index--; return path.getCurves()[index] || null; } @@ -4821,7 +5098,9 @@ getLocation: function() { var curve = this.getCurve(); - return curve ? new CurveLocation(curve, curve.getNext() ? 0 : 1) : null; + return curve + ? new CurveLocation(curve, this === curve._segment1 ? 0 : 1) + : null; }, getNext: function() { @@ -4865,9 +5144,14 @@ return '{ ' + parts.join(', ') + ' }'; }, + transform: function(matrix) { + this._transformCoordinates(matrix, new Array(6), true); + this._changed(); + }, + _transformCoordinates: function(matrix, coords, change) { var point = this._point, - handleIn = !change || !this._handleIn.isZero() + handleIn = !change || !this._handleIn.isZero() ? this._handleIn : null, handleOut = !change || !this._handleOut.isZero() ? this._handleOut : null, @@ -4885,7 +5169,7 @@ coords[i++] = handleOut._y + y; } if (matrix) { - matrix._transformCoordinates(coords, 0, coords, 0, i / 2); + matrix._transformCoordinates(coords, coords, i / 2); x = coords[0]; y = coords[1]; if (change) { @@ -4920,15 +5204,16 @@ var x, y, selected; if (!point) { x = y = 0; - } else if ((x = point[0]) !== undefined) { + } else if ((x = point[0]) !== undefined) { y = point[1]; } else { - if ((x = point.x) === undefined) { - point = Point.read(arguments); - x = point.x; - } - y = point.y; - selected = point.selected; + var pt = point; + if ((x = pt.x) === undefined) { + pt = Point.read(arguments); + x = pt.x; + } + y = pt.y; + selected = pt.selected; } this._x = x; this._y = y; @@ -4977,11 +5262,11 @@ }, setSelected: function(selected) { - this._owner._setSelected(this, selected); + this._owner.setSelected(selected, this); }, isSelected: function() { - return this._owner._isSelected(this); + return this._owner.isSelected(this); } }); @@ -5021,16 +5306,15 @@ }, _changed: function() { - delete this._length; - delete this._bounds; + this._length = this._bounds = undefined; }, getPoint1: function() { return this._segment1._point; }, - setPoint1: function(point) { - point = Point.read(arguments); + setPoint1: function() { + var point = Point.read(arguments); this._segment1._point.set(point.x, point.y); }, @@ -5038,8 +5322,8 @@ return this._segment2._point; }, - setPoint2: function(point) { - point = Point.read(arguments); + setPoint2: function() { + var point = Point.read(arguments); this._segment2._point.set(point.x, point.y); }, @@ -5047,8 +5331,8 @@ return this._segment1._handleOut; }, - setHandle1: function(point) { - point = Point.read(arguments); + setHandle1: function() { + var point = Point.read(arguments); this._segment1._handleOut.set(point.x, point.y); }, @@ -5056,8 +5340,8 @@ return this._segment2._handleIn; }, - setHandle2: function(point) { - point = Point.read(arguments); + setHandle2: function() { + var point = Point.read(arguments); this._segment2._handleIn.set(point.x, point.y); }, @@ -5090,16 +5374,21 @@ }, isSelected: function() { - return this.getHandle1().isSelected() && this.getHandle2().isSelected(); + return this.getPoint1().isSelected() + && this.getHandle2().isSelected() + && this.getHandle2().isSelected() + && this.getPoint2().isSelected(); }, setSelected: function(selected) { + this.getPoint1().setSelected(selected); this.getHandle1().setSelected(selected); this.getHandle2().setSelected(selected); - }, - - getValues: function() { - return Curve.getValues(this._segment1, this._segment2); + this.getPoint2().setSelected(selected); + }, + + getValues: function(matrix) { + return Curve.getValues(this._segment1, this._segment2, matrix); }, getPoints: function() { @@ -5111,15 +5400,12 @@ }, getLength: function() { - var from = arguments[0], - to = arguments[1], - fullLength = arguments.length === 0 || from === 0 && to === 1; - if (fullLength && this._length != null) - return this._length; - var length = Curve.getLength(this.getValues(), from, to); - if (fullLength) - this._length = length; - return length; + if (this._length == null) { + this._length = this.isLinear() + ? this._segment2._point.getDistance(this._segment1._point) + : Curve.getLength(this.getValues(), 0, 1); + } + return this._length; }, getArea: function() { @@ -5130,37 +5416,42 @@ return new Curve(Curve.getPart(this.getValues(), from, to)); }, + getPartLength: function(from, to) { + return Curve.getLength(this.getValues(), from, to); + }, + isLinear: function() { return this._segment1._handleOut.isZero() && this._segment2._handleIn.isZero(); }, + isHorizontal: function() { + return this.isLinear() && Numerical.isZero( + this._segment1._point._y - this._segment2._point._y); + }, + getIntersections: function(curve) { return Curve.getIntersections(this.getValues(), curve.getValues(), this, curve, []); }, - reverse: function() { - return new Curve(this._segment2.reverse(), this._segment1.reverse()); - }, - _getParameter: function(offset, isParameter) { return isParameter ? offset : offset && offset.curve === this ? offset.parameter : offset === undefined && isParameter === undefined - ? 0.5 + ? 0.5 : this.getParameterAt(offset, 0); }, - divide: function(offset, isParameter) { + divide: function(offset, isParameter, ignoreLinear) { var parameter = this._getParameter(offset, isParameter), tolerance = 0.00001, res = null; if (parameter > tolerance && parameter < 1 - tolerance) { var parts = Curve.subdivide(this.getValues(), parameter), - isLinear = this.isLinear(), + isLinear = ignoreLinear ? false : this.isLinear(), left = parts[0], right = parts[1]; @@ -5182,7 +5473,7 @@ } else { this._path.insert(this._segment2._index, segment); } - res = this; + res = this; } else { var end = this._segment2; this._segment2 = segment; @@ -5199,6 +5490,22 @@ : null; }, + reverse: function() { + return new Curve(this._segment2.reverse(), this._segment1.reverse()); + }, + + remove: function() { + var removed = false; + if (this._path) { + var segment2 = this._segment2, + handleOut = segment2._handleOut; + removed = segment2.remove(); + if (removed) + this._segment1._handleOut.set(handleOut.x, handleOut.y); + } + return removed; + }, + clone: function() { return new Curve(this._segment1, this._segment2); }, @@ -5214,17 +5521,20 @@ }, statics: { - getValues: function(segment1, segment2) { + getValues: function(segment1, segment2, matrix) { var p1 = segment1._point, h1 = segment1._handleOut, h2 = segment2._handleIn, - p2 = segment2._point; - return [ - p1._x, p1._y, - p1._x + h1._x, p1._y + h1._y, - p2._x + h2._x, p2._y + h2._y, - p2._x, p2._y - ]; + p2 = segment2._point, + values = [ + p1._x, p1._y, + p1._x + h1._x, p1._y + h1._y, + p2._x + h2._x, p2._y + h2._y, + p2._x, p2._y + ]; + if (matrix) + matrix._transformCoordinates(values, values, 6); + return values; }, evaluate: function(v, t, type) { @@ -5232,11 +5542,13 @@ c1x = v[2], c1y = v[3], c2x = v[4], c2y = v[5], p2x = v[6], p2y = v[7], + tolerance = 0.00001, x, y; - if (type === 0 && (t === 0 || t === 1)) { - x = t === 0 ? p1x : p2x; - y = t === 0 ? p1y : p2y; + if (type === 0 && (t < tolerance || t > 1 - tolerance)) { + var isZero = t < tolerance; + x = isZero ? p1x : p2x; + y = isZero ? p1y : p2y; } else { var cx = 3 * (c1x - p1x), bx = 3 * (c2x - c1x) - cx, @@ -5249,11 +5561,16 @@ x = ((ax * t + bx) * t + cx) * t + p1x; y = ((ay * t + by) * t + cy) * t + p1y; } else { - var tolerance = 0.00001; if (t < tolerance && c1x === p1x && c1y === p1y || t > 1 - tolerance && c2x === p2x && c2y === p2y) { x = p2x - p1x; y = p2y - p1y; + } else if (t < tolerance) { + x = cx; + y = cy; + } else if (t > 1 - tolerance) { + x = 3 * (p2x - c2x); + y = 3 * (p2y - c2y); } else { x = (3 * ax * t + 2 * bx) * t + cx; y = (3 * ay * t + 2 * by) * t + cy; @@ -5265,7 +5582,7 @@ } } } - return type == 2 ? new Point(y, -x) : new Point(x, y); + return type === 2 ? new Point(y, -x) : new Point(x, y); }, subdivide: function(v, t) { @@ -5283,8 +5600,8 @@ p7x = u * p4x + t * p5x, p7y = u * p4y + t * p5y, p8x = u * p6x + t * p7x, p8y = u * p6y + t * p7y; return [ - [p1x, p1y, p3x, p3y, p6x, p6y, p8x, p8y], - [p8x, p8y, p7x, p7y, p5x, p5y, p2x, p2y] + [p1x, p1y, p3x, p3y, p6x, p6y, p8x, p8y], + [p8x, p8y, p7x, p7y, p5x, p5y, p2x, p2y] ]; }, @@ -5300,11 +5617,10 @@ }, getParameterOf: function(v, x, y) { - if (Math.abs(v[0] - x) < 0.00001 - && Math.abs(v[1] - y) < 0.00001) + var tolerance = 0.00001; + if (Math.abs(v[0] - x) < tolerance && Math.abs(v[1] - y) < tolerance) return 0; - if (Math.abs(v[6] - x) < 0.00001 - && Math.abs(v[7] - y) < 0.00001) + if (Math.abs(v[6] - x) < tolerance && Math.abs(v[7] - y) < tolerance) return 1; var txs = [], tys = [], @@ -5317,7 +5633,7 @@ if (sy == -1 || (ty = tys[cy++]) >= 0 && ty <= 1) { if (sx == -1) tx = ty; else if (sy == -1) ty = tx; - if (Math.abs(tx - ty) < 0.00001) + if (Math.abs(tx - ty) < tolerance) return (tx + ty) * 0.5; } } @@ -5330,9 +5646,9 @@ getPart: function(v, from, to) { if (from > 0) - v = Curve.subdivide(v, from)[1]; + v = Curve.subdivide(v, from)[1]; if (to < 1) - v = Curve.subdivide(v, (to - from) / (1 - from))[0]; + v = Curve.subdivide(v, (to - from) / (1 - from))[0]; return v; }, @@ -5369,8 +5685,8 @@ }, getBounds: function(v) { - var min = v.slice(0, 2), - max = min.slice(), + var min = v.slice(0, 2), + max = min.slice(), roots = [0, 0]; for (var i = 0; i < 2; i++) Curve._addBounds(v[i], v[i + 2], v[i + 4], v[i + 6], @@ -5404,87 +5720,6 @@ + t * t * t * v3, padding); } - }, - - _getWinding: function(v, prev, x, y, roots1, roots2) { - var tolerance = 0.00001, - abs = Math.abs; - - function getDirection(v) { - var y0 = v[1], - y1 = v[7], - dir = y0 > y1 ? -1 : 1; - return dir === 1 && (y < y0 || y > y1) - || dir === -1 && (y < y1 || y > y0) - ? 0 - : dir; - } - - if (Curve.isLinear(v)) { - var dir = getDirection(v); - if (!dir) - return 0; - var cross = (v[6] - v[0]) * (y - v[1]) - (v[7] - v[1]) * (x - v[0]); - return (cross < -tolerance ? -1 : 1) == dir ? 0 : dir; - } - - var y0 = v[1], - y1 = v[3], - y2 = v[5], - y3 = v[7]; - var a = 3 * (y1 - y2) - y0 + y3, - b = 2 * (y0 + y2) - 4 * y1, - c = y1 - y0; - var count = Numerical.solveQuadratic(a, b, c, roots1, tolerance, - 1 - tolerance), - part, - rest = v, - t1 = roots1[0], - winding = 0; - for (var i = 0; i <= count; i++) { - if (i === count) { - part = rest; - } else { - var curves = Curve.subdivide(rest, t1); - part = curves[0]; - rest = curves[1]; - t1 = roots1[i]; - t1 = (roots1[i + 1] - t1) / (1 - t1); - } - if (i > 0) - part[3] = part[1]; - if (i < count) - part[5] = rest[1]; - var dir = getDirection(part); - if (!dir) - continue; - var t2, - px; - if (Curve.solveCubic(part, 1, y, roots2, -tolerance, 1 + -tolerance) - === 1) { - t2 = roots2[0]; - px = Curve.evaluate(part, t2, 0).x; - } else { - var mid = (part[1] + part[7]) / 2; - t2 = y < mid && dir > 0 ? 0 : 1; - if (t2 === 1 && y == part[7]) - continue; - px = t2 === 0 ? part[0] : part[6]; - } - var slope = Curve.evaluate(part, t2, 1).y, - stationary = abs(slope) < tolerance || t2 < tolerance - && Curve.evaluate(prev, 1, 1).y * slope < 0; - if (x >= px + (stationary ? -tolerance : tolerance * dir) - && !(stationary && (abs(t2) < tolerance - && abs(x - part[0]) > tolerance - || abs(t2 - 1) < tolerance - && abs(x - part[6]) > tolerance))) { - winding += stationary && abs(t2 - (dir > 0 ? 1 : 0)) < tolerance - ? -dir : dir; - } - prev = part; - } - return winding; } }}, Base.each(['getBounds', 'getStrokeBounds', 'getHandleBounds', 'getRoughBounds'], function(name) { @@ -5513,13 +5748,15 @@ }; }, { + beans: false, + getParameterAt: function(offset, start) { return Curve.getParameterAt(this.getValues(), offset, start !== undefined ? start : offset < 0 ? 1 : 0); }, - getParameterOf: function(point) { - point = Point.read(arguments); + getParameterOf: function() { + var point = Point.read(arguments); return Curve.getParameterOf(this.getValues(), point.x, point.y); }, @@ -5529,17 +5766,21 @@ return new CurveLocation(this, offset); }, - getLocationOf: function(point) { - point = Point.read(arguments); - var t = this.getParameterOf(point); + getLocationOf: function() { + var point = Point.read(arguments), + t = this.getParameterOf(point); return t != null ? new CurveLocation(this, t) : null; }, - getNearestLocation: function(point) { - point = Point.read(arguments); - var values = this.getValues(), + getOffsetOf: function() { + var loc = this.getLocationOf.apply(this, arguments); + return loc ? loc.getOffset() : null; + }, + + getNearestLocation: function() { + var point = Point.read(arguments), + values = this.getValues(), count = 100, - tolerance = Numerical.TOLERANCE, minDist = Infinity, minT = 0; @@ -5559,7 +5800,7 @@ refine(i / count); var step = 1 / (count * 2); - while (step > tolerance) { + while (step > 0.00001) { if (!refine(minT - step) && !refine(minT + step)) step /= 2; } @@ -5568,13 +5809,12 @@ point.getDistance(pt)); }, - getNearestPoint: function(point) { - point = Point.read(arguments); - return this.getNearestLocation(point).getPoint(); + getNearestPoint: function() { + return this.getNearestLocation.apply(this, arguments).getPoint(); } }), -new function() { +new function() { function getLengthIntegrand(v) { var p1x = v[0], p1y = v[1], @@ -5610,11 +5850,12 @@ if (b === undefined) b = 1; var isZero = Numerical.isZero; - if (isZero(v[0] - v[2]) && isZero(v[1] - v[3]) + if (a === 0 && b === 1 + && isZero(v[0] - v[2]) && isZero(v[1] - v[3]) && isZero(v[6] - v[4]) && isZero(v[7] - v[5])) { - var dx = v[6] - v[0], - dy = v[7] - v[1]; - return (b - a) * Math.sqrt(dx * dx + dy * dy); + var dx = v[6] - v[0], + dy = v[7] - v[1]; + return Math.sqrt(dx * dx + dy * dy); } var ds = getLengthIntegrand(v); return Numerical.integrate(ds, a, b, getIterations(a, b)); @@ -5643,151 +5884,92 @@ return length - offset; } return Numerical.findRoot(f, ds, - forward ? a + guess : b - guess, + forward ? a + guess : b - guess, a, b, 16, 0.00001); } }; -}, new function() { - function addLocation(locations, curve1, t1, point1, curve2, t2, point2) { - var first = locations[0], - last = locations[locations.length - 1]; - if ((!first || !point1.isClose(first._point, Numerical.EPSILON)) - && (!last || !point1.isClose(last._point, Numerical.EPSILON))) - locations.push( - new CurveLocation(curve1, t1, point1, curve2, t2, point2)); - } - - function addCurveIntersections(v1, v2, curve1, curve2, locations, - range1, range2, recursion) { - recursion = (recursion || 0) + 1; +}, new function() { + function addLocation(locations, include, curve1, t1, point1, curve2, t2, + point2) { + var loc = new CurveLocation(curve1, t1, point1, curve2, t2, point2); + if (!include || include(loc)) + locations.push(loc); + } + + function addCurveIntersections(v1, v2, curve1, curve2, locations, include, + tMin, tMax, uMin, uMax, oldTDiff, reverse, recursion) { if (recursion > 20) return; - range1 = range1 || [ 0, 1 ]; - range2 = range2 || [ 0, 1 ]; - var part1 = Curve.getPart(v1, range1[0], range1[1]), - part2 = Curve.getPart(v2, range2[0], range2[1]), - iteration = 0; - while (iteration++ < 20) { - var range, - intersects1 = clipFatLine(part1, part2, range = range2.slice()), - intersects2 = 0; - if (intersects1 === 0) - break; - if (intersects1 > 0) { - range2 = range; - part2 = Curve.getPart(v2, range2[0], range2[1]); - intersects2 = clipFatLine(part2, part1, range = range1.slice()); - if (intersects2 === 0) - break; - if (intersects1 > 0) { - range1 = range; - part1 = Curve.getPart(v1, range1[0], range1[1]); - } - } - if (intersects1 < 0 || intersects2 < 0) { - if (range1[1] - range1[0] > range2[1] - range2[0]) { - var t = (range1[0] + range1[1]) / 2; - addCurveIntersections(v1, v2, curve1, curve2, locations, - [ range1[0], t ], range2, recursion); - addCurveIntersections(v1, v2, curve1, curve2, locations, - [ t, range1[1] ], range2, recursion); - break; - } else { - var t = (range2[0] + range2[1]) / 2; - addCurveIntersections(v1, v2, curve1, curve2, locations, - range1, [ range2[0], t ], recursion); - addCurveIntersections(v1, v2, curve1, curve2, locations, - range1, [ t, range2[1] ], recursion); - break; - } - } - if (Math.abs(range1[1] - range1[0]) <= 0.00001 && - Math.abs(range2[1] - range2[0]) <= 0.00001) { - var t1 = (range1[0] + range1[1]) / 2, - t2 = (range2[0] + range2[1]) / 2; - addLocation(locations, + var q0x = v2[0], q0y = v2[1], q3x = v2[6], q3y = v2[7], + tolerance = 0.00001, + hullEpsilon = 1e-9, + getSignedDistance = Line.getSignedDistance, + d1 = getSignedDistance(q0x, q0y, q3x, q3y, v2[2], v2[3]) || 0, + d2 = getSignedDistance(q0x, q0y, q3x, q3y, v2[4], v2[5]) || 0, + factor = d1 * d2 > 0 ? 3 / 4 : 4 / 9, + dMin = factor * Math.min(0, d1, d2), + dMax = factor * Math.max(0, d1, d2), + dp0 = getSignedDistance(q0x, q0y, q3x, q3y, v1[0], v1[1]), + dp1 = getSignedDistance(q0x, q0y, q3x, q3y, v1[2], v1[3]), + dp2 = getSignedDistance(q0x, q0y, q3x, q3y, v1[4], v1[5]), + dp3 = getSignedDistance(q0x, q0y, q3x, q3y, v1[6], v1[7]), + tMinNew, tMaxNew, tDiff; + if (q0x === q3x && uMax - uMin <= hullEpsilon && recursion > 3) { + tMinNew = (tMax + tMin) / 2; + tMaxNew = tMinNew; + tDiff = 0; + } else { + var hull = getConvexHull(dp0, dp1, dp2, dp3), + top = hull[0], + bottom = hull[1], + tMinClip, tMaxClip; + tMinClip = clipConvexHull(top, bottom, dMin, dMax); + top.reverse(); + bottom.reverse(); + tMaxClip = clipConvexHull(top, bottom, dMin, dMax); + if (tMinClip == null || tMaxClip == null) + return false; + v1 = Curve.getPart(v1, tMinClip, tMaxClip); + tDiff = tMaxClip - tMinClip; + tMinNew = tMax * tMinClip + tMin * (1 - tMinClip); + tMaxNew = tMax * tMaxClip + tMin * (1 - tMaxClip); + } + if (oldTDiff > 0.8 && tDiff > 0.8) { + if (tMaxNew - tMinNew > uMax - uMin) { + var parts = Curve.subdivide(v1, 0.5), + t = tMinNew + (tMaxNew - tMinNew) / 2; + addCurveIntersections( + v2, parts[0], curve2, curve1, locations, include, + uMin, uMax, tMinNew, t, tDiff, !reverse, ++recursion); + addCurveIntersections( + v2, parts[1], curve2, curve1, locations, include, + uMin, uMax, t, tMaxNew, tDiff, !reverse, recursion); + } else { + var parts = Curve.subdivide(v2, 0.5), + t = uMin + (uMax - uMin) / 2; + addCurveIntersections( + parts[0], v1, curve2, curve1, locations, include, + uMin, t, tMinNew, tMaxNew, tDiff, !reverse, ++recursion); + addCurveIntersections( + parts[1], v1, curve2, curve1, locations, include, + t, uMax, tMinNew, tMaxNew, tDiff, !reverse, recursion); + } + } else if (Math.max(uMax - uMin, tMaxNew - tMinNew) < tolerance) { + var t1 = tMinNew + (tMaxNew - tMinNew) / 2, + t2 = uMin + (uMax - uMin) / 2; + if (reverse) { + addLocation(locations, include, + curve2, t2, Curve.evaluate(v2, t2, 0), + curve1, t1, Curve.evaluate(v1, t1, 0)); + } else { + addLocation(locations, include, curve1, t1, Curve.evaluate(v1, t1, 0), curve2, t2, Curve.evaluate(v2, t2, 0)); - break; - } - } - } - - function clipFatLine(v1, v2, range2) { - var p0x = v1[0], p0y = v1[1], p1x = v1[2], p1y = v1[3], - p2x = v1[4], p2y = v1[5], p3x = v1[6], p3y = v1[7], - q0x = v2[0], q0y = v2[1], q1x = v2[2], q1y = v2[3], - q2x = v2[4], q2y = v2[5], q3x = v2[6], q3y = v2[7], - getSignedDistance = Line.getSignedDistance, - d1 = getSignedDistance(p0x, p0y, p3x, p3y, p1x, p1y) || 0, - d2 = getSignedDistance(p0x, p0y, p3x, p3y, p2x, p2y) || 0, - factor = d1 * d2 > 0 ? 3 / 4 : 4 / 9, - dmin = factor * Math.min(0, d1, d2), - dmax = factor * Math.max(0, d1, d2), - dq0 = getSignedDistance(p0x, p0y, p3x, p3y, q0x, q0y), - dq1 = getSignedDistance(p0x, p0y, p3x, p3y, q1x, q1y), - dq2 = getSignedDistance(p0x, p0y, p3x, p3y, q2x, q2y), - dq3 = getSignedDistance(p0x, p0y, p3x, p3y, q3x, q3y); - if (dmin > Math.max(dq0, dq1, dq2, dq3) - || dmax < Math.min(dq0, dq1, dq2, dq3)) - return 0; - var hull = getConvexHull(dq0, dq1, dq2, dq3), - swap; - if (dq3 < dq0) { - swap = dmin; - dmin = dmax; - dmax = swap; - } - var tmaxdmin = -Infinity, - tmin = Infinity, - tmax = -Infinity; - for (var i = 0, l = hull.length; i < l; i++) { - var p1 = hull[i], - p2 = hull[(i + 1) % l]; - if (p2[1] < p1[1]) { - swap = p2; - p2 = p1; - p1 = swap; - } - var x1 = p1[0], - y1 = p1[1], - x2 = p2[0], - y2 = p2[1]; - var inv = (y2 - y1) / (x2 - x1); - if (dmin >= y1 && dmin <= y2) { - var ixdx = x1 + (dmin - y1) / inv; - if (ixdx < tmin) - tmin = ixdx; - if (ixdx > tmaxdmin) - tmaxdmin = ixdx; - } - if (dmax >= y1 && dmax <= y2) { - var ixdx = x1 + (dmax - y1) / inv; - if (ixdx > tmax) - tmax = ixdx; - if (ixdx < tmin) - tmin = 0; - } - } - if (tmin !== Infinity && tmax !== -Infinity) { - var min = Math.min(dmin, dmax), - max = Math.max(dmin, dmax); - if (dq3 > min && dq3 < max) - tmax = 1; - if (dq0 > min && dq0 < max) - tmin = 0; - if (tmaxdmin > tmax) - tmax = 1; - var v2tmin = range2[0], - tdiff = range2[1] - v2tmin; - range2[0] = v2tmin + tmin * tdiff; - range2[1] = v2tmin + tmax * tdiff; - if ((tdiff - (range2[1] - range2[0])) / tdiff >= 0.2) - return 1; - } - if (Curve.getBounds(v1).touches(Curve.getBounds(v2))) - return -1; - return 0; + } + } else { + addCurveIntersections(v2, v1, curve2, curve1, locations, include, + uMin, uMax, tMinNew, tMaxNew, tDiff, !reverse, ++recursion); + } } function getConvexHull(dq0, dq1, dq2, dq3) { @@ -5797,26 +5979,74 @@ p3 = [ 1, dq3 ], getSignedDistance = Line.getSignedDistance, dist1 = getSignedDistance(0, dq0, 1, dq3, 1 / 3, dq1), - dist2 = getSignedDistance(0, dq0, 1, dq3, 2 / 3, dq2); + dist2 = getSignedDistance(0, dq0, 1, dq3, 2 / 3, dq2), + flip = false, + hull; if (dist1 * dist2 < 0) { - return [ p0, p1, p3, p2 ]; - } - var pmax, cross; - if (Math.abs(dist1) > Math.abs(dist2)) { - pmax = p1; - cross = (dq3 - dq2 - (dq3 - dq0) / 3) - * (2 * (dq3 - dq2) - dq3 + dq1) / 3; + hull = [[p0, p1, p3], [p0, p2, p3]]; + flip = dist1 < 0; } else { - pmax = p2; - cross = (dq1 - dq0 + (dq0 - dq3) / 3) - * (-2 * (dq0 - dq1) + dq0 - dq2) / 3; - } - return cross < 0 - ? [ p0, pmax, p3 ] - : [ p0, p1, p2, p3 ]; - } - - function addCurveLineIntersections(v1, v2, curve1, curve2, locations) { + var pmax, cross = 0, + distZero = dist1 === 0 || dist2 === 0; + if (Math.abs(dist1) > Math.abs(dist2)) { + pmax = p1; + cross = (dq3 - dq2 - (dq3 - dq0) / 3) + * (2 * (dq3 - dq2) - dq3 + dq1) / 3; + } else { + pmax = p2; + cross = (dq1 - dq0 + (dq0 - dq3) / 3) + * (-2 * (dq0 - dq1) + dq0 - dq2) / 3; + } + hull = cross < 0 || distZero + ? [[p0, pmax, p3], [p0, p3]] + : [[p0, p1, p2, p3], [p0, p3]]; + flip = dist1 ? dist1 < 0 : dist2 < 0; + } + return flip ? hull.reverse() : hull; + } + + function clipConvexHull(hullTop, hullBottom, dMin, dMax) { + var tProxy, + tVal = null, + px, py, + qx, qy; + for (var i = 0, l = hullBottom.length - 1; i < l; i++) { + py = hullBottom[i][1]; + qy = hullBottom[i + 1][1]; + if (py < qy) { + tProxy = null; + } else if (qy <= dMax) { + px = hullBottom[i][0]; + qx = hullBottom[i + 1][0]; + tProxy = px + (dMax - py) * (qx - px) / (qy - py); + } else { + continue; + } + break; + } + if (hullTop[0][1] <= dMax) + tProxy = hullTop[0][0]; + for (var i = 0, l = hullTop.length - 1; i < l; i++) { + py = hullTop[i][1]; + qy = hullTop[i + 1][1]; + if (py >= dMin) { + tVal = tProxy; + } else if (py > qy) { + tVal = null; + } else if (qy >= dMin) { + px = hullTop[i][0]; + qx = hullTop[i + 1][0]; + tVal = px + (dMin - py) * (qx - px) / (qy - py); + } else { + continue; + } + break; + } + return tVal; + } + + function addCurveLineIntersections(v1, v2, curve1, curve2, locations, + include) { var flip = Curve.isLinear(v1), vc = flip ? v2 : v1, vl = flip ? v1 : v2, @@ -5846,43 +6076,37 @@ var tl = Curve.getParameterOf(rvl, x, 0), t1 = flip ? tl : tc, t2 = flip ? tc : tl; - addLocation(locations, + addLocation(locations, include, curve1, t1, Curve.evaluate(v1, t1, 0), curve2, t2, Curve.evaluate(v2, t2, 0)); } } } - function addLineIntersection(v1, v2, curve1, curve2, locations) { + function addLineIntersection(v1, v2, curve1, curve2, locations, include) { var point = Line.intersect( v1[0], v1[1], v1[6], v1[7], v2[0], v2[1], v2[6], v2[7]); - if (point) - addLocation(locations, curve1, null, point, curve2); + if (point) { + var x = point.x, + y = point.y; + addLocation(locations, include, + curve1, Curve.getParameterOf(v1, x, y), point, + curve2, Curve.getParameterOf(v2, x, y), point); + } } return { statics: { - getIntersections: function(v1, v2, curve1, curve2, locations) { + getIntersections: function(v1, v2, curve1, curve2, locations, include) { var linear1 = Curve.isLinear(v1), - linear2 = Curve.isLinear(v2), - c1p1 = curve1.getPoint1(), - c1p2 = curve1.getPoint2(), - c2p1 = curve2.getPoint1(), - c2p2 = curve2.getPoint2(), - tolerance = 0.00001; - if (c1p1.isClose(c2p1, tolerance)) - addLocation(locations, curve1, 0, c1p1, curve2, 0, c1p1); - if (c1p1.isClose(c2p2, tolerance)) - addLocation(locations, curve1, 0, c1p1, curve2, 1, c1p1); + linear2 = Curve.isLinear(v2); (linear1 && linear2 ? addLineIntersection : linear1 || linear2 ? addCurveLineIntersections - : addCurveIntersections)(v1, v2, curve1, curve2, locations); - if (c1p2.isClose(c2p1, tolerance)) - addLocation(locations, curve1, 1, c1p2, curve2, 0, c1p2); - if (c1p2.isClose(c2p2, tolerance)) - addLocation(locations, curve1, 1, c1p2, curve2, 1, c1p2); + : addCurveIntersections)( + v1, v2, curve1, curve2, locations, include, + 0, 1, 0, 1, 0, false, 0); return locations; } }}; @@ -5890,6 +6114,8 @@ var CurveLocation = Base.extend({ _class: 'CurveLocation', + beans: true, + initialize: function CurveLocation(curve, parameter, point, _curve2, _parameter2, _point2, _distance) { this._id = CurveLocation._id = (CurveLocation._id || 0) + 1; @@ -5904,19 +6130,19 @@ this._distance = _distance; }, - getSegment: function() { + getSegment: function(_preferFirst) { if (!this._segment) { var curve = this.getCurve(), parameter = this.getParameter(); if (parameter === 1) { this._segment = curve._segment2; - } else if (parameter === 0 || arguments[0]) { + } else if (parameter === 0 || _preferFirst) { this._segment = curve._segment1; } else if (parameter == null) { return null; } else { - this._segment = curve.getLength(0, parameter) - < curve.getLength(parameter, 1) + this._segment = curve.getPartLength(0, parameter) + < curve.getPartLength(parameter, 1) ? curve._segment1 : curve._segment2; } @@ -5924,8 +6150,8 @@ return this._segment; }, - getCurve: function() { - if (!this._curve || arguments[0]) { + getCurve: function(_uncached) { + if (!this._curve || _uncached) { this._curve = this._segment1.getCurve(); if (this._curve.getParameterOf(this._point) == null) this._curve = this._segment2.getPrevious().getCurve(); @@ -5956,43 +6182,31 @@ getOffset: function() { var path = this.getPath(); - return path && path._getOffset(this); + return path ? path._getOffset(this) : this.getCurveOffset(); }, getCurveOffset: function() { var curve = this.getCurve(), parameter = this.getParameter(); - return parameter != null && curve && curve.getLength(0, parameter); - }, - - getParameter: function() { - if ((this._parameter == null || arguments[0]) && this._point) { - var curve = this.getCurve(arguments[0] && this._point); + return parameter != null && curve && curve.getPartLength(0, parameter); + }, + + getParameter: function(_uncached) { + if ((this._parameter == null || _uncached) && this._point) { + var curve = this.getCurve(_uncached && this._point); this._parameter = curve && curve.getParameterOf(this._point); } return this._parameter; }, - getPoint: function() { - if ((!this._point || arguments[0]) && this._parameter != null) { + getPoint: function(_uncached) { + if ((!this._point || _uncached) && this._parameter != null) { var curve = this.getCurve(); this._point = curve && curve.getPointAt(this._parameter, true); } return this._point; }, - getTangent: function() { - var parameter = this.getParameter(), - curve = this.getCurve(); - return parameter != null && curve && curve.getTangentAt(parameter, true); - }, - - getNormal: function() { - var parameter = this.getParameter(), - curve = this.getCurve(); - return parameter != null && curve && curve.getNormalAt(parameter, true); - }, - getDistance: function() { return this._distance; }, @@ -6007,6 +6221,17 @@ return curve && curve.split(this.getParameter(true), true); }, + equals: function(loc) { + var isZero = Numerical.isZero; + return this === loc + || loc + && this._curve === loc._curve + && this._curve2 === loc._curve2 + && isZero(this._parameter - loc._parameter) + && isZero(this._parameter2 - loc._parameter2) + || false; + }, + toString: function() { var parts = [], point = this.getPoint(), @@ -6023,7 +6248,16 @@ parts.push('distance: ' + f.number(this._distance)); return '{ ' + parts.join(', ') + ' }'; } -}); +}, Base.each(['Tangent', 'Normal', 'Curvature'], + function(name) { + var get = 'get' + name + 'At'; + this['get' + name] = function() { + var parameter = this.getParameter(), + curve = this.getCurve(); + return parameter != null && curve && curve[get](parameter, true); + }; + }, {} +)); var PathItem = Item.extend({ _class: 'PathItem', @@ -6031,22 +6265,95 @@ initialize: function PathItem() { }, - getIntersections: function(path) { - if (!this.getBounds().touches(path.getBounds())) + getIntersections: function(path, _expand) { + if (this === path) + path = null; + if (path && !this.getBounds().touches(path.getBounds())) return []; var locations = [], curves1 = this.getCurves(), - curves2 = path.getCurves(), - length2 = curves2.length, - values2 = []; + curves2 = path ? path.getCurves() : curves1, + matrix1 = this._matrix.orNullIfIdentity(), + matrix2 = path ? path._matrix.orNullIfIdentity() : matrix1, + length1 = curves1.length, + length2 = path ? curves2.length : length1, + values2 = [], + MIN = 1e-11, + MAX = 1 - 1e-11; for (var i = 0; i < length2; i++) - values2[i] = curves2[i].getValues(); - for (var i = 0, l = curves1.length; i < l; i++) { + values2[i] = curves2[i].getValues(matrix2); + for (var i = 0; i < length1; i++) { var curve1 = curves1[i], - values1 = curve1.getValues(); - for (var j = 0; j < length2; j++) - Curve.getIntersections(values1, values2[j], curve1, curves2[j], - locations); + values1 = path ? curve1.getValues(matrix1) : values2[i]; + if (!path) { + var seg1 = curve1.getSegment1(), + seg2 = curve1.getSegment2(), + h1 = seg1._handleOut, + h2 = seg2._handleIn; + if (new Line(seg1._point.subtract(h1), h1.multiply(2), true) + .intersect(new Line(seg2._point.subtract(h2), + h2.multiply(2), true), false)) { + var parts = Curve.subdivide(values1); + Curve.getIntersections( + parts[0], parts[1], curve1, curve1, locations, + function(loc) { + if (loc._parameter <= MAX) { + loc._parameter /= 2; + loc._parameter2 = 0.5 + loc._parameter2 / 2; + return true; + } + } + ); + } + } + for (var j = path ? 0 : i + 1; j < length2; j++) { + Curve.getIntersections( + values1, values2[j], curve1, curves2[j], locations, + !path && (j === i + 1 || j === length2 - 1 && i === 0) + && function(loc) { + var t = loc._parameter; + return t >= MIN && t <= MAX; + } + ); + } + } + var last = locations.length - 1; + for (var i = last; i >= 0; i--) { + var loc = locations[i], + next = loc._curve.getNext(), + next2 = loc._curve2.getNext(); + if (next && loc._parameter >= MAX) { + loc._parameter = 0; + loc._curve = next; + } + if (next2 && loc._parameter2 >= MAX) { + loc._parameter2 = 0; + loc._curve2 = next2; + } + } + + function compare(loc1, loc2) { + var path1 = loc1.getPath(), + path2 = loc2.getPath(); + return path1 === path2 + ? (loc1.getIndex() + loc1.getParameter()) + - (loc2.getIndex() + loc2.getParameter()) + : path1._id - path2._id; + } + + if (last > 0) { + locations.sort(compare); + for (var i = last; i >= 1; i--) { + if (locations[i].equals(locations[i === 0 ? last : i - 1])) { + locations.splice(i, 1); + last--; + } + } + } + if (_expand) { + for (var i = last; i >= 0; i--) + locations.push(locations[i].getIntersection()); + locations.sort(compare); } return locations; }, @@ -6056,22 +6363,22 @@ var parts = data.match(/[mlhvcsqtaz][^mlhvcsqtaz]*/ig), coords, relative = false, + previous, control, - current = new Point(); - - function getCoord(index, coord, isCurrent) { - var val = parseFloat(coords[index]); + current = new Point(), + start = new Point(); + + function getCoord(index, coord) { + var val = +coords[index]; if (relative) val += current[coord]; - if (isCurrent) - current[coord] = val; return val; } - function getPoint(index, isCurrent) { + function getPoint(index) { return new Point( - getCoord(index, 'x', isCurrent), - getCoord(index + 1, 'y', isCurrent) + getCoord(index, 'x'), + getCoord(index + 1, 'y') ); } @@ -6079,24 +6386,31 @@ for (var i = 0, l = parts.length; i < l; i++) { var part = parts[i], - cmd = part[0], - lower = cmd.toLowerCase(); + command = part[0], + lower = command.toLowerCase(); coords = part.match(/[+-]?(?:\d*\.\d+|\d+\.?)(?:[eE][+-]?\d+)?/g); var length = coords && coords.length; - relative = cmd === lower; + relative = command === lower; + if (previous === 'z' && !/[mz]/.test(lower)) + this.moveTo(current = start); switch (lower) { case 'm': case 'l': + var move = lower === 'm'; + if (move && previous && previous !== 'z') + this.closePath(true); for (var j = 0; j < length; j += 2) - this[j === 0 && lower === 'm' ? 'moveTo' : 'lineTo']( - getPoint(j, true)); + this[j === 0 && move ? 'moveTo' : 'lineTo']( + current = getPoint(j)); control = current; + if (move) + start = current; break; case 'h': case 'v': - var coord = lower == 'h' ? 'x' : 'y'; + var coord = lower === 'h' ? 'x' : 'y'; for (var j = 0; j < length; j++) { - getCoord(j, coord, true); + current[coord] = getCoord(j, coord); this.lineTo(current); } control = current; @@ -6106,37 +6420,49 @@ this.cubicCurveTo( getPoint(j), control = getPoint(j + 2), - getPoint(j + 4, true)); + current = getPoint(j + 4)); } break; case 's': for (var j = 0; j < length; j += 4) { this.cubicCurveTo( - current.multiply(2).subtract(control), + /[cs]/.test(previous) + ? current.multiply(2).subtract(control) + : current, control = getPoint(j), - getPoint(j + 2, true)); + current = getPoint(j + 2)); + previous = lower; } break; case 'q': for (var j = 0; j < length; j += 4) { this.quadraticCurveTo( control = getPoint(j), - getPoint(j + 2, true)); + current = getPoint(j + 2)); } break; case 't': for (var j = 0; j < length; j += 2) { this.quadraticCurveTo( - control = current.multiply(2).subtract(control), - getPoint(j, true)); + control = (/[qt]/.test(previous) + ? current.multiply(2).subtract(control) + : current), + current = getPoint(j)); + previous = lower; } break; case 'a': + for (var j = 0; j < length; j += 7) { + this.arcTo(current = getPoint(j + 5), + new Size(+coords[0], +coords[1]), + +coords[2], +coords[4], +coords[3]); + } break; case 'z': - this.closePath(); + this.closePath(true); break; } + previous = lower; } }, @@ -6145,7 +6471,7 @@ }, _contains: function(point) { - var winding = this._getWinding(point); + var winding = this._getWinding(point, false, true); return !!(this.getWindingRule() === 'evenodd' ? winding & 1 : winding); } @@ -6165,11 +6491,20 @@ ? typeof arg[0] === 'object' ? arg : arguments - : arg && (arg.point !== undefined && arg.size === undefined - || arg.x !== undefined) + : arg && (arg.size === undefined && (arg.x !== undefined + || arg.point !== undefined)) ? arguments : null; - this.setSegments(segments || []); + if (segments && segments.length > 0) { + this.setSegments(segments); + } else { + this._curves = undefined; + this._selectedSegmentState = 0; + if (!segments && typeof arg === 'string') { + this.setPathData(arg); + arg = null; + } + } this._initialize(!segments && arg); }, @@ -6178,28 +6513,34 @@ }, clone: function(insert) { - var copy = this._clone(new Path({ - segments: this._segments, - insert: false - }), insert); + var copy = new Path(Item.NO_INSERT); + copy.setSegments(this._segments); copy._closed = this._closed; if (this._clockwise !== undefined) copy._clockwise = this._clockwise; - return copy; + return this._clone(copy, insert); }, _changed: function _changed(flags) { _changed.base.call(this, flags); - if (flags & 4) { - delete this._length; - delete this._clockwise; - if (this._curves) { + if (flags & 8) { + var parent = this._parent; + if (parent) + parent._currentPath = undefined; + this._length = this._clockwise = undefined; + if (this._curves && !(flags & 16)) { for (var i = 0, l = this._curves.length; i < l; i++) - this._curves[i]._changed(5); - } - } else if (flags & 8) { - delete this._bounds; - } + this._curves[i]._changed(); + } + this._monoCurves = undefined; + } else if (flags & 32) { + this._bounds = undefined; + } + }, + + getStyle: function() { + var parent = this._parent; + return (parent instanceof CompoundPath ? parent : this)._style; }, getSegments: function() { @@ -6210,8 +6551,9 @@ var fullySelected = this.isFullySelected(); this._segments.length = 0; this._selectedSegmentState = 0; - delete this._curves; - this._add(Segment.readAll(segments)); + this._curves = undefined; + if (segments && segments.length > 0) + this._add(Segment.readAll(segments)); if (fullySelected) this.setFullySelected(true); }, @@ -6246,41 +6588,6 @@ return curves[curves.length - 1]; }, - getPathData: function() { - var segments = this._segments, - precision = arguments[0], - f = Formatter.instance, - parts = []; - - function addCurve(seg1, seg2, skipLine) { - var point1 = seg1._point, - point2 = seg2._point, - handle1 = seg1._handleOut, - handle2 = seg2._handleIn; - if (handle1.isZero() && handle2.isZero()) { - if (!skipLine) { - parts.push('L' + f.point(point2, precision)); - } - } else { - var end = point2.subtract(point1); - parts.push('c' + f.point(handle1, precision) - + ' ' + f.point(end.add(handle2), precision) - + ' ' + f.point(end, precision)); - } - } - - if (segments.length === 0) - return ''; - parts.push('M' + f.point(segments[0]._point)); - for (var i = 0, l = segments.length - 1; i < l; i++) - addCurve(segments[i], segments[i + 1], false); - if (this._closed) { - addCurve(segments[segments.length - 1], segments[0], true); - parts.push('z'); - } - return parts.join(''); - }, - isClosed: function() { return this._closed; }, @@ -6294,9 +6601,62 @@ this._curves[length - 1] = new Curve(this, this._segments[length - 1], this._segments[0]); } - this._changed(5); - } - }, + this._changed(25); + } + } +}, { + beans: true, + + getPathData: function(_matrix, _precision) { + var segments = this._segments, + length = segments.length, + f = new Formatter(_precision), + coords = new Array(6), + first = true, + curX, curY, + prevX, prevY, + inX, inY, + outX, outY, + parts = []; + + function addSegment(segment, skipLine) { + segment._transformCoordinates(_matrix, coords, false); + curX = coords[0]; + curY = coords[1]; + if (first) { + parts.push('M' + f.pair(curX, curY)); + first = false; + } else { + inX = coords[2]; + inY = coords[3]; + if (inX === curX && inY === curY + && outX === prevX && outY === prevY) { + if (!skipLine) + parts.push('l' + f.pair(curX - prevX, curY - prevY)); + } else { + parts.push('c' + f.pair(outX - prevX, outY - prevY) + + ' ' + f.pair(inX - prevX, inY - prevY) + + ' ' + f.pair(curX - prevX, curY - prevY)); + } + } + prevX = curX; + prevY = curY; + outX = coords[4]; + outY = coords[5]; + } + + if (length === 0) + return ''; + + for (var i = 0; i < length; i++) + addSegment(segments[i]); + if (this._closed && length > 0) { + addSegment(segments[0], true); + parts.push('z'); + } + return parts.join(''); + } +}, { isEmpty: function() { return this._segments.length === 0; @@ -6310,7 +6670,7 @@ return true; }, - _applyMatrix: function(matrix) { + _transformContent: function(matrix) { var coords = new Array(6); for (var i = 0, l = this._segments.length; i < l; i++) this._segments[i]._transformCoordinates(matrix, coords, true); @@ -6353,7 +6713,7 @@ curves.splice(i, 0, new Curve(this, null, null)); this._adjustCurves(from, to); } - this._changed(5); + this._changed(25); return segs; }, @@ -6366,12 +6726,17 @@ curve._path = this; curve._segment1 = segments[i]; curve._segment2 = segments[i + 1] || segments[0]; + curve._changed(); } if (curve = curves[this._closed && from === 0 ? segments.length - 1 - : from - 1]) + : from - 1]) { curve._segment2 = segments[from] || segments[0]; - if (curve = curves[to]) + curve._changed(); + } + if (curve = curves[to]) { curve._segment1 = segments[to]; + curve._changed(); + } }, _countCurves: function() { @@ -6411,12 +6776,12 @@ return this.removeSegments(index, index + 1)[0] || null; }, - removeSegments: function(from, to) { + removeSegments: function(from, to, _includeCurves) { from = from || 0; to = Base.pick(to, this._segments.length); var segments = this._segments, curves = this._curves, - count = segments.length, + count = segments.length, removed = segments.splice(from, to - from), amount = removed.length; if (!amount) @@ -6425,8 +6790,7 @@ var segment = removed[i]; if (segment._selectionState) this._updateSelection(segment, segment._selectionState, 0); - delete segment._index; - delete segment._path; + segment._index = segment._path = null; } for (var i = from, l = segments.length; i < l; i++) segments[i]._index = i; @@ -6435,20 +6799,38 @@ ? from - 1 : from, curves = curves.splice(index, amount); - if (arguments[2]) + if (_includeCurves) removed._curves = curves.slice(1); this._adjustCurves(index, index); } - this._changed(5); + this._changed(25); return removed; }, clear: '#removeSegments', + getLength: function() { + if (this._length == null) { + var curves = this.getCurves(); + this._length = 0; + for (var i = 0, l = curves.length; i < l; i++) + this._length += curves[i].getLength(); + } + return this._length; + }, + + getArea: function() { + var curves = this.getCurves(); + var area = 0; + for (var i = 0, l = curves.length; i < l; i++) + area += curves[i].getArea(); + return area; + }, + isFullySelected: function() { var length = this._segments.length; return this._selected && length > 0 && this._selectedSegmentState - === length * 4; + === length * 7; }, setFullySelected: function(selected) { @@ -6466,10 +6848,10 @@ _selectSegments: function(selected) { var length = this._segments.length; this._selectedSegmentState = selected - ? length * 4 : 0; + ? length * 7 : 0; for (var i = 0; i < length; i++) this._segments[i]._selectionState = selected - ? 4 : 0; + ? 7 : 0; }, _updateSelection: function(segment, oldState, newState) { @@ -6492,6 +6874,16 @@ this.setSegments(segments); }, + reduce: function() { + var curves = this.getCurves(); + for (var i = curves.length - 1; i >= 0; i--) { + var curve = curves[i]; + if (curve.isLinear() && curve.getLength() === 0) + curve.remove(); + } + return this; + }, + simplify: function(tolerance) { if (this._segments.length > 2) { var fitter = new PathFitter(this, tolerance || 2.5); @@ -6509,13 +6901,14 @@ index = arg.index; parameter = arg.parameter; } - if (parameter >= 1) { + var tolerance = 0.00001; + if (parameter >= 1 - tolerance) { index++; parameter--; } var curves = this.getCurves(); if (index >= 0 && index < curves.length) { - if (parameter > 0) { + if (parameter > tolerance) { curves[index++].divide(parameter, true); } var segs = this.removeSegments(index, this._segments.length, true), @@ -6554,9 +6947,10 @@ segment._handleOut = handleIn; segment._index = i; } - delete this._curves; + this._curves = null; if (this._clockwise !== undefined) this._clockwise = !this._clockwise; + this._changed(9); }, join: function(path) { @@ -6586,120 +6980,14 @@ if (path.closed) this._add([segments[0]]); path.remove(); - first1 = this.getFirstSegment(); - last1 = this.getLastSegment(); - if (last1._point.equals(first1._point)) { - first1.setHandleIn(last1._handleIn); - last1.remove(); - this.setClosed(true); - } - this._changed(5); - return true; - } - return false; - }, - - getLength: function() { - if (this._length == null) { - var curves = this.getCurves(); - this._length = 0; - for (var i = 0, l = curves.length; i < l; i++) - this._length += curves[i].getLength(); - } - return this._length; - }, - - getArea: function() { - var curves = this.getCurves(); - var area = 0; - for (var i = 0, l = curves.length; i < l; i++) - area += curves[i].getArea(); - return area; - }, - - _getOffset: function(location) { - var index = location && location.getIndex(); - if (index != null) { - var curves = this.getCurves(), - offset = 0; - for (var i = 0; i < index; i++) - offset += curves[i].getLength(); - var curve = curves[index]; - return offset + curve.getLength(0, location.getParameter()); - } - return null; - }, - - getLocationOf: function(point) { - point = Point.read(arguments); - var curves = this.getCurves(); - for (var i = 0, l = curves.length; i < l; i++) { - var loc = curves[i].getLocationOf(point); - if (loc) - return loc; - } - return null; - }, - - getLocationAt: function(offset, isParameter) { - var curves = this.getCurves(), - length = 0; - if (isParameter) { - var index = ~~offset; - return curves[index].getLocationAt(offset - index, true); - } - for (var i = 0, l = curves.length; i < l; i++) { - var start = length, - curve = curves[i]; - length += curve.getLength(); - if (length >= offset) { - return curve.getLocationAt(offset - start); - } - } - if (offset <= this.getLength()) - return new CurveLocation(curves[curves.length - 1], 1); - return null; - }, - - getPointAt: function(offset, isParameter) { - var loc = this.getLocationAt(offset, isParameter); - return loc && loc.getPoint(); - }, - - getTangentAt: function(offset, isParameter) { - var loc = this.getLocationAt(offset, isParameter); - return loc && loc.getTangent(); - }, - - getNormalAt: function(offset, isParameter) { - var loc = this.getLocationAt(offset, isParameter); - return loc && loc.getNormal(); - }, - - getNearestLocation: function(point) { - point = Point.read(arguments); - var curves = this.getCurves(), - minDist = Infinity, - minLoc = null; - for (var i = 0, l = curves.length; i < l; i++) { - var loc = curves[i].getNearestLocation(point); - if (loc._distance < minDist) { - minDist = loc._distance; - minLoc = loc; - } - } - return minLoc; - }, - - getNearestPoint: function(point) { - point = Point.read(arguments); - return this.getNearestLocation(point).getPoint(); - }, - - getStyle: function() { - var parent = this._parent; - return (parent && parent._type === 'compound-path' - ? parent : this)._style; + } + var first = this.getFirstSegment(), + last = this.getLastSegment(); + if (first !== last && first._point.equals(last._point)) { + first.setHandleIn(last._handleIn); + last.remove(); + this.setClosed(true); + } }, toShape: function(insert) { @@ -6769,139 +7057,108 @@ return null; }, - _getWinding: function(point) { - var closed = this._closed; - if (!closed && !this.hasFill() - || !this._getBounds('getRoughBounds')._containsPoint(point)) - return 0; - var curves = this.getCurves(), + _hitTestSelf: function(point, options) { + var that = this, + style = this.getStyle(), segments = this._segments, - winding = 0, - roots1 = [], - roots2 = [], - last = (closed - ? curves[curves.length - 1] - : new Curve(segments[segments.length - 1]._point, - segments[0]._point)).getValues(), - previous = last; - for (var i = 0, l = curves.length; i < l; i++) { - var curve = curves[i].getValues(), - x = curve[0], - y = curve[1]; - if (!(x === curve[2] && y === curve[3] && x === curve[4] - && y === curve[5] && x === curve[6] && y === curve[7])) { - winding += Curve._getWinding(curve, previous, point.x, point.y, - roots1, roots2); - previous = curve; - } - } - if (!closed) { - winding += Curve._getWinding(last, previous, point.x, point.y, - roots1, roots2); - } - return winding; - }, - - _hitTest: function(point, options) { - var style = this.getStyle(), - segments = this._segments, + numSegments = segments.length, closed = this._closed, - tolerance = options.tolerance, - radius = 0, join, cap, miterLimit, - that = this, - area, loc, res; - - if (options.stroke) { - radius = style.getStrokeWidth() / 2; + tolerancePadding = options._tolerancePadding, + strokePadding = tolerancePadding, + join, cap, miterLimit, + area, loc, res, + hitStroke = options.stroke && style.hasStroke(), + hitFill = options.fill && style.hasFill(), + hitCurves = options.curves, + radius = hitStroke + ? style.getStrokeWidth() / 2 + : hitFill && options.tolerance > 0 || hitCurves + ? 0 : null; + if (radius !== null) { if (radius > 0) { join = style.getStrokeJoin(); cap = style.getStrokeCap(); miterLimit = radius * style.getMiterLimit(); + strokePadding = tolerancePadding.add(new Point(radius, radius)); } else { join = cap = 'round'; } - radius += tolerance; - } - - function checkPoint(seg, pt, name) { - if (point.getDistance(pt) < tolerance) - return new HitResult(name, that, { segment: seg, point: pt }); + } + + function isCloseEnough(pt, padding) { + return point.subtract(pt).divide(padding).length <= 1; + } + + function checkSegmentPoint(seg, pt, name) { + if (!options.selected || pt.isSelected()) { + var anchor = seg._point; + if (pt !== anchor) + pt = pt.add(anchor); + if (isCloseEnough(pt, strokePadding)) { + return new HitResult(name, that, { + segment: seg, + point: pt + }); + } + } } function checkSegmentPoints(seg, ends) { - var pt = seg._point; - return (ends || options.segments) && checkPoint(seg, pt, 'segment') + return (ends || options.segments) + && checkSegmentPoint(seg, seg._point, 'segment') || (!ends && options.handles) && ( - checkPoint(seg, pt.add(seg._handleIn), 'handle-in') || - checkPoint(seg, pt.add(seg._handleOut), 'handle-out')); - } - - function addAreaPoint(point) { - area.push(point); - } - - function getAreaCurve(index) { - var p1 = area[index], - p2 = area[(index + 1) % area.length]; - return [p1.x, p1.y, p1.x, p1.y, p2.x, p2.y, p2.x ,p2.y]; - } - - function isInArea(point) { - var length = area.length, - previous = getAreaCurve(length - 1), - roots1 = [], - roots2 = [], - winding = 0; - for (var i = 0; i < length; i++) { - var curve = getAreaCurve(i); - winding += Curve._getWinding(curve, previous, point.x, point.y, - roots1, roots2); - previous = curve; - } - return !!winding; + checkSegmentPoint(seg, seg._handleIn, 'handle-in') || + checkSegmentPoint(seg, seg._handleOut, 'handle-out')); + } + + function addToArea(point) { + area.add(point); } function checkSegmentStroke(segment) { if (join !== 'round' || cap !== 'round') { - area = []; + area = new Path({ internal: true, closed: true }); if (closed || segment._index > 0 - && segment._index < segments.length - 1) { - if (join !== 'round' && (segment._handleIn.isZero() + && segment._index < numSegments - 1) { + if (join !== 'round' && (segment._handleIn.isZero() || segment._handleOut.isZero())) - Path._addSquareJoin(segment, join, radius, miterLimit, - addAreaPoint, true); + Path._addBevelJoin(segment, join, radius, miterLimit, + addToArea, true); } else if (cap !== 'round') { - Path._addSquareCap(segment, cap, radius, addAreaPoint, true); - } - if (area.length > 0) - return isInArea(point); - } - return point.getDistance(segment._point) <= radius; + Path._addSquareCap(segment, cap, radius, addToArea, true); + } + if (!area.isEmpty()) { + var loc; + return area.contains(point) + || (loc = area.getNearestLocation(point)) + && isCloseEnough(loc.getPoint(), tolerancePadding); + } + } + return isCloseEnough(segment._point, strokePadding); } if (options.ends && !options.segments && !closed) { if (res = checkSegmentPoints(segments[0], true) - || checkSegmentPoints(segments[segments.length - 1], true)) + || checkSegmentPoints(segments[numSegments - 1], true)) return res; } else if (options.segments || options.handles) { - for (var i = 0, l = segments.length; i < l; i++) { + for (var i = 0; i < numSegments; i++) if (res = checkSegmentPoints(segments[i])) return res; - } - } - if (radius > 0) { + } + if (radius !== null) { loc = this.getNearestLocation(point); if (loc) { var parameter = loc.getParameter(); - if (parameter === 0 || parameter === 1) { + if (parameter === 0 || parameter === 1 && numSegments > 1) { if (!checkSegmentStroke(loc.getSegment())) loc = null; - } else if (loc._distance > radius) { + } else if (!isCloseEnough(loc.getPoint(), strokePadding)) { loc = null; } } - if (!loc && join === 'miter') { - for (var i = 0, l = segments.length; i < l; i++) { + if (!loc && join === 'miter' && numSegments > 1) { + for (var i = 0; i < numSegments; i++) { var segment = segments[i]; if (point.getDistance(segment._point) <= miterLimit && checkSegmentStroke(segment)) { @@ -6911,14 +7168,106 @@ } } } - return !loc && options.fill && this.hasFill() && this._contains(point) - ? new HitResult('fill', this) - : loc - ? new HitResult('stroke', this, { location: loc }) - : null; - } - -}, new function() { + return !loc && hitFill && this._contains(point) + || loc && !hitStroke && !hitCurves + ? new HitResult('fill', this) + : loc + ? new HitResult(hitStroke ? 'stroke' : 'curve', this, { + location: loc, + point: loc.getPoint() + }) + : null; + } + +}, { + beans: false, + + _getOffset: function(location) { + var index = location && location.getIndex(); + if (index != null) { + var curves = this.getCurves(), + offset = 0; + for (var i = 0; i < index; i++) + offset += curves[i].getLength(); + var curve = curves[index], + parameter = location.getParameter(); + if (parameter > 0) + offset += curve.getPartLength(0, parameter); + return offset; + } + return null; + }, + + getLocationOf: function() { + var point = Point.read(arguments), + curves = this.getCurves(); + for (var i = 0, l = curves.length; i < l; i++) { + var loc = curves[i].getLocationOf(point); + if (loc) + return loc; + } + return null; + }, + + getOffsetOf: function() { + var loc = this.getLocationOf.apply(this, arguments); + return loc ? loc.getOffset() : null; + }, + + getLocationAt: function(offset, isParameter) { + var curves = this.getCurves(), + length = 0; + if (isParameter) { + var index = ~~offset; + return curves[index].getLocationAt(offset - index, true); + } + for (var i = 0, l = curves.length; i < l; i++) { + var start = length, + curve = curves[i]; + length += curve.getLength(); + if (length > offset) { + return curve.getLocationAt(offset - start); + } + } + if (offset <= this.getLength()) + return new CurveLocation(curves[curves.length - 1], 1); + return null; + }, + + getPointAt: function(offset, isParameter) { + var loc = this.getLocationAt(offset, isParameter); + return loc && loc.getPoint(); + }, + + getTangentAt: function(offset, isParameter) { + var loc = this.getLocationAt(offset, isParameter); + return loc && loc.getTangent(); + }, + + getNormalAt: function(offset, isParameter) { + var loc = this.getLocationAt(offset, isParameter); + return loc && loc.getNormal(); + }, + + getNearestLocation: function() { + var point = Point.read(arguments), + curves = this.getCurves(), + minDist = Infinity, + minLoc = null; + for (var i = 0, l = curves.length; i < l; i++) { + var loc = curves[i].getNearestLocation(point); + if (loc._distance < minDist) { + minDist = loc._distance; + minLoc = loc; + } + } + return minLoc; + }, + + getNearestPoint: function() { + return this.getNearestLocation.apply(this, arguments).getPoint(); + } +}, new function() { function drawHandles(ctx, segments, matrix, size) { var half = size / 2; @@ -6942,24 +7291,19 @@ var segment = segments[i]; segment._transformCoordinates(matrix, coords, false); var state = segment._selectionState, - selected = state & 4, pX = coords[0], pY = coords[1]; - if (selected || (state & 1)) + if (state & 1) drawHandle(2); - if (selected || (state & 2)) + if (state & 2) drawHandle(4); - ctx.save(); - ctx.beginPath(); - ctx.rect(pX - half, pY - half, size, size); - ctx.fill(); - if (!selected) { - ctx.beginPath(); - ctx.rect(pX - half + 1, pY - half + 1, size - 2, size - 2); + ctx.fillRect(pX - half, pY - half, size, size); + if (!(state & 4)) { + var fillStyle = ctx.fillStyle; ctx.fillStyle = '#ffffff'; - ctx.fill(); - } - ctx.restore(); + ctx.fillRect(pX - half + 1, pY - half + 1, size - 2, size - 2); + ctx.fillStyle = fillStyle; + } } } @@ -6973,8 +7317,7 @@ inX, inY, outX, outY; - function drawSegment(i) { - var segment = segments[i]; + function drawSegment(segment) { if (matrix) { segment._transformCoordinates(matrix, coords, false); curX = coords[0]; @@ -6996,7 +7339,8 @@ inX = curX + handle._x; inY = curY + handle._y; } - if (inX == curX && inY == curY && outX == prevX && outY == prevY) { + if (inX === curX && inY === curY + && outX === prevX && outY === prevY) { ctx.lineTo(curX, curY); } else { ctx.bezierCurveTo(outX, outY, inX, inY, curX, curY); @@ -7015,35 +7359,40 @@ } for (var i = 0; i < length; i++) - drawSegment(i); - if (path._closed && length > 1) - drawSegment(0); + drawSegment(segments[i]); + if (path._closed && length > 0) + drawSegment(segments[0]); } return { - _draw: function(ctx, param) { - var clip = param.clip, - compound = param.compound; - if (!compound) - ctx.beginPath(); - - var style = this.getStyle(), + _draw: function(ctx, param, strokeMatrix) { + var dontStart = param.dontStart, + dontPaint = param.dontFinish || param.clip, + style = this.getStyle(), hasFill = style.hasFill(), hasStroke = style.hasStroke(), dashArray = style.getDashArray(), dashLength = !paper.support.nativeDash && hasStroke && dashArray && dashArray.length; + if (!dontStart) + ctx.beginPath(); + + if (!dontStart && this._currentPath) { + ctx.currentPath = this._currentPath; + } else if (hasFill || hasStroke && !dashLength || dontPaint) { + drawSegments(ctx, this, strokeMatrix); + if (this._closed) + ctx.closePath(); + if (!dontStart) + this._currentPath = ctx.currentPath; + } + function getOffset(i) { return dashArray[((i % dashLength) + dashLength) % dashLength]; } - if (hasFill || hasStroke && !dashLength || compound || clip) - drawSegments(ctx, this); - if (this._closed) - ctx.closePath(); - - if (!clip && !compound && (hasFill || hasStroke)) { + if (!dontPaint && (hasFill || hasStroke)) { this._setStyles(ctx); if (hasFill) { ctx.fill(style.getWindingRule()); @@ -7051,8 +7400,9 @@ } if (hasStroke) { if (dashLength) { - ctx.beginPath(); - var flattener = new PathFlattener(this), + if (!dontStart) + ctx.beginPath(); + var flattener = new PathFlattener(this, strokeMatrix), length = flattener.length, from = -style.getDashOffset(), to, i = 0; @@ -7077,16 +7427,15 @@ ctx.beginPath(); drawSegments(ctx, this, matrix); ctx.stroke(); - drawHandles(ctx, this._segments, matrix, - this._project.options.handleSize || 4); + drawHandles(ctx, this._segments, matrix, paper.settings.handleSize); } }; -}, new function() { +}, new function() { function getFirstControlPoints(rhs) { var n = rhs.length, - x = [], - tmp = [], + x = [], + tmp = [], b = 2; x[0] = rhs[0] / b; for (var i = 1; i < n; i++) { @@ -7174,19 +7523,20 @@ } } }; -}, new function() { +}, new function() { function getCurrentSegment(that) { var segments = that._segments; - if (segments.length == 0) + if (segments.length === 0) throw new Error('Use a moveTo() command first'); return segments[segments.length - 1]; } return { moveTo: function() { - if (this._segments.length === 1) + var segments = this._segments; + if (segments.length === 1) this.removeSegment(0); - if (!this._segments.length) + if (!segments.length) this._add([ new Segment(Point.read(arguments)) ]); }, @@ -7235,52 +7585,115 @@ arcTo: function() { var current = getCurrentSegment(this), from = current._point, + to = Point.read(arguments), through, - to = Point.read(arguments), - clockwise = Base.pick(Base.peek(arguments), true); + peek = Base.peek(arguments), + clockwise = Base.pick(peek, true), + center, extent, vector, matrix; if (typeof clockwise === 'boolean') { var middle = from.add(to).divide(2), through = middle.add(middle.subtract(from).rotate( clockwise ? -90 : 90)); - } else { + } else if (Base.remain(arguments) <= 2) { through = to; to = Point.read(arguments); - } - var l1 = new Line(from.add(through).divide(2), - through.subtract(from).rotate(90), true), - l2 = new Line(through.add(to).divide(2), - to.subtract(through).rotate(90), true), - center = l1.intersect(l2, true), - line = new Line(from, to), - throughSide = line.getSide(through); - if (!center) { - if (!throughSide) + } else { + var radius = Size.read(arguments); + if (radius.isZero()) return this.lineTo(to); - throw new Error('Cannot put an arc through the given points: ' - + [from, through, to]); - } - var vector = from.subtract(center), - extent = vector.getDirectedAngle(to.subtract(center)), - centerSide = line.getSide(center); - if (centerSide == 0) { - extent = throughSide * Math.abs(extent); - } else if (throughSide == centerSide) { - extent -= 360 * (extent < 0 ? -1 : 1); + var rotation = Base.read(arguments), + clockwise = !!Base.read(arguments), + large = !!Base.read(arguments), + middle = from.add(to).divide(2), + pt = from.subtract(middle).rotate(-rotation), + x = pt.x, + y = pt.y, + abs = Math.abs, + EPSILON = 1e-11, + rx = abs(radius.width), + ry = abs(radius.height), + rxSq = rx * rx, + rySq = ry * ry, + xSq = x * x, + ySq = y * y; + var factor = Math.sqrt(xSq / rxSq + ySq / rySq); + if (factor > 1) { + rx *= factor; + ry *= factor; + rxSq = rx * rx; + rySq = ry * ry; + } + factor = (rxSq * rySq - rxSq * ySq - rySq * xSq) / + (rxSq * ySq + rySq * xSq); + if (abs(factor) < EPSILON) + factor = 0; + if (factor < 0) + throw new Error( + 'Cannot create an arc with the given arguments'); + center = new Point(rx * y / ry, -ry * x / rx) + .multiply((large === clockwise ? -1 : 1) + * Math.sqrt(factor)) + .rotate(rotation).add(middle); + matrix = new Matrix().translate(center).rotate(rotation) + .scale(rx, ry); + vector = matrix._inverseTransform(from); + extent = vector.getDirectedAngle(matrix._inverseTransform(to)); + if (!clockwise && extent > 0) + extent -= 360; + else if (clockwise && extent < 0) + extent += 360; + } + if (through) { + var l1 = new Line(from.add(through).divide(2), + through.subtract(from).rotate(90), true), + l2 = new Line(through.add(to).divide(2), + to.subtract(through).rotate(90), true), + line = new Line(from, to), + throughSide = line.getSide(through); + center = l1.intersect(l2, true); + if (!center) { + if (!throughSide) + return this.lineTo(to); + throw new Error( + 'Cannot create an arc with the given arguments'); + } + vector = from.subtract(center); + extent = vector.getDirectedAngle(to.subtract(center)); + var centerSide = line.getSide(center); + if (centerSide === 0) { + extent = throughSide * Math.abs(extent); + } else if (throughSide === centerSide) { + extent += extent < 0 ? 360 : -360; + } } var ext = Math.abs(extent), - count = ext >= 360 ? 4 : Math.ceil(ext / 90), + count = ext >= 360 ? 4 : Math.ceil(ext / 90), inc = extent / count, half = inc * Math.PI / 360, z = 4 / 3 * Math.sin(half) / (1 + Math.cos(half)), segments = []; for (var i = 0; i <= count; i++) { - var pt = i < count ? center.add(vector) : to; - var out = i < count ? vector.rotate(90).multiply(z) : null; - if (i == 0) { + var pt = to, + out = null; + if (i < count) { + out = vector.rotate(90).multiply(z); + if (matrix) { + pt = matrix._transformPoint(vector); + out = matrix._transformPoint(vector.add(out)) + .subtract(pt); + } else { + pt = center.add(vector); + } + } + if (i === 0) { current.setHandleOut(out); } else { - segments.push( - new Segment(pt, vector.rotate(-90).multiply(z), out)); + var _in = vector.rotate(-90).multiply(z); + if (matrix) { + _in = matrix._transformPoint(vector.add(_in)) + .subtract(pt); + } + segments.push(new Segment(pt, _in, out)); } vector = vector.rotate(inc); } @@ -7328,17 +7741,13 @@ } }, - closePath: function() { - var first = this.getFirstSegment(), - last = this.getLastSegment(); - if (first._point.equals(last._point)) { - first.setHandleIn(last._handleIn); - last.remove(); - } + closePath: function(join) { this.setClosed(true); + if (join) + this.join(); } }; -}, { +}, { _getBounds: function(getter, matrix) { return Path[getter](this._segments, this._closed, this.getStyle(), @@ -7363,18 +7772,18 @@ return new Rectangle(); var coords = new Array(6), prevCoords = first._transformCoordinates(matrix, new Array(6), false), - min = prevCoords.slice(0, 2), - max = min.slice(), + min = prevCoords.slice(0, 2), + max = min.slice(), roots = new Array(2); function processSegment(segment) { segment._transformCoordinates(matrix, coords, false); for (var i = 0; i < 2; i++) { Curve._addBounds( - prevCoords[i], - prevCoords[i + 4], - coords[i + 2], - coords[i], + prevCoords[i], + prevCoords[i + 4], + coords[i + 2], + coords[i], i, strokePadding ? strokePadding[i] : 0, min, max, roots); } var tmp = prevCoords; @@ -7390,29 +7799,11 @@ }, getStrokeBounds: function(segments, closed, style, matrix) { - function getPenPadding(radius, matrix) { - if (!matrix) - return [radius, radius]; - var mx = matrix.shiftless(), - hor = mx.transform(new Point(radius, 0)), - ver = mx.transform(new Point(0, radius)), - phi = hor.getAngleInRadians(), - a = hor.getLength(), - b = ver.getLength(); - var sin = Math.sin(phi), - cos = Math.cos(phi), - tan = Math.tan(phi), - tx = -Math.atan(b * tan / a), - ty = Math.atan(b / (tan * a)); - return [Math.abs(a * Math.cos(tx) * cos - b * Math.sin(tx) * sin), - Math.abs(b * Math.sin(ty) * cos + a * Math.cos(ty) * sin)]; - } - if (!style.hasStroke()) return Path.getBounds(segments, closed, style, matrix); var length = segments.length - (closed ? 0 : 1), radius = style.getStrokeWidth() / 2, - padding = getPenPadding(radius, matrix), + padding = Path._getPenPadding(radius, matrix), bounds = Path.getBounds(segments, closed, style, matrix, padding), join = style.getStrokeJoin(), cap = style.getStrokeCap(), @@ -7424,25 +7815,27 @@ ? matrix._transformPoint(point, point) : point); } + function addRound(segment) { + bounds = bounds.unite(joinBounds.setCenter(matrix + ? matrix._transformPoint(segment._point) : segment._point)); + } + function addJoin(segment, join) { - if (join === 'round' || !segment._handleIn.isZero() - && !segment._handleOut.isZero()) { - bounds = bounds.unite(joinBounds.setCenter(matrix - ? matrix._transformPoint(segment._point) : segment._point)); + var handleIn = segment._handleIn, + handleOut = segment._handleOut; + if (join === 'round' || !handleIn.isZero() && !handleOut.isZero() + && handleIn.isColinear(handleOut)) { + addRound(segment); } else { - Path._addSquareJoin(segment, join, radius, miterLimit, add); + Path._addBevelJoin(segment, join, radius, miterLimit, add); } } function addCap(segment, cap) { - switch (cap) { - case 'round': - addJoin(segment, cap); - break; - case 'butt': - case 'square': - Path._addSquareCap(segment, cap, radius, add); - break; + if (cap === 'round') { + addRound(segment); + } else { + Path._addSquareCap(segment, cap, radius, add); } } @@ -7450,14 +7843,32 @@ addJoin(segments[i], join); if (closed) { addJoin(segments[0], join); - } else { + } else if (length > 0) { addCap(segments[0], cap); addCap(segments[segments.length - 1], cap); } return bounds; }, - _addSquareJoin: function(segment, join, radius, miterLimit, addPoint, area) { + _getPenPadding: function(radius, matrix) { + if (!matrix) + return [radius, radius]; + var mx = matrix.shiftless(), + hor = mx.transform(new Point(radius, 0)), + ver = mx.transform(new Point(0, radius)), + phi = hor.getAngleInRadians(), + a = hor.getLength(), + b = ver.getLength(); + var sin = Math.sin(phi), + cos = Math.cos(phi), + tan = Math.tan(phi), + tx = -Math.atan(b * tan / a), + ty = Math.atan(b / (tan * a)); + return [Math.abs(a * Math.cos(tx) * cos - b * Math.sin(tx) * sin), + Math.abs(b * Math.sin(ty) * cos + a * Math.cos(ty) * sin)]; + }, + + _addBevelJoin: function(segment, join, radius, miterLimit, addPoint, area) { var curve2 = segment.getCurve(), curve1 = curve2.getPrevious(), point = curve2.getPointAt(0, true), @@ -7498,7 +7909,7 @@ addPoint(point.add(normal)); } if (cap === 'square') - point = point.add(normal.rotate(loc.getParameter() == 0 ? -90 : 90)); + point = point.add(normal.rotate(loc.getParameter() === 0 ? -90 : 90)); addPoint(point.add(normal)); addPoint(point.subtract(normal)); }, @@ -7510,19 +7921,19 @@ x2 = -x1, y1 = x1, y2 = x2; - strokePadding = strokePadding / 2 || 0; - joinPadding = joinPadding / 2 || 0; for (var i = 0, l = segments.length; i < l; i++) { var segment = segments[i]; segment._transformCoordinates(matrix, coords, false); for (var j = 0; j < 6; j += 2) { - var padding = j == 0 ? joinPadding : strokePadding, + var padding = j === 0 ? joinPadding : strokePadding, + paddingX = padding ? padding[0] : 0, + paddingY = padding ? padding[1] : 0, x = coords[j], y = coords[j + 1], - xn = x - padding, - xx = x + padding, - yn = y - padding, - yx = y + padding; + xn = x - paddingX, + xx = x + paddingX, + yn = y - paddingY, + yx = y + paddingY; if (xn < x1) x1 = xn; if (xx > x2) x2 = xx; if (yn < y1) y1 = yn; @@ -7533,24 +7944,23 @@ }, getRoughBounds: function(segments, closed, style, matrix) { - var strokeWidth = style.getStrokeColor() ? style.getStrokeWidth() : 0, - joinWidth = strokeWidth; - if (strokeWidth === 0) { - strokeWidth = 0.00001; - } else { + var strokeRadius = style.hasStroke() ? style.getStrokeWidth() / 2 : 0, + joinRadius = strokeRadius; + if (strokeRadius > 0) { if (style.getStrokeJoin() === 'miter') - joinWidth = strokeWidth * style.getMiterLimit(); + joinRadius = strokeRadius * style.getMiterLimit(); if (style.getStrokeCap() === 'square') - joinWidth = Math.max(joinWidth, strokeWidth * Math.sqrt(2)); + joinRadius = Math.max(joinRadius, strokeRadius * Math.sqrt(2)); } return Path.getHandleBounds(segments, closed, style, matrix, - strokeWidth, joinWidth); + Path._getPenPadding(strokeRadius, matrix), + Path._getPenPadding(joinRadius, matrix)); } }}); Path.inject({ statics: new function() { - var kappa = Numerical.KAPPA, + var kappa = 0.5522847498307936, ellipseSegments = [ new Segment([-1, 0], [0, kappa ], [0, -kappa]), new Segment([0, -1], [-kappa, 0], [kappa, 0 ]), @@ -7558,9 +7968,16 @@ new Segment([0, 1], [kappa, 0 ], [-kappa, 0]) ]; + function createPath(segments, closed, args) { + var props = Base.getNamed(args), + path = new Path(props && props.insert === false && Item.NO_INSERT); + path._add(segments); + path._closed = closed; + return path.set(props); + } + function createEllipse(center, radius, args) { - var path = new Path(), - segments = new Array(4); + var segments = new Array(4); for (var i = 0; i < 4; i++) { var segment = ellipseSegments[i]; segments[i] = new Segment( @@ -7569,17 +7986,15 @@ segment._handleOut.multiply(radius) ); } - path._add(segments); - path._closed = true; - return path.set(Base.getNamed(args)); + return createPath(segments, true, args); } return { Line: function() { - return new Path( - Point.readNamed(arguments, 'from'), - Point.readNamed(arguments, 'to') - ).set(Base.getNamed(arguments)); + return createPath([ + new Segment(Point.readNamed(arguments, 'from')), + new Segment(Point.readNamed(arguments, 'to')) + ], false, arguments); }, Circle: function() { @@ -7590,27 +8005,27 @@ Rectangle: function() { var rect = Rectangle.readNamed(arguments, 'rectangle'), - radius = Size.readNamed(arguments, 'radius', 0, 0, + radius = Size.readNamed(arguments, 'radius', 0, { readNull: true }), bl = rect.getBottomLeft(true), tl = rect.getTopLeft(true), tr = rect.getTopRight(true), - br = rect.getBottomRight(true); - path = new Path(); + br = rect.getBottomRight(true), + segments; if (!radius || radius.isZero()) { - path._add([ + segments = [ new Segment(bl), new Segment(tl), new Segment(tr), new Segment(br) - ]); + ]; } else { radius = Size.min(radius, rect.getSize(true).divide(2)); var rx = radius.width, ry = radius.height, hx = rx * kappa, hy = ry * kappa; - path._add([ + segments = [ new Segment(bl.add(rx, 0), null, [-hx, 0]), new Segment(bl.subtract(0, ry), [0, hy]), new Segment(tl.add(0, ry), null, [0, -hy]), @@ -7619,10 +8034,9 @@ new Segment(tr.add(0, ry), [0, -hy], null), new Segment(br.subtract(0, ry), null, [0, hy]), new Segment(br.subtract(rx, 0), [hx, 0]) - ]); - } - path._closed = true; - return path.set(Base.getNamed(arguments)); + ]; + } + return createPath(segments, true, arguments); }, RoundRectangle: '#Rectangle', @@ -7638,29 +8052,27 @@ var from = Point.readNamed(arguments, 'from'), through = Point.readNamed(arguments, 'through'), to = Point.readNamed(arguments, 'to'), - path = new Path(); + props = Base.getNamed(arguments), + path = new Path(props && props.insert === false + && Item.NO_INSERT); path.moveTo(from); path.arcTo(through, to); - return path.set(Base.getNamed(arguments)); + return path.set(props); }, RegularPolygon: function() { var center = Point.readNamed(arguments, 'center'), sides = Base.readNamed(arguments, 'sides'), radius = Base.readNamed(arguments, 'radius'), - path = new Path(), step = 360 / sides, three = !(sides % 3), vector = new Point(0, three ? -radius : radius), offset = three ? -1 : 0.5, segments = new Array(sides); - for (var i = 0; i < sides; i++) { + for (var i = 0; i < sides; i++) segments[i] = new Segment(center.add( vector.rotate((i + offset) * step))); - } - path._add(segments); - path._closed = true; - return path.set(Base.getNamed(arguments)); + return createPath(segments, true, arguments); }, Star: function() { @@ -7668,17 +8080,13 @@ points = Base.readNamed(arguments, 'points') * 2, radius1 = Base.readNamed(arguments, 'radius1'), radius2 = Base.readNamed(arguments, 'radius2'), - path = new Path(), step = 360 / points, vector = new Point(0, -1), segments = new Array(points); - for (var i = 0; i < points; i++) { - segments[i] = new Segment(center.add( - vector.rotate(step * i).multiply(i % 2 ? radius2 : radius1))); - } - path._add(segments); - path._closed = true; - return path.set(Base.getNamed(arguments)); + for (var i = 0; i < points; i++) + segments[i] = new Segment(center.add(vector.rotate(step * i) + .multiply(i % 2 ? radius2 : radius1))); + return createPath(segments, true, arguments); } }; }}); @@ -7692,12 +8100,17 @@ initialize: function CompoundPath(arg) { this._children = []; this._namedChildren = {}; - if (!this._initialize(arg)) - this.addChildren(Array.isArray(arg) ? arg : arguments); + if (!this._initialize(arg)) { + if (typeof arg === 'string') { + this.setPathData(arg); + } else { + this.addChildren(Array.isArray(arg) ? arg : arguments); + } + } }, insertChildren: function insertChildren(index, items, _preserve) { - items = insertChildren.base.call(this, index, items, _preserve, 'path'); + items = insertChildren.base.call(this, index, items, _preserve, Path); for (var i = 0, l = !_preserve && items && items.length; i < l; i++) { var item = items[i]; if (item._clockwise === undefined) @@ -7723,7 +8136,7 @@ }, setClockwise: function(clockwise) { - if (this.isClockwise() != !!clockwise) + if (this.isClockwise() !== !!clockwise) this.reverse(); }, @@ -7741,7 +8154,7 @@ var children = this._children, curves = []; for (var i = 0, l = children.length; i < l; i++) - curves = curves.concat(children[i].getCurves()); + curves.push.apply(curves, children[i].getCurves()); return curves; }, @@ -7761,49 +8174,42 @@ for (var i = 0, l = children.length; i < l; i++) area += children[i].getArea(); return area; - }, - - getPathData: function() { + } +}, { + beans: true, + + getPathData: function(_matrix, _precision) { var children = this._children, paths = []; - for (var i = 0, l = children.length; i < l; i++) - paths.push(children[i].getPathData(arguments[0])); + for (var i = 0, l = children.length; i < l; i++) { + var child = children[i], + mx = child._matrix; + paths.push(child.getPathData(_matrix && !mx.isIdentity() + ? _matrix.chain(mx) : mx, _precision)); + } return paths.join(' '); - }, - - _getWinding: function(point) { - var children = this._children, - winding = 0; - for (var i = 0, l = children.length; i < l; i++) - winding += children[i]._getWinding(point); - return winding; - }, - - _hitTest : function _hitTest(point, options) { - var res = _hitTest.base.call(this, point, - new Base(options, { fill: false })); - if (!res) { - if (options.compoundChildren) { - var children = this._children; - for (var i = children.length - 1; i >= 0 && !res; i--) - res = children[i]._hitTest(point, options); - } else if (options.fill && this.hasFill() - && this._contains(point)) { - res = new HitResult('fill', this); - } - } - return res; - }, - - _draw: function(ctx, param) { + } +}, { + _getChildHitTestOptions: function(options) { + return options.class === Path || options.type === 'path' + ? options + : new Base(options, { fill: false }); + }, + + _draw: function(ctx, param, strokeMatrix) { var children = this._children; if (children.length === 0) return; - ctx.beginPath(); - param = param.extend({ compound: true }); - for (var i = 0, l = children.length; i < l; i++) - children[i].draw(ctx, param); + if (this._currentPath) { + ctx.currentPath = this._currentPath; + } else { + param = param.extend({ dontStart: true, dontFinish: true }); + ctx.beginPath(); + for (var i = 0, l = children.length; i < l; i++) + children[i].draw(ctx, param, strokeMatrix); + this._currentPath = ctx.currentPath; + } if (!param.clip) { this._setStyles(ctx); @@ -7815,28 +8221,44 @@ if (style.hasStroke()) ctx.stroke(); } - } -}, new function() { - function getCurrentPath(that) { - if (!that._children.length) + }, + + _drawSelected: function(ctx, matrix, selectedItems) { + var children = this._children; + for (var i = 0, l = children.length; i < l; i++) { + var child = children[i], + mx = child._matrix; + if (!selectedItems[child._id]) + child._drawSelected(ctx, mx.isIdentity() ? matrix + : matrix.chain(mx)); + } + } +}, new function() { + function getCurrentPath(that, check) { + var children = that._children; + if (check && children.length === 0) throw new Error('Use a moveTo() command first'); - return that._children[that._children.length - 1]; + return children[children.length - 1]; } var fields = { moveTo: function() { - var path = new Path(); - this.addChild(path); + var current = getCurrentPath(this), + path = current && current.isEmpty() ? current : new Path(); + if (path !== current) + this.addChild(path); path.moveTo.apply(path, arguments); }, moveBy: function() { - this.moveTo(getCurrentPath(this).getLastSegment()._point.add( - Point.read(arguments))); - }, - - closePath: function() { - getCurrentPath(this).closePath(); + var current = getCurrentPath(this, true), + last = current && current.getLastSegment(), + point = Point.read(arguments); + this.moveTo(last ? point.add(last._point) : point); + }, + + closePath: function(join) { + getCurrentPath(this, true).closePath(join); } }; @@ -7844,7 +8266,7 @@ 'lineBy', 'cubicCurveBy', 'quadraticCurveBy', 'curveBy', 'arcBy'], function(key) { fields[key] = function() { - var path = getCurrentPath(this); + var path = getCurrentPath(this, true); path[key].apply(path, arguments); }; } @@ -7853,11 +8275,468 @@ return fields; }); +PathItem.inject(new function() { + function computeBoolean(path1, path2, operator, subtract) { + function preparePath(path) { + return path.clone(false).reduce().reorient().transform(null, true); + } + + var _path1 = preparePath(path1), + _path2 = path2 && path1 !== path2 && preparePath(path2); + if (!_path1.isClockwise()) + _path1.reverse(); + if (_path2 && !(subtract ^ _path2.isClockwise())) + _path2.reverse(); + splitPath(_path1.getIntersections(_path2, true)); + + var chain = [], + windings = [], + lengths = [], + segments = [], + monoCurves = []; + + function collect(paths) { + for (var i = 0, l = paths.length; i < l; i++) { + var path = paths[i]; + segments.push.apply(segments, path._segments); + monoCurves.push.apply(monoCurves, path._getMonoCurves()); + } + } + + collect(_path1._children || [_path1]); + if (_path2) + collect(_path2._children || [_path2]); + segments.sort(function(a, b) { + var _a = a._intersection, + _b = b._intersection; + return !_a && !_b || _a && _b ? 0 : _a ? -1 : 1; + }); + for (var i = 0, l = segments.length; i < l; i++) { + var segment = segments[i]; + if (segment._winding != null) + continue; + chain.length = windings.length = lengths.length = 0; + var totalLength = 0, + startSeg = segment; + do { + chain.push(segment); + lengths.push(totalLength += segment.getCurve().getLength()); + segment = segment.getNext(); + } while (segment && !segment._intersection && segment !== startSeg); + for (var j = 0; j < 3; j++) { + var length = totalLength * Math.random(), + amount = lengths.length, + k = 0; + do { + if (lengths[k] >= length) { + if (k > 0) + length -= lengths[k - 1]; + break; + } + } while (++k < amount); + var curve = chain[k].getCurve(), + point = curve.getPointAt(length), + hor = curve.isHorizontal(), + path = curve._path; + if (path._parent instanceof CompoundPath) + path = path._parent; + windings[j] = subtract && _path2 + && (path === _path1 && _path2._getWinding(point, hor) + || path === _path2 && !_path1._getWinding(point, hor)) + ? 0 + : getWinding(point, monoCurves, hor); + } + windings.sort(); + var winding = windings[1]; + for (var j = chain.length - 1; j >= 0; j--) + chain[j]._winding = winding; + } + var result = new CompoundPath(); + result.addChildren(tracePaths(segments, operator), true); + _path1.remove(); + if (_path2) + _path2.remove(); + result = result.reduce(); + result.setStyle(path1._style); + return result; + } + + function splitPath(intersections) { + var TOLERANCE = 0.00001, + linearSegments; + + function resetLinear() { + for (var i = 0, l = linearSegments.length; i < l; i++) { + var segment = linearSegments[i]; + segment._handleOut.set(0, 0); + segment._handleIn.set(0, 0); + } + } + + for (var i = intersections.length - 1, curve, prevLoc; i >= 0; i--) { + var loc = intersections[i], + t = loc._parameter; + if (prevLoc && prevLoc._curve === loc._curve + && prevLoc._parameter > 0) { + t /= prevLoc._parameter; + } else { + if (linearSegments) + resetLinear(); + curve = loc._curve; + linearSegments = curve.isLinear() && []; + } + var newCurve, + segment; + if (newCurve = curve.divide(t, true, true)) { + segment = newCurve._segment1; + curve = newCurve.getPrevious(); + } else { + segment = t < TOLERANCE + ? curve._segment1 + : t > 1 - TOLERANCE + ? curve._segment2 + : curve.getPartLength(0, t) < curve.getPartLength(t, 1) + ? curve._segment1 + : curve._segment2; + } + segment._intersection = loc.getIntersection(); + loc._segment = segment; + if (linearSegments) + linearSegments.push(segment); + prevLoc = loc; + } + if (linearSegments) + resetLinear(); + } + + function getWinding(point, curves, horizontal, testContains) { + var TOLERANCE = 0.00001, + x = point.x, + y = point.y, + windLeft = 0, + windRight = 0, + roots = [], + abs = Math.abs, + MAX = 1 - TOLERANCE; + if (horizontal) { + var yTop = -Infinity, + yBottom = Infinity, + yBefore = y - TOLERANCE, + yAfter = y + TOLERANCE; + for (var i = 0, l = curves.length; i < l; i++) { + var values = curves[i].values; + if (Curve.solveCubic(values, 0, x, roots, 0, 1) > 0) { + for (var j = roots.length - 1; j >= 0; j--) { + var y0 = Curve.evaluate(values, roots[j], 0).y; + if (y0 < yBefore && y0 > yTop) { + yTop = y0; + } else if (y0 > yAfter && y0 < yBottom) { + yBottom = y0; + } + } + } + } + yTop = (yTop + y) / 2; + yBottom = (yBottom + y) / 2; + if (yTop > -Infinity) + windLeft = getWinding(new Point(x, yTop), curves); + if (yBottom < Infinity) + windRight = getWinding(new Point(x, yBottom), curves); + } else { + var xBefore = x - TOLERANCE, + xAfter = x + TOLERANCE; + for (var i = 0, l = curves.length; i < l; i++) { + var curve = curves[i], + values = curve.values, + winding = curve.winding, + next = curve.next; + if (winding && (winding === 1 + && y >= values[1] && y <= values[7] + || y >= values[7] && y <= values[1]) + && Curve.solveCubic(values, 1, y, roots, 0, + !next.winding && next.values[1] === y ? 1 : MAX) === 1){ + var t = roots[0], + x0 = Curve.evaluate(values, t, 0).x, + slope = Curve.evaluate(values, t, 1).y; + if (abs(slope) < TOLERANCE && !Curve.isLinear(values) + || t < TOLERANCE && slope * Curve.evaluate( + curve.previous.values, t, 1).y < 0) { + if (testContains && x0 >= xBefore && x0 <= xAfter) { + ++windLeft; + ++windRight; + } + } else if (x0 <= xBefore) { + windLeft += winding; + } else if (x0 >= xAfter) { + windRight += winding; + } + } + } + } + return Math.max(abs(windLeft), abs(windRight)); + } + + function tracePaths(segments, operator, selfOp) { + operator = operator || function() { + return true; + }; + var paths = [], + ZERO = 1e-3, + ONE = 1 - 1e-3; + for (var i = 0, seg, startSeg, l = segments.length; i < l; i++) { + seg = startSeg = segments[i]; + if (seg._visited || !operator(seg._winding)) + continue; + var path = new Path(Item.NO_INSERT), + inter = seg._intersection, + startInterSeg = inter && inter._segment, + added = false, + dir = 1; + do { + var handleIn = dir > 0 ? seg._handleIn : seg._handleOut, + handleOut = dir > 0 ? seg._handleOut : seg._handleIn, + interSeg; + if (added && (!operator(seg._winding) || selfOp) + && (inter = seg._intersection) + && (interSeg = inter._segment) + && interSeg !== startSeg) { + if (selfOp) { + seg._visited = interSeg._visited; + seg = interSeg; + dir = 1; + } else { + var c1 = seg.getCurve(); + if (dir > 0) + c1 = c1.getPrevious(); + var t1 = c1.getTangentAt(dir < 1 ? ZERO : ONE, true), + c4 = interSeg.getCurve(), + c3 = c4.getPrevious(), + t3 = c3.getTangentAt(ONE, true), + t4 = c4.getTangentAt(ZERO, true), + w3 = t1.cross(t3), + w4 = t1.cross(t4); + if (w3 * w4 !== 0) { + var curve = w3 < w4 ? c3 : c4, + nextCurve = operator(curve._segment1._winding) + ? curve + : w3 < w4 ? c4 : c3, + nextSeg = nextCurve._segment1; + dir = nextCurve === c3 ? -1 : 1; + if (nextSeg._visited && seg._path !== nextSeg._path + || !operator(nextSeg._winding)) { + dir = 1; + } else { + seg._visited = interSeg._visited; + seg = interSeg; + if (nextSeg._visited) + dir = 1; + } + } else { + dir = 1; + } + } + handleOut = dir > 0 ? seg._handleOut : seg._handleIn; + } + path.add(new Segment(seg._point, added && handleIn, handleOut)); + added = true; + seg._visited = true; + seg = dir > 0 ? seg.getNext() : seg. getPrevious(); + } while (seg && !seg._visited + && seg !== startSeg && seg !== startInterSeg + && (seg._intersection || operator(seg._winding))); + if (seg && (seg === startSeg || seg === startInterSeg)) { + path.firstSegment.setHandleIn((seg === startInterSeg + ? startInterSeg : seg)._handleIn); + path.setClosed(true); + } else { + path.lastSegment._handleOut.set(0, 0); + } + if (path._segments.length > + (path._closed ? path.isPolygon() ? 2 : 0 : 1)) + paths.push(path); + } + return paths; + } + + return { + _getWinding: function(point, horizontal, testContains) { + return getWinding(point, this._getMonoCurves(), + horizontal, testContains); + }, + + unite: function(path) { + return computeBoolean(this, path, function(w) { + return w === 1 || w === 0; + }, false); + }, + + intersect: function(path) { + return computeBoolean(this, path, function(w) { + return w === 2; + }, false); + }, + + subtract: function(path) { + return computeBoolean(this, path, function(w) { + return w === 1; + }, true); + }, + + exclude: function(path) { + return new Group([this.subtract(path), path.subtract(this)]); + }, + + divide: function(path) { + return new Group([this.subtract(path), this.intersect(path)]); + } + }; +}); + +Path.inject({ + _getMonoCurves: function() { + var monoCurves = this._monoCurves, + prevCurve; + + function insertCurve(v) { + var y0 = v[1], + y1 = v[7], + curve = { + values: v, + winding: y0 === y1 + ? 0 + : y0 > y1 + ? -1 + : 1, + previous: prevCurve, + next: null + }; + if (prevCurve) + prevCurve.next = curve; + monoCurves.push(curve); + prevCurve = curve; + } + + function handleCurve(v) { + if (Curve.getLength(v) === 0) + return; + var y0 = v[1], + y1 = v[3], + y2 = v[5], + y3 = v[7]; + if (Curve.isLinear(v)) { + insertCurve(v); + } else { + var a = 3 * (y1 - y2) - y0 + y3, + b = 2 * (y0 + y2) - 4 * y1, + c = y1 - y0, + TOLERANCE = 0.00001, + roots = []; + var count = Numerical.solveQuadratic(a, b, c, roots, TOLERANCE, + 1 - TOLERANCE); + if (count === 0) { + insertCurve(v); + } else { + roots.sort(); + var t = roots[0], + parts = Curve.subdivide(v, t); + insertCurve(parts[0]); + if (count > 1) { + t = (roots[1] - t) / (1 - t); + parts = Curve.subdivide(parts[1], t); + insertCurve(parts[0]); + } + insertCurve(parts[1]); + } + } + } + + if (!monoCurves) { + monoCurves = this._monoCurves = []; + var curves = this.getCurves(), + segments = this._segments; + for (var i = 0, l = curves.length; i < l; i++) + handleCurve(curves[i].getValues()); + if (!this._closed && segments.length > 1) { + var p1 = segments[segments.length - 1]._point, + p2 = segments[0]._point, + p1x = p1._x, p1y = p1._y, + p2x = p2._x, p2y = p2._y; + handleCurve([p1x, p1y, p1x, p1y, p2x, p2y, p2x, p2y]); + } + if (monoCurves.length > 0) { + var first = monoCurves[0], + last = monoCurves[monoCurves.length - 1]; + first.previous = last; + last.next = first; + } + } + return monoCurves; + }, + + getInteriorPoint: function() { + var bounds = this.getBounds(), + point = bounds.getCenter(true); + if (!this.contains(point)) { + var curves = this._getMonoCurves(), + roots = [], + y = point.y, + xIntercepts = []; + for (var i = 0, l = curves.length; i < l; i++) { + var values = curves[i].values; + if ((curves[i].winding === 1 + && y >= values[1] && y <= values[7] + || y >= values[7] && y <= values[1]) + && Curve.solveCubic(values, 1, y, roots, 0, 1) > 0) { + for (var j = roots.length - 1; j >= 0; j--) + xIntercepts.push(Curve.evaluate(values, roots[j], 0).x); + } + if (xIntercepts.length > 1) + break; + } + point.x = (xIntercepts[0] + xIntercepts[1]) / 2; + } + return point; + }, + + reorient: function() { + this.setClockwise(true); + return this; + } +}); + +CompoundPath.inject({ + _getMonoCurves: function() { + var children = this._children, + monoCurves = []; + for (var i = 0, l = children.length; i < l; i++) + monoCurves.push.apply(monoCurves, children[i]._getMonoCurves()); + return monoCurves; + }, + + reorient: function() { + var children = this.removeChildren().sort(function(a, b) { + return b.getBounds().getArea() - a.getBounds().getArea(); + }); + this.addChildren(children); + var clockwise = children[0].isClockwise(); + for (var i = 1, l = children.length; i < l; i++) { + var point = children[i].getInteriorPoint(), + counters = 0; + for (var j = i - 1; j >= 0; j--) { + if (children[j].contains(point)) + counters++; + } + children[i].setClockwise(counters % 2 === 0 && clockwise); + } + return this; + } +}); + var PathFlattener = Base.extend({ - initialize: function(path) { - this.curves = []; - this.parts = []; - this.length = 0; + initialize: function(path, matrix) { + this.curves = []; + this.parts = []; + this.length = 0; this.index = 0; var segments = path._segments, @@ -7866,7 +8745,7 @@ that = this; function addCurve(segment1, segment2) { - var curve = Curve.getValues(segment1, segment2); + var curve = Curve.getValues(segment1, segment2, matrix); that.curves.push(curve); that._computeParts(curve, segment1._index, 0, 1); } @@ -7917,7 +8796,7 @@ prevLen = prev ? prev.offset : 0; return { value: prevVal + (part.value - prevVal) - * (offset - prevLen) / (part.offset - prevLen), + * (offset - prevLen) / (part.offset - prevLen), index: part.index }; } @@ -8044,8 +8923,8 @@ var detC0C1 = C[0][0] * C[1][1] - C[1][0] * C[0][1], alpha1, alpha2; if (Math.abs(detC0C1) > epsilon) { - var detC0X = C[0][0] * X[1] - C[1][0] * X[0], - detXC1 = X[0] * C[1][1] - X[1] * C[0][1]; + var detC0X = C[0][0] * X[1] - C[1][0] * X[0], + detXC1 = X[0] * C[1][1] - X[1] * C[0][1]; alpha1 = detXC1 / detC0C1; alpha2 = detC0X / detC0C1; } else { @@ -8123,7 +9002,7 @@ for (var i = first + 1; i < last; i++) { var P = this.evaluate(3, curve, u[i - first]); var v = P.subtract(this.points[i]); - var dist = v.x * v.x + v.y * v.y; + var dist = v.x * v.x + v.y * v.y; if (dist >= maxDist) { maxDist = dist; index = i; @@ -8136,186 +9015,11 @@ } }); -PathItem.inject(new function() { - - function splitPath(intersections, collectOthers) { - intersections.sort(function(loc1, loc2) { - var path1 = loc1.getPath(), - path2 = loc2.getPath(); - return path1 === path2 - ? (loc1.getIndex() + loc1.getParameter()) - - (loc2.getIndex() + loc2.getParameter()) - : path1._id - path2._id; - }); - var others = collectOthers && []; - for (var i = intersections.length - 1; i >= 0; i--) { - var loc = intersections[i], - other = loc.getIntersection(), - curve = loc.divide(), - segment = curve && curve.getSegment1() || loc.getSegment(); - if (others) - others.push(other); - segment._intersection = other; - loc._segment = segment; - } - return others; - } - - function reorientPath(path) { - if (path instanceof CompoundPath) { - var children = path.removeChildren(), - length = children.length, - bounds = new Array(length), - counters = new Array(length), - clockwise; - children.sort(function(a, b){ - var b1 = a.getBounds(), b2 = b.getBounds(); - return b1._width * b1._height < b2._width * b2._height; - }); - path.addChildren(children); - clockwise = children[0].isClockwise(); - for (var i = 0; i < length; i++) { - bounds[i] = children[i].getBounds(); - counters[i] = 0; - } - for (var i = 0; i < length; i++) { - for (var j = 1; j < length; j++) { - if (i !== j && bounds[i].contains(bounds[j])) - counters[j]++; - } - if (i > 0 && counters[i] % 2 === 0) - children[i].setClockwise(clockwise); - } - } - return path; - } - - function computeBoolean(path1, path2, operator, subtract) { - path1 = reorientPath(path1.clone(false)); - path2 = reorientPath(path2.clone(false)); - var path1Clockwise = path1.isClockwise(), - path2Clockwise = path2.isClockwise(), - intersections = path1.getIntersections(path2); - splitPath(splitPath(intersections, true)); - if (!path1Clockwise) - path1.reverse(); - if (!(subtract ^ path2Clockwise)) - path2.reverse(); - path1Clockwise = true; - path2Clockwise = !subtract; - var paths = [] - .concat(path1._children || [path1]) - .concat(path2._children || [path2]), - segments = [], - result = new CompoundPath(); - for (var i = 0, l = paths.length; i < l; i++) { - var path = paths[i], - parent = path._parent, - clockwise = path.isClockwise(), - segs = path._segments; - path = parent instanceof CompoundPath ? parent : path; - for (var j = segs.length - 1; j >= 0; j--) { - var segment = segs[j], - midPoint = segment.getCurve().getPoint(0.5), - insidePath1 = path !== path1 && path1.contains(midPoint) - && (clockwise === path1Clockwise || subtract - || !testOnCurve(path1, midPoint)), - insidePath2 = path !== path2 && path2.contains(midPoint) - && (clockwise === path2Clockwise - || !testOnCurve(path2, midPoint)); - if (operator(path === path1, insidePath1, insidePath2)) { - segment._invalid = true; - } else { - segments.push(segment); - } - } - } - for (var i = 0, l = segments.length; i < l; i++) { - var segment = segments[i]; - if (segment._visited) - continue; - var path = new Path(), - loc = segment._intersection, - intersection = loc && loc.getSegment(true); - if (segment.getPrevious()._invalid) - segment.setHandleIn(intersection - ? intersection._handleIn - : new Point(0, 0)); - do { - segment._visited = true; - if (segment._invalid && segment._intersection) { - var inter = segment._intersection.getSegment(true); - path.add(new Segment(segment._point, segment._handleIn, - inter._handleOut)); - inter._visited = true; - segment = inter; - } else { - path.add(segment.clone()); - } - segment = segment.getNext(); - } while (segment && !segment._visited && segment !== intersection); - var amount = path._segments.length; - if (amount > 1 && (amount > 2 || !path.isPolygon())) { - path.setClosed(true); - result.addChild(path, true); - } else { - path.remove(); - } - } - path1.remove(); - path2.remove(); - return result.reduce(); - } - - function testOnCurve(path, point) { - var curves = path.getCurves(), - bounds = path.getBounds(); - if (bounds.contains(point)) { - for (var i = 0, l = curves.length; i < l; i++) { - var curve = curves[i]; - if (curve.getBounds().contains(point) - && curve.getParameterOf(point)) - return true; - } - } - return false; - } - - return { - unite: function(path) { - return computeBoolean(this, path, - function(isPath1, isInPath1, isInPath2) { - return isInPath1 || isInPath2; - }); - }, - - intersect: function(path) { - return computeBoolean(this, path, - function(isPath1, isInPath1, isInPath2) { - return !(isInPath1 || isInPath2); - }); - }, - - subtract: function(path) { - return computeBoolean(this, path, - function(isPath1, isInPath1, isInPath2) { - return isPath1 && isInPath2 || !isPath1 && !isInPath1; - }, true); - }, - - exclude: function(path) { - return new Group([this.subtract(path), path.subtract(this)]); - }, - - divide: function(path) { - return new Group([this.subtract(path), this.intersect(path)]); - } - }; -}); - var TextItem = Item.extend({ _class: 'TextItem', _boundsSelected: true, + _applyMatrix: false, + _canApplyMatrix: false, _serializeFields: { content: null }, @@ -8345,7 +9049,7 @@ setContent: function(content) { this._content = '' + content; this._lines = this._content.split(/\r\n|\n|\r/mg); - this._changed(69); + this._changed(265); }, isEmpty: function() { @@ -8367,7 +9071,7 @@ }, clone: function(insert) { - return this._clone(new PointText({ insert: false }), insert); + return this._clone(new PointText(Item.NO_INSERT), insert); }, getPoint: function() { @@ -8375,8 +9079,8 @@ return new LinkedPoint(point.x, point.y, this, 'setPoint'); }, - setPoint: function(point) { - point = Point.read(arguments); + setPoint: function() { + var point = Point.read(arguments); this.translate(point.subtract(this._matrix.getTranslation())); }, @@ -8388,7 +9092,6 @@ lines = this._lines, leading = style.getLeading(), shadowColor = ctx.shadowColor; - ctx.font = style.getFontStyle(); ctx.textAlign = style.getJustification(); for (var i = 0, l = lines.length; i < l; i++) { @@ -8402,36 +9105,26 @@ ctx.strokeText(line, 0, 0); ctx.translate(0, leading); } - } -}, new function() { - var measureCtx = null; - - return { - _getBounds: function(getter, matrix) { - if (!measureCtx) - measureCtx = CanvasProvider.getContext(1, 1); - var style = this._style, - lines = this._lines, - count = lines.length, - justification = style.getJustification(), - leading = style.getLeading(), - x = 0; - measureCtx.font = style.getFontStyle(); - var width = 0; - for (var i = 0; i < count; i++) - width = Math.max(width, measureCtx.measureText(lines[i]).width); - if (justification !== 'left') - x -= width / (justification === 'center' ? 2: 1); - var bounds = new Rectangle(x, - count ? - 0.75 * leading : 0, - width, count * leading); - return matrix ? matrix._transformBounds(bounds, bounds) : bounds; - } - }; + }, + + _getBounds: function(getter, matrix) { + var style = this._style, + lines = this._lines, + numLines = lines.length, + justification = style.getJustification(), + leading = style.getLeading(), + width = this.getView().getTextWidth(style.getFontStyle(), lines), + x = 0; + if (justification !== 'left') + x -= width / (justification === 'center' ? 2: 1); + var bounds = new Rectangle(x, + numLines ? - 0.75 * leading : 0, + width, numLines * leading); + return matrix ? matrix._transformBounds(bounds, bounds) : bounds; + } }); var Color = Base.extend(new function() { - var types = { gray: ['gray'], rgb: ['red', 'green', 'blue'], @@ -8457,7 +9150,7 @@ } else if (match = string.match(/^rgba?\((.*)\)$/)) { components = match[1].split(','); for (var i = 0, l = components.length; i < l; i++) { - var value = parseFloat(components[i]); + var value = +components[i]; components[i] = i < 3 ? value / 255 : value; } } else { @@ -8474,7 +9167,7 @@ cached = colorCache[string] = [ data[0] / 255, data[1] / 255, - data[2] / 255 + data[2] / 255 ]; } components = cached.slice(); @@ -8483,12 +9176,12 @@ } var hsbIndices = [ - [0, 3, 1], - [2, 0, 1], - [1, 0, 3], - [1, 2, 0], - [3, 1, 0], - [0, 1, 2] + [0, 3, 1], + [2, 0, 1], + [1, 0, 3], + [1, 2, 0], + [3, 1, 0], + [0, 1, 2] ]; var converters = { @@ -8497,22 +9190,22 @@ min = Math.min(r, g, b), delta = max - min, h = delta === 0 ? 0 - : ( max == r ? (g - b) / delta + (g < b ? 6 : 0) + : ( max == r ? (g - b) / delta + (g < b ? 6 : 0) : max == g ? (b - r) / delta + 2 - : (r - g) / delta + 4) * 60; + : (r - g) / delta + 4) * 60; return [h, max === 0 ? 0 : delta / max, max]; }, 'hsb-rgb': function(h, s, b) { - var h = (h / 60) % 6, - i = Math.floor(h), + h = (((h / 60) % 6) + 6) % 6; + var i = Math.floor(h), f = h - i, i = hsbIndices[i], v = [ - b, - b * (1 - s), - b * (1 - s * f), - b * (1 - s * (1 - f)) + b, + b * (1 - s), + b * (1 - s * f), + b * (1 - s * (1 - f)) ]; return [v[i[0]], v[i[1]], v[i[2]]]; }, @@ -8523,9 +9216,9 @@ delta = max - min, achromatic = delta === 0, h = achromatic ? 0 - : ( max == r ? (g - b) / delta + (g < b ? 6 : 0) + : ( max == r ? (g - b) / delta + (g < b ? 6 : 0) : max == g ? (b - r) / delta + 2 - : (r - g) / delta + 4) * 60, + : (r - g) / delta + 4) * 60, l = (max + min) / 2, s = achromatic ? 0 : l < 0.5 ? delta / (max + min) @@ -8534,7 +9227,7 @@ }, 'hsl-rgb': function(h, s, l) { - h /= 360; + h = (((h / 360) % 1) + 1) % 1; if (s === 0) return [l, l, l]; var t3s = [ h + 1 / 3, h, h - 1 / 3 ], @@ -8590,9 +9283,8 @@ parser = componentParsers[type][index] = name === 'gradient' ? function(value) { var current = this._components[0]; - value = Gradient.read( - Array.isArray(value) ? value : arguments, - 0, 0, { readNull: true }); + value = Gradient.read(Array.isArray(value) ? value + : arguments, 0, { readNull: true }); if (current !== value) { if (current) current._removeOwner(this); @@ -8601,22 +9293,16 @@ } return value; } - : name === 'hue' - ? function(value) { - return isNaN(value) ? 0 - : ((value % 360) + 360) % 360; + : type === 'gradient' + ? function() { + return Point.read(arguments, 0, { + readNull: name === 'highlight', + clone: true + }); } - : type === 'gradient' - ? function() { - return Point.read(arguments, 0, 0, { - readNull: name === 'highlight', - clone: true - }); - } - : function(value) { - return isNaN(value) ? 0 - : Math.min(Math.max(value, 0), 1); - }; + : function(value) { + return value == null || isNaN(value) ? 0 : value; + }; this['get' + part] = function() { return this._type === type @@ -8647,7 +9333,6 @@ var slice = Array.prototype.slice, args = arguments, read = 0, - parse = true, type, components, alpha, @@ -8665,13 +9350,12 @@ alpha = args[2]; } else { if (this.__read) - read = 1; + read = 1; args = slice.call(args, 1); argType = typeof arg; } } if (!components) { - parse = !(this.__options && this.__options.dontParse); values = argType === 'number' ? args : argType === 'object' && arg.length != null @@ -8724,7 +9408,7 @@ ? 'gray' : 'rgb'; var properties = types[type]; - parsers = parse && componentParsers[type]; + parsers = componentParsers[type]; this._components = components = []; for (var i = 0, l = properties.length; i < l; i++) { var value = arg[properties[i]]; @@ -8735,8 +9419,7 @@ radial: arg.radial }; } - if (parse) - value = parsers[i].call(this, value); + value = parsers[i].call(this, value); if (value != null) components[i] = value; } @@ -8753,9 +9436,7 @@ this._components = components = []; var parsers = componentParsers[this._type]; for (var i = 0, l = parsers.length; i < l; i++) { - var value = values && values[i]; - if (parse) - value = parsers[i].call(this, value); + var value = parsers[i].call(this, values && values[i]); if (value != null) components[i] = value; } @@ -8779,7 +9460,7 @@ _changed: function() { this._canvasStyle = null; if (this._owner) - this._owner._changed(17); + this._owner._changed(65); }, _convert: function(type) { @@ -8790,7 +9471,7 @@ ? converter.apply(this, this._components) : converters['rgb-' + type].apply(this, converters[this._type + '-rgb'].apply(this, - this._components)); + this._components)); }, convert: function(type) { @@ -8828,12 +9509,13 @@ }, equals: function(color) { - if (Base.isPlainValue(color)) - color = Color.read(arguments); - return color === this || color && this._class === color._class - && this._type === color._type - && this._alpha === color._alpha - && Base.equals(this._components, color._components) + var col = Base.isPlainValue(color, true) + ? Color.read(arguments) + : color; + return col === this || col && this._class === col._class + && this._type === col._type + && this._alpha === col._alpha + && Base.equals(this._components, col._components) || false; }, @@ -8856,13 +9538,16 @@ toCSS: function(hex) { var components = this._convert('rgb'), alpha = hex || this._alpha == null ? 1 : this._alpha; + function convert(val) { + return Math.round((val < 0 ? 0 : val > 1 ? 1 : val) * 255); + } components = [ - Math.round(components[0] * 255), - Math.round(components[1] * 255), - Math.round(components[2] * 255) + convert(components[0]), + convert(components[1]), + convert(components[2]) ]; if (alpha < 1) - components.push(alpha); + components.push(alpha < 0 ? 0 : alpha); return hex ? '#' + ((1 << 24) + (components[0] << 16) + (components[1] << 8) @@ -8926,46 +9611,32 @@ } }); }, new function() { - function clamp(value, hue) { - return value < 0 - ? 0 - : hue && value > 360 - ? 360 - : !hue && value > 1 - ? 1 - : value; - } - var operators = { - add: function(a, b, hue) { - return clamp(a + b, hue); - }, - - subtract: function(a, b, hue) { - return clamp(a - b, hue); - }, - - multiply: function(a, b, hue) { - return clamp(a * b, hue); - }, - - divide: function(a, b, hue) { - return clamp(a / b, hue); + add: function(a, b) { + return a + b; + }, + + subtract: function(a, b) { + return a - b; + }, + + multiply: function(a, b) { + return a * b; + }, + + divide: function(a, b) { + return a / b; } }; return Base.each(operators, function(operator, name) { - var options = { dontParse: /^(multiply|divide)$/.test(name) }; - this[name] = function(color) { - color = Color.read(arguments, 0, 0, options); + color = Color.read(arguments); var type = this._type, - properties = this._properties, components1 = this._components, components2 = color._convert(type); for (var i = 0, l = components1.length; i < l; i++) - components2[i] = operator(components1[i], components2[i], - properties[i] === 'hue'); + components2[i] = operator(components1[i], components2[i]); return new Color(type, components2, this._alpha != null ? operator(this._alpha, color.getAlpha()) @@ -9030,7 +9701,7 @@ if (index != -1) { this._owners.splice(index, 1); if (this._owners.length === 0) - delete this._owners; + this._owners = undefined; } }, @@ -9048,12 +9719,12 @@ setStops: function(stops) { if (this.stops) { for (var i = 0, l = this._stops.length; i < l; i++) - delete this._stops[i]._owner; + this._stops[i]._owner = undefined; } if (stops.length < 2) throw new Error( 'Gradient stop list needs to contain at least two stops.'); - this._stops = GradientStop.readAll(stops, 0, false, true); + this._stops = GradientStop.readAll(stops, 0, { clone: true }); for (var i = 0, l = this._stops.length; i < l; i++) { var stop = this._stops[i]; stop._owner = this; @@ -9075,7 +9746,7 @@ equals: function(gradient) { if (gradient === this) return true; - if (gradient && this._class === gradient._class + if (gradient && this._class === gradient._class && this._stops.length === gradient._stops.length) { for (var i = 0, l = this._stops.length; i < l; i++) { if (!this._stops[i].equals(gradient._stops[i])) @@ -9113,13 +9784,13 @@ }, _serialize: function(options, dictionary) { - return Base.serialize([this._color, this._rampPoint], options, true, + return Base.serialize([this._color, this._rampPoint], options, true, dictionary); }, _changed: function() { if (this._owner) - this._owner._changed(17); + this._owner._changed(65); }, getRampPoint: function() { @@ -9159,6 +9830,7 @@ strokeWidth: 1, strokeCap: 'butt', strokeJoin: 'miter', + strokeScaling: true, miterLimit: 10, dashOffset: 0, dashArray: [], @@ -9167,29 +9839,35 @@ shadowBlur: 0, shadowOffset: new Point(), selectedColor: undefined, + fontFamily: 'sans-serif', + fontWeight: 'normal', + fontSize: 12, font: 'sans-serif', - fontSize: 12, leading: null, justification: 'left' }; var flags = { - strokeWidth: 25, - strokeCap: 25, - strokeJoin: 25, - miterLimit: 25, - font: 5, - fontSize: 5, - leading: 5, - justification: 5 + strokeWidth: 97, + strokeCap: 97, + strokeJoin: 97, + strokeScaling: 105, + miterLimit: 97, + fontFamily: 9, + fontWeight: 9, + fontSize: 9, + font: 9, + leading: 9, + justification: 9 }; - var item = {}, + var item = { beans: true }, fields = { _defaults: defaults, _textDefaults: new Base(defaults, { - fillColor: new Color() - }) + fillColor: new Color() + }), + beans: true }; Base.each(defaults, function(value, key) { @@ -9200,9 +9878,10 @@ get = 'get' + part; fields[set] = function(value) { - var children = this._item && this._item._children; + var owner = this._owner, + children = owner && owner._children; if (children && children.length > 0 - && this._item._type !== 'compound-path') { + && !(owner instanceof CompoundPath)) { for (var i = 0, l = children.length; i < l; i++) children[i]._style[set](value); } else { @@ -9210,25 +9889,26 @@ if (old != value) { if (isColor) { if (old) - delete old._owner; + old._owner = undefined; if (value && value.constructor === Color) { if (value._owner) value = value.clone(); - value._owner = this._item; + value._owner = owner; } } this._values[key] = value; - if (this._item) - this._item._changed(flag || 17); + if (owner) + owner._changed(flag || 65); } } }; - fields[get] = function() { - var value, - children = this._item && this._item._children; - if (!children || children.length === 0 || arguments[0] - || this._item._type === 'compound-path') { + fields[get] = function(_dontMerge) { + var owner = this._owner, + children = owner && owner._children, + value; + if (!children || children.length === 0 || _dontMerge + || owner instanceof CompoundPath) { var value = this._values[key]; if (value === undefined) { value = this._defaults[key]; @@ -9236,10 +9916,10 @@ value = value.clone(); this._values[key] = value; } else if (isColor && !(value && value.constructor === Color)) { - this._values[key] = value = Color.read( - [value], 0, 0, { readNull: true, clone: true }); + this._values[key] = value = Color.read([value], 0, + { readNull: true, clone: true }); if (value) - value._owner = this._item; + value._owner = owner; } return value; } @@ -9254,8 +9934,8 @@ return value; }; - item[get] = function() { - return this._style[get](); + item[get] = function(_dontMerge) { + return this._style[get](_dontMerge); }; item[set] = function(value) { @@ -9268,10 +9948,11 @@ }, { _class: 'Style', - initialize: function Style(style, _item) { + initialize: function Style(style, _owner, _project) { this._values = {}; - this._item = _item; - if (_item instanceof TextItem) + this._owner = _owner; + this._project = _owner && _owner._project || _project || paper.project; + if (_owner instanceof TextItem) this._defaults = this._textDefaults; if (style) this.set(style); @@ -9309,14 +9990,26 @@ return !!this.getShadowColor() && this.getShadowBlur() > 0; }, - getLeading: function getLeading() { - var leading = getLeading.base.call(this); - return leading != null ? leading : this.getFontSize() * 1.2; + getView: function() { + return this._project.getView(); }, getFontStyle: function() { - var size = this.getFontSize(); - return size + (/[a-z]/i.test(size + '') ? ' ' : 'px ') + this.getFont(); + var fontSize = this.getFontSize(); + return this.getFontWeight() + + ' ' + fontSize + (/[a-z]/i.test(fontSize + '') ? ' ' : 'px ') + + this.getFontFamily(); + }, + + getFont: '#getFontFamily', + setFont: '#setFontFamily', + + getLeading: function getLeading() { + var leading = getLeading.base.call(this), + fontSize = this.getFontSize(); + if (/pt|em|%|px/.test(fontSize)) + fontSize = this.getView().getPixelSize(fontSize); + return leading != null ? leading : fontSize * 1.2; } }); @@ -9347,6 +10040,23 @@ return res; } + function handlePrefix(el, name, set, value) { + var prefixes = ['', 'webkit', 'moz', 'Moz', 'ms', 'o'], + suffix = name[0].toUpperCase() + name.substring(1); + for (var i = 0; i < 6; i++) { + var prefix = prefixes[i], + key = prefix ? prefix + suffix : name; + if (key in el) { + if (set) { + el[key] = value; + } else { + return el[key]; + } + break; + } + } + } + return { create: function(nodes, parent) { var isArray = Array.isArray(nodes), @@ -9452,7 +10162,7 @@ rect = { left: 0, top: 0, width: 0, height: 0 }; } var x = rect.left - (html.clientLeft || body.clientLeft || 0), - y = rect.top - (html.clientTop || body.clientTop || 0); + y = rect.top - (html.clientTop || body.clientTop || 0); if (!viewport) { var view = doc.defaultView; x += view.pageXOffset || html.scrollLeft || body.scrollLeft; @@ -9465,7 +10175,7 @@ var doc = el.ownerDocument, view = doc.defaultView, html = doc.documentElement; - return new Rectangle(0, 0, + return new Rectangle(0, 0, view.innerWidth || html.clientWidth, view.innerHeight || html.clientHeight ); @@ -9488,13 +10198,17 @@ this.getBounds(el, true)); }, - getPrefixValue: function(el, name) { - var value = el[name], - prefixes = ['webkit', 'moz', 'ms', 'o'], - suffix = name[0].toUpperCase() + name.substring(1); - for (var i = 0; i < 4 && value == null; i++) - value = el[prefixes[i] + suffix]; - return value; + getPrefixed: function(el, name) { + return handlePrefix(el, name); + }, + + setPrefixed: function(el, name, value) { + if (typeof name === 'object') { + for (var key in name) + handlePrefix(el, key, true, name[key]); + } else { + handlePrefix(el, name, true, value); + } } }; }; @@ -9502,25 +10216,19 @@ var DomEvent = { add: function(el, events) { for (var type in events) { - var func = events[type]; - if (el.addEventListener) { - el.addEventListener(type, func, false); - } else if (el.attachEvent) { - el.attachEvent('on' + type, func.bound = function() { - func.call(el, window.event); - }); - } + var func = events[type], + parts = type.split(/[\s,]+/g); + for (var i = 0, l = parts.length; i < l; i++) + el.addEventListener(parts[i], func, false); } }, remove: function(el, events) { for (var type in events) { - var func = events[type]; - if (el.removeEventListener) { - el.removeEventListener(type, func, false); - } else if (el.detachEvent) { - el.detachEvent('on' + type, func.bound); - } + var func = events[type], + parts = type.split(/[\s,]+/g); + for (var i = 0, l = parts.length; i < l; i++) + el.removeEventListener(parts[i], func, false); } }, @@ -9540,36 +10248,23 @@ return event.target || event.srcElement; }, + getRelatedTarget: function(event) { + return event.relatedTarget || event.toElement; + }, + getOffset: function(event, target) { return DomEvent.getPoint(event).subtract(DomElement.getOffset( target || DomEvent.getTarget(event))); }, - preventDefault: function(event) { - if (event.preventDefault) { - event.preventDefault(); - } else { - event.returnValue = false; - } - }, - - stopPropagation: function(event) { - if (event.stopPropagation) { - event.stopPropagation(); - } else { - event.cancelBubble = true; - } - }, - stop: function(event) { - DomEvent.stopPropagation(event); - DomEvent.preventDefault(event); + event.stopPropagation(); + event.preventDefault(); } }; DomEvent.requestAnimationFrame = new function() { - var nativeRequest = DomElement.getPrefixValue(window, - 'requestAnimationFrame'), + var nativeRequest = DomElement.getPrefixed(window, 'requestAnimationFrame'), requested = false, callbacks = [], focused = true, @@ -9620,21 +10315,32 @@ var View = Base.extend(Callback, { _class: 'View', - initialize: function View(element) { - this._scope = paper; - this._project = paper.project; + initialize: function View(project, element) { + this._project = project; + this._scope = project._scope; this._element = element; var size; + if (!this._pixelRatio) + this._pixelRatio = window.devicePixelRatio || 1; this._id = element.getAttribute('id'); if (this._id == null) element.setAttribute('id', this._id = 'view-' + View._id++); - DomEvent.add(element, this._viewHandlers); + DomEvent.add(element, this._viewEvents); + var none = 'none'; + DomElement.setPrefixed(element.style, { + userSelect: none, + touchAction: none, + touchCallout: none, + contentZooming: none, + userDrag: none, + tapHighlightColor: 'rgba(0,0,0,0)' + }); if (PaperScope.hasAttribute(element, 'resize')) { var offset = DomElement.getOffset(element, true), that = this; size = DomElement.getViewportBounds(element) .getSize().subtract(offset); - this._windowHandlers = { + this._windowEvents = { resize: function() { if (!DomElement.isInvisible(element)) offset = DomElement.getOffset(element, true); @@ -9642,12 +10348,16 @@ .getSize().subtract(offset)); } }; - DomEvent.add(window, this._windowHandlers); + DomEvent.add(window, this._windowEvents); } else { size = DomElement.getSize(element); - if (size.isNaN() || size.isZero()) - size = new Size(parseInt(element.getAttribute('width'), 10), - parseInt(element.getAttribute('height'), 10)); + if (size.isNaN() || size.isZero()) { + var getSize = function(name) { + return element[name] + || parseInt(element.getAttribute(name), 10); + }; + size = new Size(getSize('width'), getSize('height')); + } } this._setViewSize(size); if (PaperScope.hasAttribute(element, 'stats') @@ -9675,16 +10385,17 @@ remove: function() { if (!this._project) return false; - if (View._focused == this) + if (View._focused === this) View._focused = null; View._views.splice(View._views.indexOf(this), 1); delete View._viewsById[this._id]; - if (this._project.view == this) - this._project.view = null; - DomEvent.remove(this._element, this._viewHandlers); - DomEvent.remove(window, this._windowHandlers); + if (this._project._view === this) + this._project._view = null; + DomEvent.remove(this._element, this._viewEvents); + DomEvent.remove(window, this._windowEvents); this._element = this._project = null; this.detach('frame'); + this._animate = false; this._frameItems = {}; return true; }, @@ -9692,13 +10403,11 @@ _events: { onFrame: { install: function() { - this._animate = true; - if (!this._requested) - this._requestFrame(); + this.play(); }, uninstall: function() { - this._animate = false; + this.pause(); } }, @@ -9735,7 +10444,7 @@ if (this._stats) this._stats.update(); this._handlingFrame = false; - this.draw(true); + this.update(); }, _animateItem: function(item, animate) { @@ -9766,50 +10475,58 @@ } }, - _redraw: function() { - this._project._needsRedraw = true; + _update: function() { + this._project._needsUpdate = true; if (this._handlingFrame) return; if (this._animate) { this._handleFrame(); } else { - this.draw(); + this.update(); } }, _changed: function(flags) { if (flags & 1) - this._project._needsRedraw = true; + this._project._needsUpdate = true; }, _transform: function(matrix) { this._matrix.concatenate(matrix); this._bounds = null; - this._redraw(); + this._update(); }, getElement: function() { return this._element; }, + getPixelRatio: function() { + return this._pixelRatio; + }, + + getResolution: function() { + return this._pixelRatio * 72; + }, + getViewSize: function() { var size = this._viewSize; return new LinkedSize(size.width, size.height, this, 'setViewSize'); }, - setViewSize: function(size) { - size = Size.read(arguments); - var delta = size.subtract(this._viewSize); + setViewSize: function() { + var size = Size.read(arguments), + delta = size.subtract(this._viewSize); if (delta.isZero()) return; this._viewSize.set(size.width, size.height); this._setViewSize(size); - this._bounds = null; + this._bounds = null; this.fire('resize', { size: size, delta: delta }); - this._redraw(); + this._update(); }, _setViewSize: function(size) { @@ -9826,15 +10543,15 @@ }, getSize: function() { - return this.getBounds().getSize(arguments[0]); + return this.getBounds().getSize(); }, getCenter: function() { - return this.getBounds().getCenter(arguments[0]); - }, - - setCenter: function(center) { - center = Point.read(arguments); + return this.getBounds().getCenter(); + }, + + setCenter: function() { + var center = Point.read(arguments); this.scrollBy(center.subtract(this.getCenter())); }, @@ -9856,13 +10573,27 @@ this._transform(new Matrix().translate(Point.read(arguments).negate())); }, + play: function() { + this._animate = true; + if (!this._requested) + this._requestFrame(); + }, + + pause: function() { + this._animate = false; + }, + + draw: function() { + this.update(); + }, + projectToView: function() { return this._matrix._transformPoint(Point.read(arguments)); }, viewToProject: function() { return this._matrix._inverseTransform(Point.read(arguments)); - }, + } }, { statics: { @@ -9870,10 +10601,10 @@ _viewsById: {}, _id: 0, - create: function(element) { + create: function(project, element) { if (typeof element === 'string') element = document.getElementById(element); - return new CanvasView(element); + return new CanvasView(project, element); } } }, new function() { @@ -9903,80 +10634,106 @@ } } - function mousedown(event) { + function handleMouseMove(view, point, event) { + view._handleEvent('mousemove', point, event); + var tool = view._scope.tool; + if (tool) { + tool._handleEvent(dragging && tool.responds('mousedrag') + ? 'mousedrag' : 'mousemove', point, event); + } + view.update(); + return tool; + } + + var navigator = window.navigator, + mousedown, mousemove, mouseup; + if (navigator.pointerEnabled || navigator.msPointerEnabled) { + mousedown = 'pointerdown MSPointerDown'; + mousemove = 'pointermove MSPointerMove'; + mouseup = 'pointerup pointercancel MSPointerUp MSPointerCancel'; + } else { + mousedown = 'touchstart'; + mousemove = 'touchmove'; + mouseup = 'touchend touchcancel'; + if (!('ontouchstart' in window && navigator.userAgent.match( + /mobile|tablet|ip(ad|hone|od)|android|silk/i))) { + mousedown += ' mousedown'; + mousemove += ' mousemove'; + mouseup += ' mouseup'; + } + } + + var viewEvents = { + 'selectstart dragstart': function(event) { + if (dragging) + event.preventDefault(); + } + }; + + var docEvents = { + mouseout: function(event) { + var view = View._focused, + target = DomEvent.getRelatedTarget(event); + if (view && (!target || target.nodeName === 'HTML')) + handleMouseMove(view, viewToProject(view, event), event); + }, + + scroll: updateFocus + }; + + viewEvents[mousedown] = function(event) { var view = View._focused = getView(event), point = viewToProject(view, event); dragging = true; - if (view._onMouseDown) - view._onMouseDown(event, point); - if (tool = view._scope._tool) - tool._onHandleEvent('mousedown', point, event); - view.draw(true); - } - - function mousemove(event) { - var view; + view._handleEvent('mousedown', point, event); + if (tool = view._scope.tool) + tool._handleEvent('mousedown', point, event); + view.update(); + }; + + docEvents[mousemove] = function(event) { + var view = View._focused; if (!dragging) { - view = getView(event); - if (view) { - prevFocus = View._focused; - View._focused = tempFocus = view; - } else if (tempFocus && tempFocus == View._focused) { - View._focused = prevFocus; + var target = getView(event); + if (target) { + if (view !== target) + handleMouseMove(view, viewToProject(view, event), event); + prevFocus = view; + view = View._focused = tempFocus = target; + } else if (tempFocus && tempFocus === view) { + view = View._focused = prevFocus; updateFocus(); } } - if (!(view = view || View._focused)) - return; - var point = event && viewToProject(view, event); - if (view._onMouseMove) - view._onMouseMove(event, point); - if (tool = view._scope._tool) { - if (tool._onHandleEvent(dragging && tool.responds('mousedrag') - ? 'mousedrag' : 'mousemove', point, event)) - DomEvent.stop(event); - } - view.draw(true); - } - - function mouseup(event) { + if (view) { + var point = viewToProject(view, event); + if (dragging || view.getBounds().contains(point)) + tool = handleMouseMove(view, point, event); + } + }; + + docEvents[mouseup] = function(event) { var view = View._focused; if (!view || !dragging) return; var point = viewToProject(view, event); - curPoint = null; dragging = false; - if (view._onMouseUp) - view._onMouseUp(event, point); - if (tool && tool._onHandleEvent('mouseup', point, event)) - DomEvent.stop(event); - view.draw(true); - } - - function selectstart(event) { - if (dragging) - DomEvent.stop(event); - } - - DomEvent.add(document, { - mousemove: mousemove, - mouseup: mouseup, - touchmove: mousemove, - touchend: mouseup, - selectstart: selectstart, - scroll: updateFocus - }); + view._handleEvent('mouseup', point, event); + if (tool) + tool._handleEvent('mouseup', point, event); + view.update(); + }; + + DomEvent.add(document, docEvents); DomEvent.add(window, { load: updateFocus }); return { - _viewHandlers: { - mousedown: mousedown, - touchstart: mousedown, - selectstart: selectstart - }, + _viewEvents: viewEvents, + + _handleEvent: function() {}, statics: { updateFocus: updateFocus @@ -9987,53 +10744,74 @@ var CanvasView = View.extend({ _class: 'CanvasView', - initialize: function CanvasView(canvas) { + initialize: function CanvasView(project, canvas) { if (!(canvas instanceof HTMLCanvasElement)) { var size = Size.read(arguments); if (size.isZero()) throw new Error( 'Cannot create CanvasView with the provided argument: ' - + canvas); + + [].slice.call(arguments, 1)); canvas = CanvasProvider.getCanvas(size); } this._context = canvas.getContext('2d'); this._eventCounters = {}; - this._ratio = 1; + this._pixelRatio = 1; if (PaperScope.getAttribute(canvas, 'hidpi') !== 'off') { var deviceRatio = window.devicePixelRatio || 1, - backingStoreRatio = DomElement.getPrefixValue(this._context, + backingStoreRatio = DomElement.getPrefixed(this._context, 'backingStorePixelRatio') || 1; - this._ratio = deviceRatio / backingStoreRatio; - } - View.call(this, canvas); + this._pixelRatio = deviceRatio / backingStoreRatio; + } + View.call(this, project, canvas); }, _setViewSize: function(size) { var width = size.width, height = size.height, - ratio = this._ratio, + pixelRatio = this._pixelRatio, element = this._element, style = element.style; - element.width = width * ratio; - element.height = height * ratio; - if (ratio !== 1) { + element.width = width * pixelRatio; + element.height = height * pixelRatio; + if (pixelRatio !== 1) { style.width = width + 'px'; style.height = height + 'px'; - this._context.scale(ratio, ratio); - } - }, - - draw: function(checkRedraw) { - if (checkRedraw && !this._project._needsRedraw) + this._context.scale(pixelRatio, pixelRatio); + } + }, + + getPixelSize: function(size) { + var ctx = this._context, + prevFont = ctx.font; + ctx.font = size + ' serif'; + size = parseFloat(ctx.font); + ctx.font = prevFont; + return size; + }, + + getTextWidth: function(font, lines) { + var ctx = this._context, + prevFont = ctx.font, + width = 0; + ctx.font = font; + for (var i = 0, l = lines.length; i < l; i++) + width = Math.max(width, ctx.measureText(lines[i]).width); + ctx.font = prevFont; + return width; + }, + + update: function() { + var project = this._project; + if (!project || !project._needsUpdate) return false; var ctx = this._context, size = this._viewSize; ctx.clearRect(0, 0, size.width + 1, size.height + 1); - this._project.draw(ctx, this._matrix, this._ratio); - this._project._needsRedraw = false; + project.draw(ctx, this._matrix, this._pixelRatio); + project._needsUpdate = false; return true; } -}, new function() { +}, new function() { var downPoint, lastPoint, @@ -10041,86 +10819,98 @@ downItem, lastItem, overItem, - hasDrag, - doubleClick, + dragItem, + dblClick, clickTime; - function callEvent(type, event, point, target, lastPoint, bubble) { + function callEvent(view, type, event, point, target, lastPoint) { var item = target, mouseEvent; - while (item) { - if (item.responds(type)) { - if (!mouseEvent) + + function call(obj) { + if (obj.responds(type)) { + if (!mouseEvent) { mouseEvent = new MouseEvent(type, event, point, target, lastPoint ? point.subtract(lastPoint) : null); - if (item.fire(type, mouseEvent) - && (!bubble || mouseEvent._stopped)) - return false; - } + } + if (obj.fire(type, mouseEvent) && mouseEvent.isStopped) { + event.preventDefault(); + return true; + } + } + } + + while (item) { + if (call(item)) + return true; item = item.getParent(); } - return true; - } - - function handleEvent(view, type, event, point, lastPoint) { - if (view._eventCounters[type]) { - var project = view._project, + if (call(view)) + return true; + return false; + } + + return { + _handleEvent: function(type, point, event) { + if (!this._eventCounters[type]) + return; + var project = this._project, hit = project.hitTest(point, { - tolerance: project.options.hitTolerance || 0, + tolerance: 0, fill: true, stroke: true }), - item = hit && hit.item; - if (item) { - if (type === 'mousemove' && item != overItem) - lastPoint = point; - if (type !== 'mousemove' || !hasDrag) - callEvent(type, event, point, item, lastPoint); - return item; - } - } - } - - return { - _onMouseDown: function(event, point) { - var item = handleEvent(this, 'mousedown', event, point); - doubleClick = lastItem == item && (Date.now() - clickTime < 300); - downItem = lastItem = item; - downPoint = lastPoint = overPoint = point; - hasDrag = downItem && downItem.responds('mousedrag'); - }, - - _onMouseUp: function(event, point) { - var item = handleEvent(this, 'mouseup', event, point); - if (hasDrag) { - if (lastPoint && !lastPoint.equals(point)) - callEvent('mousedrag', event, point, downItem, lastPoint); - if (item != downItem) { - overPoint = point; - callEvent('mousemove', event, point, item, overPoint); - } - } - if (item === downItem) { - clickTime = Date.now(); - if (!doubleClick - || callEvent('doubleclick', event, downPoint, item)) - callEvent('click', event, downPoint, item); - doubleClick = false; - } - downItem = null; - hasDrag = false; - }, - - _onMouseMove: function(event, point) { - if (downItem) - callEvent('mousedrag', event, point, downItem, lastPoint); - var item = handleEvent(this, 'mousemove', event, point, overPoint); - lastPoint = overPoint = point; - if (item !== overItem) { - callEvent('mouseleave', event, point, overItem); - overItem = item; - callEvent('mouseenter', event, point, item); - } + item = hit && hit.item, + stopped = false; + switch (type) { + case 'mousedown': + stopped = callEvent(this, type, event, point, item); + dblClick = lastItem == item && (Date.now() - clickTime < 300); + downItem = lastItem = item; + downPoint = lastPoint = overPoint = point; + dragItem = !stopped && item; + while (dragItem && !dragItem.responds('mousedrag')) + dragItem = dragItem._parent; + break; + case 'mouseup': + stopped = callEvent(this, type, event, point, item, downPoint); + if (dragItem) { + if (lastPoint && !lastPoint.equals(point)) + callEvent(this, 'mousedrag', event, point, dragItem, + lastPoint); + if (item !== dragItem) { + overPoint = point; + callEvent(this, 'mousemove', event, point, item, + overPoint); + } + } + if (!stopped && item && item === downItem) { + clickTime = Date.now(); + callEvent(this, dblClick && downItem.responds('doubleclick') + ? 'doubleclick' : 'click', event, downPoint, item); + dblClick = false; + } + downItem = dragItem = null; + break; + case 'mousemove': + if (dragItem) + stopped = callEvent(this, 'mousedrag', event, point, + dragItem, lastPoint); + if (!stopped) { + if (item !== overItem) + overPoint = point; + stopped = callEvent(this, type, event, point, item, + overPoint); + } + lastPoint = overPoint = point; + if (item !== overItem) { + callEvent(this, 'mouseleave', event, point, overItem); + overItem = item; + callEvent(this, 'mouseenter', event, point, item); + } + break; + } + return stopped; } }; }); @@ -10132,14 +10922,17 @@ this.event = event; }, + isPrevented: false, + isStopped: false, + preventDefault: function() { - this._prevented = true; - DomEvent.preventDefault(this.event); + this.isPrevented = true; + this.event.preventDefault(); }, stopPropagation: function() { - this._stopped = true; - DomEvent.stopPropagation(this.event); + this.isStopped = true; + this.event.stopPropagation(); }, stop: function() { @@ -10192,14 +10985,14 @@ 40: 'down', 46: 'delete', 91: 'command', - 93: 'command', - 224: 'command' + 93: 'command', + 224: 'command' }, specialChars = { - 9: true, - 13: true, - 32: true + 9: true, + 13: true, + 32: true }, modifiers = new Base({ @@ -10211,9 +11004,9 @@ space: false }), - charCodeMap = {}, - keyMap = {}, - downCode; + charCodeMap = {}, + keyMap = {}, + downCode; function handleKey(down, keyCode, charCode, event) { var character = charCode ? String.fromCharCode(charCode) : '', @@ -10222,7 +11015,7 @@ type = down ? 'keydown' : 'keyup', view = View._focused, scope = view && view.isVisible() && view._scope, - tool = scope && scope._tool, + tool = scope && scope.tool, name; keyMap[key] = down; if (specialKey && (name = Base.camelize(specialKey)) in modifiers) @@ -10236,15 +11029,17 @@ paper = scope; tool.fire(type, new KeyEvent(down, key, character, event)); if (view) - view.draw(true); + view.update(); } } DomEvent.add(document, { keydown: function(event) { var code = event.which || event.keyCode; - if (code in specialKeys) { - handleKey(true, code, code in specialChars ? code : 0, event); + if (code in specialKeys || modifiers.command) { + handleKey(true, code, + code in specialChars || modifiers.command ? code : 0, + event); } else { downCode = code; } @@ -10591,7 +11386,7 @@ getDelta: function() { return !this._delta && this.tool._lastPoint - ? this.tool._point.subtract(this.tool._lastPoint) + ? this.tool._point.subtract(this.tool._lastPoint) : this._delta; }, @@ -10616,7 +11411,7 @@ if (result) { var item = result.item, parent = item._parent; - while (/^(group|compound-path)$/.test(parent._type)) { + while (/^(Group|CompoundPath)$/.test(parent._class)) { item = parent; parent = parent._parent; } @@ -10625,6 +11420,7 @@ } return this._item; }, + setItem: function(item) { this._item = item; }, @@ -10641,7 +11437,7 @@ var Tool = PaperScopeItem.extend({ _class: 'Tool', _list: 'tools', - _reference: '_tool', + _reference: 'tool', _events: [ 'onActivate', 'onDeactivate', 'onEditOptions', 'onMouseDown', 'onMouseUp', 'onMouseDrag', 'onMouseMove', 'onKeyDown', 'onKeyUp' ], @@ -10748,7 +11544,7 @@ && this.fire(type, new ToolEvent(this, type, event)); }, - _onHandleEvent: function(type, point, event) { + _handleEvent: function(type, point, event) { paper = this._scope; var called = false; switch (type) { @@ -10786,6 +11582,8 @@ } break; } + if (called) + event.preventDefault(); return called; } @@ -10816,20 +11614,13 @@ var CanvasProvider = { canvases: [], - getCanvas: function(width, height, ratio) { + getCanvas: function(width, height) { var canvas, - init = true; + clear = true; if (typeof width === 'object') { - ratio = height; height = width.height; width = width.width; } - if (!ratio) { - ratio = 1; - } else if (ratio !== 1) { - width *= ratio; - height *= ratio; - } if (this.canvases.length) { canvas = this.canvases.pop(); } else { @@ -10837,15 +11628,13 @@ } var ctx = canvas.getContext('2d'); if (canvas.width === width && canvas.height === height) { - if (init) + if (clear) ctx.clearRect(0, 0, width + 1, height + 1); } else { canvas.width = width; canvas.height = height; } ctx.save(); - if (ratio !== 1) - ctx.scale(ratio, ratio); return canvas; }, @@ -10864,9 +11653,9 @@ var min = Math.min, max = Math.max, abs = Math.abs, - sr, sg, sb, sa, - br, bg, bb, ba, - dr, dg, db; + sr, sg, sb, sa, + br, bg, bb, ba, + dr, dg, db; function getLum(r, g, b) { return 0.2989 * r + 0.587 * g + 0.114 * b; @@ -10901,9 +11690,9 @@ function setSat(r, g, b, s) { var col = [r, g, b], - mx = max(r, g, b), - mn = min(r, g, b), - md; + mx = max(r, g, b), + mn = min(r, g, b), + md; mn = mn === r ? 0 : mn === g ? 1 : 2; mx = mx === r ? 0 : mx === g ? 1 : 2; md = min(mn, mx) === 0 ? max(mn, mx) === 1 ? 2 : 1 : 0; @@ -11048,19 +11837,21 @@ var ctx = CanvasProvider.getContext(1, 1); Base.each(modes, function(func, mode) { - ctx.save(); var darken = mode === 'darken', ok = false; - ctx.fillStyle = darken ? '#300' : '#a00'; - ctx.fillRect(0, 0, 1, 1); - ctx.globalCompositeOperation = mode; - if (ctx.globalCompositeOperation === mode) { - ctx.fillStyle = darken ? '#a00' : '#300'; + ctx.save(); + try { + ctx.fillStyle = darken ? '#300' : '#a00'; ctx.fillRect(0, 0, 1, 1); - ok = ctx.getImageData(0, 0, 1, 1).data[0] !== (darken ? 170 : 51); - } - nativeModes[mode] = ok; + ctx.globalCompositeOperation = mode; + if (ctx.globalCompositeOperation === mode) { + ctx.fillStyle = darken ? '#a00' : '#300'; + ctx.fillRect(0, 0, 1, 1); + ok = ctx.getImageData(0, 0, 1, 1).data[0] !== darken ? 170 : 51; + } + } catch (e) {} ctx.restore(); + nativeModes[mode] = ok; }); CanvasProvider.release(ctx); @@ -11074,15 +11865,15 @@ if (!normal) dstContext.globalCompositeOperation = mode; dstContext.drawImage(srcCanvas, offset.x, offset.y); - dstContext.restore(); + dstContext.restore(); } else { var process = modes[mode]; if (!process) return; var dstData = dstContext.getImageData(offset.x, offset.y, srcCanvas.width, srcCanvas.height), - dst = dstData.data, - src = srcContext.getImageData(0, 0, + dst = dstData.data, + src = srcContext.getImageData(0, 0, srcCanvas.width, srcCanvas.height).data; for (var i = 0, l = dst.length; i < l; i += 4) { sr = src[i]; @@ -11112,10 +11903,20 @@ strokeWidth: ['stroke-width', 'number'], strokeCap: ['stroke-linecap', 'string'], strokeJoin: ['stroke-linejoin', 'string'], + strokeScaling: ['vector-effect', 'lookup', { + true: 'none', + false: 'non-scaling-stroke' + }, function(item, value) { + return !value + && (item instanceof PathItem + || item instanceof Shape + || item instanceof TextItem); + }], miterLimit: ['stroke-miterlimit', 'number'], dashArray: ['stroke-dasharray', 'array'], dashOffset: ['stroke-dashoffset', 'number'], - font: ['font-family', 'string'], + fontFamily: ['font-family', 'string'], + fontWeight: ['font-weight', 'string'], fontSize: ['font-size', 'number'], justification: ['text-anchor', 'lookup', { left: 'start', @@ -11135,6 +11936,7 @@ fromSVG: lookup && Base.each(lookup, function(value, name) { this[value] = name; }, {}), + exportFilter: entry[3], get: 'get' + part, set: 'set' + part }; @@ -11168,10 +11970,9 @@ document.createElementNS('http://www.w3.org/2000/svg', tag), attrs); } - function getTransform(item, coordinates, center) { - var matrix = item._matrix, - trans = matrix.getTranslation(), - attrs = {}; + function getTransform(matrix, coordinates, center) { + var attrs = new Base(), + trans = matrix.getTranslation(); if (coordinates) { matrix = matrix.shiftless(); var point = matrix._inverseTransform(trans); @@ -11179,28 +11980,29 @@ attrs[center ? 'cy' : 'y'] = point.y; trans = null; } - if (matrix.isIdentity()) - return attrs; - var decomposed = matrix.decompose(); - if (decomposed && !decomposed.shearing) { - var parts = [], - angle = decomposed.rotation, - scale = decomposed.scaling; - if (trans && !trans.isZero()) - parts.push('translate(' + formatter.point(trans) + ')'); - if (!Numerical.isZero(scale.x - 1) || !Numerical.isZero(scale.y - 1)) - parts.push('scale(' + formatter.point(scale) +')'); - if (angle) - parts.push('rotate(' + formatter.number(angle) + ')'); - attrs.transform = parts.join(' '); - } else { - attrs.transform = 'matrix(' + matrix.getValues().join(',') + ')'; + if (!matrix.isIdentity()) { + var decomposed = matrix.decompose(); + if (decomposed && !decomposed.shearing) { + var parts = [], + angle = decomposed.rotation, + scale = decomposed.scaling; + if (trans && !trans.isZero()) + parts.push('translate(' + formatter.point(trans) + ')'); + if (angle) + parts.push('rotate(' + formatter.number(angle) + ')'); + if (!Numerical.isZero(scale.x - 1) + || !Numerical.isZero(scale.y - 1)) + parts.push('scale(' + formatter.point(scale) +')'); + attrs.transform = parts.join(' '); + } else { + attrs.transform = 'matrix(' + matrix.getValues().join(',') + ')'; + } } return attrs; } function exportGroup(item, options) { - var attrs = getTransform(item), + var attrs = getTransform(item._matrix), children = item._children; var node = createElement('g', attrs); for (var i = 0, l = children.length; i < l; i++) { @@ -11223,7 +12025,7 @@ } function exportRaster(item) { - var attrs = getTransform(item, true), + var attrs = getTransform(item._matrix, true), size = item.getSize(); attrs.x -= size.width / 2; attrs.y -= size.height / 2; @@ -11241,7 +12043,7 @@ } var segments = item._segments, type, - attrs; + attrs = getTransform(item._matrix); if (segments.length === 0) return null; if (item.isPolygon()) { @@ -11250,34 +12052,31 @@ var parts = []; for(i = 0, l = segments.length; i < l; i++) parts.push(formatter.point(segments[i]._point)); - attrs = { - points: parts.join(' ') - }; + attrs.points = parts.join(' '); } else { type = 'line'; var first = segments[0]._point, last = segments[segments.length - 1]._point; - attrs = { + attrs.set({ x1: first.x, y1: first.y, x2: last.x, y2: last.y - }; + }); } } else { type = 'path'; - var data = item.getPathData(); - attrs = data && { d: data }; + attrs.d = item.getPathData(null, options.precision); } return createElement(type, attrs); } function exportShape(item) { - var shape = item._shape, + var type = item._type, radius = item._radius, - attrs = getTransform(item, true, shape !== 'rectangle'); - if (shape === 'rectangle') { - shape = 'rect'; + attrs = getTransform(item._matrix, true, type !== 'rectangle'); + if (type === 'rectangle') { + type = 'rect'; var size = item._size, width = size.width, height = size.height; @@ -11289,26 +12088,26 @@ radius = null; } if (radius) { - if (shape === 'circle') { + if (type === 'circle') { attrs.r = radius; } else { attrs.rx = radius.width; attrs.ry = radius.height; } } - return createElement(shape, attrs); - } - - function exportCompoundPath(item) { - var attrs = getTransform(item, true); - var data = item.getPathData(); + return createElement(type, attrs); + } + + function exportCompoundPath(item, options) { + var attrs = getTransform(item._matrix); + var data = item.getPathData(null, options.precision); if (data) attrs.d = data; return createElement('path', attrs); } function exportPlacedSymbol(item, options) { - var attrs = getTransform(item, true), + var attrs = getTransform(item._matrix, true), symbol = item.getSymbol(), symbolNode = getDefinition(symbol, 'symbol'), definition = symbol.getDefinition(), @@ -11378,25 +12177,25 @@ } function exportText(item) { - var node = createElement('text', getTransform(item, true)); + var node = createElement('text', getTransform(item._matrix, true)); node.textContent = item._content; return node; } var exporters = { - group: exportGroup, - layer: exportGroup, - raster: exportRaster, - path: exportPath, - shape: exportShape, - 'compound-path': exportCompoundPath, - 'placed-symbol': exportPlacedSymbol, - 'point-text': exportText + Group: exportGroup, + Layer: exportGroup, + Raster: exportRaster, + Path: exportPath, + Shape: exportShape, + CompoundPath: exportCompoundPath, + PlacedSymbol: exportPlacedSymbol, + PointText: exportText }; - function applyStyle(item, node) { + function applyStyle(item, node, isRoot) { var attrs = {}, - parent = item.getParent(); + parent = !isRoot && item.getParent(); if (item._name != null) attrs.id = item._name; @@ -11405,7 +12204,9 @@ var get = entry.get, type = entry.type, value = item[get](); - if (!parent || !Base.equals(parent[get](), value)) { + if (entry.exportFilter + ? entry.exportFilter(item, value) + : !parent || !Base.equals(parent[get](), value)) { if (type === 'color' && value != null) { var alpha = value.getAlpha(); if (alpha < 1) @@ -11430,7 +12231,7 @@ if (attrs.opacity === 1) delete attrs.opacity; - if (item._visibility != null && !item._visibility) + if (!item._visible) attrs.visibility = 'hidden'; return setAttributes(node, attrs); @@ -11474,12 +12275,18 @@ : svg; } - function exportSVG(item, options) { - var exporter = exporters[item._type], + function exportSVG(item, options, isRoot) { + var exporter = exporters[item._class], node = exporter && exporter(item, options); - if (node && item._data) - node.setAttribute('data-paper-data', JSON.stringify(item._data)); - return node && applyStyle(item, node); + if (node) { + var onExport = options.onExport; + if (onExport) + node = onExport(item, node, options) || node; + var data = JSON.stringify(item._data); + if (data && data !== '{}') + node.setAttribute('data-paper-data', data); + } + return node && applyStyle(item, node, isRoot); } function setOptions(options) { @@ -11492,7 +12299,7 @@ Item.inject({ exportSVG: function(options) { options = setOptions(options); - return exportDefinitions(exportSVG(this, options), options); + return exportDefinitions(exportSVG(this, options, true), options); } }); @@ -11500,7 +12307,8 @@ exportSVG: function(options) { options = setOptions(options); var layers = this.layers, - size = this.view.getSize(), + view = this.getView(), + size = view.getViewSize(), node = createElement('svg', { x: 0, y: 0, @@ -11509,9 +12317,14 @@ version: '1.1', xmlns: 'http://www.w3.org/2000/svg', 'xmlns:xlink': 'http://www.w3.org/1999/xlink' - }); + }), + parent = node, + matrix = view._matrix; + if (!matrix.isIdentity()) + parent = node.appendChild( + createElement('g', getTransform(matrix))); for (var i = 0, l = layers.length; i < l; i++) - node.appendChild(exportSVG(layers[i], options)); + parent.appendChild(exportSVG(layers[i], options, true)); return exportDefinitions(node, options); } }); @@ -11565,7 +12378,7 @@ : value; } - function importGroup(node, type, isRoot, options) { + function importGroup(node, type, options, isRoot) { var nodes = node.childNodes, isClip = type === 'clippath', item = new Group(), @@ -11573,7 +12386,6 @@ currentStyle = project._currentStyle, children = []; if (!isClip) { - item._transformContent = false; item = applyAttributes(item, node, isRoot); project._currentStyle = item._style.clone(); } @@ -11581,7 +12393,7 @@ var childNode = nodes[i], child; if (childNode.nodeType === 1 - && (child = importSVG(childNode, false, options)) + && (child = importSVG(childNode, options, false)) && !(child instanceof Symbol)) children.push(child); } @@ -11597,11 +12409,14 @@ } function importPoly(node, type) { - var path = new Path(), - points = node.points; - path.moveTo(points.getItem(0)); - for (var i = 1, l = points.numberOfItems; i < l; i++) - path.lineTo(points.getItem(i)); + var coords = node.getAttribute('points').match( + /[+-]?(?:\d*\.\d+|\d+\.?)(?:[eE][+-]?\d+)?/g), + points = []; + for (var i = 0, l = coords.length; i < l; i += 2) + points.push(new Point( + parseFloat(coords[i]), + parseFloat(coords[i + 1]))); + var path = new Path(points); if (type === 'polygon') path.closePath(); return path; @@ -11609,24 +12424,29 @@ function importPath(node) { var data = node.getAttribute('d'), - path = data.match(/m/gi).length > 1 - ? new CompoundPath() - : new Path(); - path.setPathData(data); - return path; + param = { pathData: data }; + return data.match(/m/gi).length > 1 || /z\S+/i.test(data) + ? new CompoundPath(param) + : new Path(param); } function importGradient(node, type) { - var nodes = node.childNodes, - stops = []; - for (var i = 0, l = nodes.length; i < l; i++) { - var child = nodes[i]; - if (child.nodeType === 1) - stops.push(applyAttributes(new GradientStop(), child)); - } - var isRadial = type === 'radialgradient', - gradient = new Gradient(stops, isRadial), - origin, destination, highlight; + var id = (getValue(node, 'href', true) || '').substring(1), + isRadial = type === 'radialgradient', + gradient; + if (id) { + gradient = definitions[id].getGradient(); + } else { + var nodes = node.childNodes, + stops = []; + for (var i = 0, l = nodes.length; i < l; i++) { + var child = nodes[i]; + if (child.nodeType === 1) + stops.push(applyAttributes(new GradientStop(), child)); + } + gradient = new Gradient(stops, isRadial); + } + var origin, destination, highlight; if (isRadial) { origin = getPoint(node, 'cx', 'cy'); destination = origin.add(getValue(node, 'r'), 0); @@ -11641,14 +12461,14 @@ } var importers = { - '#document': function (node, type, isRoot, options) { + '#document': function (node, type, options, isRoot) { var nodes = node.childNodes; for (var i = 0, l = nodes.length; i < l; i++) { var child = nodes[i]; if (child.nodeType === 1) { var next = child.nextSibling; document.body.appendChild(child); - var item = importSVG(child, isRoot, options); + var item = importSVG(child, options, isRoot); if (next) { node.insertBefore(child, next); } else { @@ -11679,8 +12499,8 @@ return raster; }, - symbol: function(node, type, isRoot, options) { - return new Symbol(importGroup(node, type, isRoot, options), true); + symbol: function(node, type, options, isRoot) { + return new Symbol(importGroup(node, type, options, isRoot), true); }, defs: importGroup, @@ -11755,10 +12575,10 @@ matrix.scale(v); break; case 'skewX': + matrix.skew(v[0], 0); + break; case 'skewY': - var value = Math.tan(v[0] * Math.PI / 180), - isX = command == 'skewX'; - matrix.shear(isX ? value : 0, isX ? 0 : value); + matrix.skew(0, v[0]); break; } } @@ -11812,6 +12632,10 @@ item.setVisible(value === 'visible'); }, + display: function(item, value) { + item.setVisible(value !== null); + }, + 'stop-color': function(item, value) { if (item.setColor) item.setColor(value); @@ -11851,6 +12675,7 @@ }); function getAttribute(node, name, styles) { + ppp = node; var attr = node.attributes[name], value = attr && attr.value; if (!value) { @@ -11885,7 +12710,7 @@ return match && definitions[match[1]]; } - function importSVG(source, isRoot, options) { + function importSVG(source, options, isRoot) { if (!source) return null; if (!options) { @@ -11899,12 +12724,12 @@ function onLoadCallback(svg) { paper = scope; - var item = importSVG(svg, isRoot, options), + var item = importSVG(svg, options, isRoot), onLoad = options.onLoad, - view = scope.project && scope.project.view; + view = scope.project && scope.getView(); if (onLoad) onLoad.call(this, item); - view.draw(true); + view.update(); } if (isRoot) { @@ -11930,11 +12755,19 @@ throw new Error('Unsupported SVG source: ' + source); var type = node.nodeName.toLowerCase(), importer = importers[type], - item = importer && importer(node, type, isRoot, options) || null, - data = node.getAttribute && node.getAttribute('data-paper-data'); + item, + data = node.getAttribute && node.getAttribute('data-paper-data'), + settings = scope.settings, + prevApplyMatrix = settings.applyMatrix; + settings.applyMatrix = false; + item = importer && importer(node, type, options, isRoot) || null; + settings.applyMatrix = prevApplyMatrix; if (item) { - if (!(item instanceof Group)) + if (type !== '#document' && !(item instanceof Group)) item = applyAttributes(item, node, isRoot); + var onImport = options.onImport; + if (onImport) + item = onImport(node, item, options) || item; if (options.expandShapes && item instanceof Shape) { item.remove(); item = item.toPath(); @@ -11949,69 +12782,70 @@ Item.inject({ importSVG: function(node, options) { - return this.addChild(importSVG(node, true, options)); + return this.addChild(importSVG(node, options, true)); } }); Project.inject({ importSVG: function(node, options) { this.activate(); - return importSVG(node, true, options); + return importSVG(node, options, true); } }); }; -paper = new (PaperScope.inject(new Base(Base.exports, { - enumerable: true, - Base: Base, - Numerical: Numerical, - DomElement: DomElement, - DomEvent: DomEvent, - Http: Http, - Key: Key -})))(); - -if (typeof define === 'function' && define.amd) - define('paper', paper); - -return paper; -}; - -paper.PaperScope.prototype.PaperScript = (function(root) { - var Base = paper.Base, - PaperScope = paper.PaperScope, - PaperScript, - exports, define, +Base.exports.PaperScript = (function() { + var exports, define, scope = this; !function(e,r){return"object"==typeof exports&&"object"==typeof module?r(exports):"function"==typeof define&&define.amd?define(["exports"],r):(r(e.acorn||(e.acorn={})),void 0)}(this,function(e){"use strict";function r(e){fr=e||{};for(var r in hr)Object.prototype.hasOwnProperty.call(fr,r)||(fr[r]=hr[r]);mr=fr.sourceFile||null}function t(e,r){var t=vr(pr,e);r+=" ("+t.line+":"+t.column+")";var n=new SyntaxError(r);throw n.pos=e,n.loc=t,n.raisedAt=br,n}function n(e){function r(e){if(1==e.length)return t+="return str === "+JSON.stringify(e[0])+";";t+="switch(str){";for(var r=0;r3){n.sort(function(e,r){return r.length-e.length}),t+="switch(str.length){";for(var a=0;abr&&10!==t&&13!==t&&8232!==t&&8329!==t;)++br,t=pr.charCodeAt(br);fr.onComment&&fr.onComment(!1,pr.slice(e+2,br),e,br,r,fr.locations&&new a)}function u(){for(;dr>br;){var e=pr.charCodeAt(br);if(32===e)++br;else if(13===e){++br;var r=pr.charCodeAt(br);10===r&&++br,fr.locations&&(++Ar,Sr=br)}else if(10===e)++br,++Ar,Sr=br;else if(14>e&&e>8)++br;else if(47===e){var r=pr.charCodeAt(br+1);if(42===r)s();else{if(47!==r)break;c()}}else if(160===e)++br;else{if(!(e>=5760&&Jt.test(String.fromCharCode(e))))break;++br}}}function l(){var e=pr.charCodeAt(br+1);return e>=48&&57>=e?E(!0):(++br,i(xt))}function f(){var e=pr.charCodeAt(br+1);return Er?(++br,k()):61===e?x(Et,2):x(wt,1)}function p(){var e=pr.charCodeAt(br+1);return 61===e?x(Et,2):x(Ft,1)}function d(e){var r=pr.charCodeAt(br+1);return r===e?x(124===e?Lt:Ut,2):61===r?x(Et,2):x(124===e?Rt:Vt,1)}function m(){var e=pr.charCodeAt(br+1);return 61===e?x(Et,2):x(Tt,1)}function h(e){var r=pr.charCodeAt(br+1);return r===e?x(St,2):61===r?x(Et,2):x(At,1)}function v(e){var r=pr.charCodeAt(br+1),t=1;return r===e?(t=62===e&&62===pr.charCodeAt(br+2)?3:2,61===pr.charCodeAt(br+t)?x(Et,t+1):x(jt,t)):(61===r&&(t=61===pr.charCodeAt(br+2)?3:2),x(Ot,t))}function b(e){var r=pr.charCodeAt(br+1);return 61===r?x(qt,61===pr.charCodeAt(br+2)?3:2):x(61===e?Ct:It,1)}function y(e){switch(e){case 46:return l();case 40:return++br,i(ht);case 41:return++br,i(vt);case 59:return++br,i(yt);case 44:return++br,i(bt);case 91:return++br,i(ft);case 93:return++br,i(pt);case 123:return++br,i(dt);case 125:return++br,i(mt);case 58:return++br,i(gt);case 63:return++br,i(kt);case 48:var r=pr.charCodeAt(br+1);if(120===r||88===r)return C();case 49:case 50:case 51:case 52:case 53:case 54:case 55:case 56:case 57:return E(!1);case 34:case 39:return A(e);case 47:return f(e);case 37:case 42:return p();case 124:case 38:return d(e);case 94:return m();case 43:case 45:return h(e);case 60:case 62:return v(e);case 61:case 33:return b(e);case 126:return x(It,1)}return!1}function g(e){if(e?br=yr+1:yr=br,fr.locations&&(xr=new a),e)return k();if(br>=dr)return i(Br);var r=pr.charCodeAt(br);if(Qt(r)||92===r)return L();var n=y(r);if(n===!1){var o=String.fromCharCode(r);if("\\"===o||$t.test(o))return L();t(br,"Unexpected character '"+o+"'")}return n}function x(e,r){var t=pr.slice(br,br+r);br+=r,i(e,t)}function k(){for(var e,r,n="",a=br;;){br>=dr&&t(a,"Unterminated regular expression");var o=pr.charAt(br);if(Gt.test(o)&&t(a,"Unterminated regular expression"),e)e=!1;else{if("["===o)r=!0;else if("]"===o&&r)r=!1;else if("/"===o&&!r)break;e="\\"===o}++br}var n=pr.slice(a,br);++br;var s=I();return s&&!/^[gmsiy]*$/.test(s)&&t(a,"Invalid regexp flag"),i(jr,new RegExp(n,s))}function w(e,r){for(var t=br,n=0,a=0,o=null==r?1/0:r;o>a;++a){var i,s=pr.charCodeAt(br);if(i=s>=97?s-97+10:s>=65?s-65+10:s>=48&&57>=s?s-48:1/0,i>=e)break;++br,n=n*e+i}return br===t||null!=r&&br-t!==r?null:n}function C(){br+=2;var e=w(16);return null==e&&t(yr+2,"Expected hexadecimal number"),Qt(pr.charCodeAt(br))&&t(br,"Identifier directly after number"),i(Or,e)}function E(e){var r=br,n=!1,a=48===pr.charCodeAt(br);e||null!==w(10)||t(r,"Invalid number"),46===pr.charCodeAt(br)&&(++br,w(10),n=!0);var o=pr.charCodeAt(br);(69===o||101===o)&&(o=pr.charCodeAt(++br),(43===o||45===o)&&++br,null===w(10)&&t(r,"Invalid number"),n=!0),Qt(pr.charCodeAt(br))&&t(br,"Identifier directly after number");var s,c=pr.slice(r,br);return n?s=parseFloat(c):a&&1!==c.length?/[89]/.test(c)||Vr?t(r,"Invalid number"):s=parseInt(c,8):s=parseInt(c,10),i(Or,s)}function A(e){br++;for(var r="";;){br>=dr&&t(yr,"Unterminated string constant");var n=pr.charCodeAt(br);if(n===e)return++br,i(Fr,r);if(92===n){n=pr.charCodeAt(++br);var a=/^[0-7]+/.exec(pr.slice(br,br+3));for(a&&(a=a[0]);a&&parseInt(a,8)>255;)a=a.slice(0,a.length-1);if("0"===a&&(a=null),++br,a)Vr&&t(br-2,"Octal literal in strict mode"),r+=String.fromCharCode(parseInt(a,8)),br+=a.length-1;else switch(n){case 110:r+="\n";break;case 114:r+="\r";break;case 120:r+=String.fromCharCode(S(2));break;case 117:r+=String.fromCharCode(S(4));break;case 85:r+=String.fromCharCode(S(8));break;case 116:r+=" ";break;case 98:r+="\b";break;case 118:r+=" ";break;case 102:r+="\f";break;case 48:r+="\0";break;case 13:10===pr.charCodeAt(br)&&++br;case 10:fr.locations&&(Sr=br,++Ar);break;default:r+=String.fromCharCode(n)}}else(13===n||10===n||8232===n||8329===n)&&t(yr,"Unterminated string constant"),r+=String.fromCharCode(n),++br}}function S(e){var r=w(16,e);return null===r&&t(yr,"Bad character escape sequence"),r}function I(){Bt=!1;for(var e,r=!0,n=br;;){var a=pr.charCodeAt(br);if(Yt(a))Bt&&(e+=pr.charAt(br)),++br;else{if(92!==a)break;Bt||(e=pr.slice(n,br)),Bt=!0,117!=pr.charCodeAt(++br)&&t(br,"Expecting Unicode escape sequence \\uXXXX"),++br;var o=S(4),i=String.fromCharCode(o);i||t(br-1,"Invalid Unicode escape"),(r?Qt(o):Yt(o))||t(br-4,"Invalid Unicode escape"),e+=i}r=!1}return Bt?e:pr.slice(n,br)}function L(){var e=I(),r=Dr;return Bt||(Wt(e)?r=lt[e]:(fr.forbidReserved&&(3===fr.ecmaVersion?Mt:zt)(e)||Vr&&Xt(e))&&t(yr,"The keyword '"+e+"' is reserved")),i(r,e)}function U(){Ir=yr,Lr=gr,Ur=kr,g()}function R(e){for(Vr=e,br=Lr;Sr>br;)Sr=pr.lastIndexOf("\n",Sr-2)+1,--Ar;u(),g()}function T(){this.type=null,this.start=yr,this.end=null}function V(){this.start=xr,this.end=null,null!==mr&&(this.source=mr)}function q(){var e=new T;return fr.locations&&(e.loc=new V),fr.ranges&&(e.range=[yr,0]),e}function O(e){var r=new T;return r.start=e.start,fr.locations&&(r.loc=new V,r.loc.start=e.loc.start),fr.ranges&&(r.range=[e.range[0],0]),r}function j(e,r){return e.type=r,e.end=Lr,fr.locations&&(e.loc.end=Ur),fr.ranges&&(e.range[1]=Lr),e}function F(e){return fr.ecmaVersion>=5&&"ExpressionStatement"===e.type&&"Literal"===e.expression.type&&"use strict"===e.expression.value}function D(e){return wr===e?(U(),!0):void 0}function B(){return!fr.strictSemicolons&&(wr===Br||wr===mt||Gt.test(pr.slice(Lr,yr)))}function M(){D(yt)||B()||X()}function z(e){wr===e?U():X()}function X(){t(yr,"Unexpected token")}function N(e){"Identifier"!==e.type&&"MemberExpression"!==e.type&&t(e.start,"Assigning to rvalue"),Vr&&"Identifier"===e.type&&Nt(e.name)&&t(e.start,"Assigning to "+e.name+" in strict mode")}function W(e){Ir=Lr=br,fr.locations&&(Ur=new a),Rr=Vr=null,Tr=[],g();var r=e||q(),t=!0;for(e||(r.body=[]);wr!==Br;){var n=J();r.body.push(n),t&&F(n)&&R(!0),t=!1}return j(r,"Program")}function J(){wr===wt&&g(!0);var e=wr,r=q();switch(e){case Mr:case Nr:U();var n=e===Mr;D(yt)||B()?r.label=null:wr!==Dr?X():(r.label=lr(),M());for(var a=0;ar){var a=O(e);a.left=e,a.operator=Cr,U(),a.right=er(rr(),n,t);var a=j(a,/&&|\|\|/.test(a.operator)?"LogicalExpression":"BinaryExpression");return er(a,r,t)}return e}function rr(){if(wr.prefix){var e=q(),r=wr.isUpdate;return e.operator=Cr,e.prefix=!0,U(),e.argument=rr(),r?N(e.argument):Vr&&"delete"===e.operator&&"Identifier"===e.argument.type&&t(e.start,"Deleting local variable in strict mode"),j(e,r?"UpdateExpression":"UnaryExpression")}for(var n=tr();wr.postfix&&!B();){var e=O(n);e.operator=Cr,e.prefix=!1,e.argument=n,N(n),U(),n=j(e,"UpdateExpression")}return n}function tr(){return nr(ar())}function nr(e,r){if(D(xt)){var t=O(e);return t.object=e,t.property=lr(!0),t.computed=!1,nr(j(t,"MemberExpression"),r)}if(D(ft)){var t=O(e);return t.object=e,t.property=K(),t.computed=!0,z(pt),nr(j(t,"MemberExpression"),r)}if(!r&&D(ht)){var t=O(e);return t.callee=e,t.arguments=ur(vt,!1),nr(j(t,"CallExpression"),r)}return e}function ar(){switch(wr){case ot:var e=q();return U(),j(e,"ThisExpression");case Dr:return lr();case Or:case Fr:case jr:var e=q();return e.value=Cr,e.raw=pr.slice(yr,gr),U(),j(e,"Literal");case it:case st:case ct:var e=q();return e.value=wr.atomValue,e.raw=wr.keyword,U(),j(e,"Literal");case ht:var r=xr,t=yr;U();var n=K();return n.start=t,n.end=gr,fr.locations&&(n.loc.start=r,n.loc.end=kr),fr.ranges&&(n.range=[t,gr]),z(vt),n;case ft:var e=q();return U(),e.elements=ur(pt,!0,!0),j(e,"ArrayExpression");case dt:return ir();case Gr:var e=q();return U(),cr(e,!1);case at:return or();default:X()}}function or(){var e=q();return U(),e.callee=nr(ar(),!0),e.arguments=D(ht)?ur(vt,!1):qr,j(e,"NewExpression")}function ir(){var e=q(),r=!0,n=!1;for(e.properties=[],U();!D(mt);){if(r)r=!1;else if(z(bt),fr.allowTrailingCommas&&D(mt))break;var a,o={key:sr()},i=!1;if(D(gt)?(o.value=K(!0),a=o.kind="init"):fr.ecmaVersion>=5&&"Identifier"===o.key.type&&("get"===o.key.name||"set"===o.key.name)?(i=n=!0,a=o.kind=o.key.name,o.key=sr(),wr!==ht&&X(),o.value=cr(q(),!1)):X(),"Identifier"===o.key.type&&(Vr||n))for(var s=0;si?e.id:e.params[i];if((Xt(s.name)||Nt(s.name))&&t(s.start,"Defining '"+s.name+"' in strict mode"),i>=0)for(var c=0;i>c;++c)s.name===e.params[c].name&&t(s.start,"Argument name clash in strict mode")}return j(e,r?"FunctionDeclaration":"FunctionExpression")}function ur(e,r,t){for(var n=[],a=!0;!D(e);){if(a)a=!1;else if(z(bt),r&&fr.allowTrailingCommas&&D(e))break;t&&wr===bt?n.push(null):n.push(K(!0))}return n}function lr(e){var r=q();return r.name=wr===Dr?Cr:e&&!fr.forbidReserved&&wr.keyword||X(),U(),j(r,"Identifier")}e.version="0.3.2";var fr,pr,dr,mr;e.parse=function(e,t){return pr=String(e),dr=pr.length,r(t),o(),W(fr.program)};var hr=e.defaultOptions={ecmaVersion:5,strictSemicolons:!1,allowTrailingCommas:!0,forbidReserved:!1,locations:!1,onComment:null,ranges:!1,program:null,sourceFile:null},vr=e.getLineInfo=function(e,r){for(var t=1,n=0;;){Kt.lastIndex=n;var a=Kt.exec(e);if(!(a&&a.indexe?36===e:91>e?!0:97>e?95===e:123>e?!0:e>=170&&$t.test(String.fromCharCode(e))},Yt=e.isIdentifierChar=function(e){return 48>e?36===e:58>e?!0:65>e?!1:91>e?!0:97>e?95===e:123>e?!0:e>=170&&_t.test(String.fromCharCode(e))},Zt={kind:"loop"},en={kind:"switch"}}); + var ua = navigator.userAgent, + browser = {}; + ua.toLowerCase().replace( + /(opera|chrome|safari|webkit|firefox|msie|trident)\/?\s*([.\d]+)(?:.*version\/([.\d]+))?(?:.*rv\:([.\d]+))?/g, + function(all, n, v1, v2, rv) { + if (!browser.chrome) { + var v = n === 'opera' ? v2 : v1; + if (n === 'trident') { + v = rv; + n = 'msie'; + } + browser.version = parseFloat(v); + browser.name = n; + browser[n] = true; + if (browser.chrome) + delete browser.webkit; + } + } + ); + var binaryOperators = { - '+': '_add', - '-': '_subtract', - '*': '_multiply', - '/': '_divide', - '%': '_modulo', + '+': '__add', + '-': '__subtract', + '*': '__multiply', + '/': '__divide', + '%': '__modulo', '==': 'equals', '!=': 'equals' }; var unaryOperators = { - '-': '_negate', + '-': '__negate', '+': null }; var fields = Base.each( ['add', 'subtract', 'multiply', 'divide', 'modulo', 'negate'], function(name) { - this['_' + name] = '#' + name; - }, + this['__' + name] = '#' + name; + }, {} ); - paper.Point.inject(fields); - paper.Size.inject(fields); - paper.Color.inject(fields); - - function _$_(left, operator, right) { + Point.inject(fields); + Size.inject(fields); + Color.inject(fields); + + function __$__(left, operator, right) { var handler = binaryOperators[operator]; if (left && left[handler]) { var res = left[handler](right); @@ -12028,7 +12862,7 @@ } } - function $_(operator, value) { + function $__(operator, value) { var handler = unaryOperators[operator]; if (handler && value && value[handler]) return value[handler](); @@ -12038,7 +12872,15 @@ } } - function compile(code) { + function parse(code, options) { + return scope.acorn.parse(code, options); + } + + function compile(code, url, options) { + if (!code) + return ''; + options = options || {}; + url = url || ''; var insertions = []; @@ -12059,8 +12901,8 @@ function replaceCode(node, str) { var start = getOffset(node.range[0]), - end = getOffset(node.range[1]); - var insert = 0; + end = getOffset(node.range[1]), + insert = 0; for (var i = insertions.length - 1; i >= 0; i--) { if (start > insertions[i][0]) { insert = i + 1; @@ -12075,7 +12917,7 @@ if (!node) return; for (var key in node) { - if (key === 'range') + if (key === 'range' || key === 'loc') continue; var value = node[key]; if (Array.isArray(value)) { @@ -12085,134 +12927,222 @@ walkAST(value, node); } } - switch (node && node.type) { + switch (node.type) { + case 'UnaryExpression': + if (node.operator in unaryOperators + && node.argument.type !== 'Literal') { + var arg = getCode(node.argument); + replaceCode(node, '$__("' + node.operator + '", ' + + arg + ')'); + } + break; case 'BinaryExpression': if (node.operator in binaryOperators && node.left.type !== 'Literal') { var left = getCode(node.left), right = getCode(node.right); - replaceCode(node, '_$_(' + left + ', "' + node.operator + replaceCode(node, '__$__(' + left + ', "' + node.operator + '", ' + right + ')'); } break; - case 'AssignmentExpression': - if (/^.=$/.test(node.operator) - && node.left.type !== 'Literal') { - var left = getCode(node.left), - right = getCode(node.right); - replaceCode(node, left + ' = _$_(' + left + ', "' - + node.operator[0] + '", ' + right + ')'); - } - break; case 'UpdateExpression': - if (!node.prefix && !(parent && ( - parent.type === 'BinaryExpression' + case 'AssignmentExpression': + var parentType = parent && parent.type; + if (!( + parentType === 'ForStatement' + || parentType === 'BinaryExpression' && /^[=!<>]/.test(parent.operator) - || parent.type === 'MemberExpression' - && parent.computed))) { - var arg = getCode(node.argument); - replaceCode(node, arg + ' = _$_(' + arg + ', "' - + node.operator[0] + '", 1)'); - } - break; - case 'UnaryExpression': - if (node.operator in unaryOperators - && node.argument.type !== 'Literal') { - var arg = getCode(node.argument); - replaceCode(node, '$_("' + node.operator + '", ' - + arg + ')'); + || parentType === 'MemberExpression' && parent.computed + )) { + if (node.type === 'UpdateExpression') { + var arg = getCode(node.argument); + var str = arg + ' = __$__(' + arg + + ', "' + node.operator[0] + '", 1)'; + if (!node.prefix + && (parentType === 'AssignmentExpression' + || parentType === 'VariableDeclarator')) + str = arg + '; ' + str; + replaceCode(node, str); + } else { + if (/^.=$/.test(node.operator) + && node.left.type !== 'Literal') { + var left = getCode(node.left), + right = getCode(node.right); + replaceCode(node, left + ' = __$__(' + left + ', "' + + node.operator[0] + '", ' + right + ')'); + } + } } break; } } - walkAST(scope.acorn.parse(code, { ranges: true })); + var sourceMap = null, + version = browser.version, + lineBreaks = /\r\n|\n|\r/mg; + if (browser.chrome && version >= 30 + || browser.webkit && version >= 537.76 + || browser.firefox && version >= 23) { + var offset = 0; + if (window.location.href.indexOf(url) === 0) { + var html = document.getElementsByTagName('html')[0].innerHTML; + offset = html.substr(0, html.indexOf(code) + 1).match( + lineBreaks).length + 1; + } + var mappings = ['AAAA']; + mappings.length = (code.match(lineBreaks) || []).length + 1 + offset; + sourceMap = { + version: 3, + file: url, + names:[], + mappings: mappings.join(';AACA'), + sourceRoot: '', + sources: [url] + }; + var source = options.source || !url && code; + if (source) + sourceMap.sourcesContent = [source]; + } + walkAST(parse(code, { ranges: true })); + if (sourceMap) { + code = new Array(offset + 1).join('\n') + code + + "\n//# sourceMappingURL=data:application/json;base64," + + (btoa(unescape(encodeURIComponent( + JSON.stringify(sourceMap))))) + + "\n//# sourceURL=" + (url || 'paperscript'); + } return code; } - function evaluate(code, scope) { + function execute(code, scope, url, options) { paper = scope; - var view = scope.project && scope.project.view, - res; - with (scope) { - (function() { - var onActivate, onDeactivate, onEditOptions, - onMouseDown, onMouseUp, onMouseDrag, onMouseMove, - onKeyDown, onKeyUp, onFrame, onResize; - code = compile(code); - if (root.InstallTrigger) { - var handle = PaperScript.handleException; - if (!handle) { - handle = PaperScript.handleException = function(e) { - throw e.lineNumber >= lineNumber - ? new Error(e.message, e.fileName, - e.lineNumber - lineNumber) - : e; - } - var lineNumber = new Error().lineNumber; - lineNumber += (new Error().lineNumber - lineNumber) * 3; - } - try { - res = eval(';' + code); - } catch (e) { - handle(e); - } - } else { - res = eval(code); - } - if (/on(?:Key|Mouse)(?:Up|Down|Move|Drag)/.test(code)) { - Base.each(paper.Tool.prototype._events, function(key) { - var value = eval(key); - if (value) { - scope.getTool()[key] = value; - } - }); - } - if (view) { - view.setOnResize(onResize); - view.fire('resize', { - size: view.size, - delta: new Point() - }); - if (onFrame) - view.setOnFrame(onFrame); - view.draw(); - } - }).call(scope); - } - return res; - } - - function load() { - Base.each(document.getElementsByTagName('script'), function(script) { - if (/^text\/(?:x-|)paperscript$/.test(script.type) - && !script.getAttribute('data-paper-ignore')) { - var canvas = PaperScope.getAttribute(script, 'canvas'), - scope = PaperScope.get(canvas) - || new PaperScope(script).setup(canvas), - src = script.src; - if (src) { - paper.Http.request('get', src, function(code) { - evaluate(code, scope); - }); - } else { - evaluate(script.innerHTML, scope); - } - script.setAttribute('data-paper-ignore', true); - } - }, this); + var view = scope.getView(), + tool = /\s+on(?:Key|Mouse)(?:Up|Down|Move|Drag)\b/.test(code) + ? new Tool() + : null, + toolHandlers = tool ? tool._events : [], + handlers = ['onFrame', 'onResize'].concat(toolHandlers), + params = [], + args = [], + func; + code = compile(code, url, options); + function expose(scope, hidden) { + for (var key in scope) { + if ((hidden || !/^_/.test(key)) && new RegExp('([\\b\\s\\W]|^)' + + key.replace(/\$/g, '\\$') + '\\b').test(code)) { + params.push(key); + args.push(scope[key]); + } + } + } + expose({ __$__: __$__, $__: $__, paper: scope, view: view, tool: tool }, + true); + expose(scope); + handlers = Base.each(handlers, function(key) { + if (new RegExp('\\s+' + key + '\\b').test(code)) { + params.push(key); + this.push(key + ': ' + key); + } + }, []).join(', '); + if (handlers) + code += '\nreturn { ' + handlers + ' };'; + if (browser.chrome || browser.firefox) { + var script = document.createElement('script'), + head = document.head; + if (browser.firefox) + code = '\n' + code; + script.appendChild(document.createTextNode( + 'paper._execute = function(' + params + ') {' + code + '\n}' + )); + head.appendChild(script); + func = paper._execute; + delete paper._execute; + head.removeChild(script); + } else { + func = Function(params, code); + } + var res = func.apply(scope, args) || {}; + Base.each(toolHandlers, function(key) { + var value = res[key]; + if (value) + tool[key] = value; + }); + if (view) { + if (res.onResize) + view.setOnResize(res.onResize); + view.fire('resize', { + size: view.size, + delta: new Point() + }); + if (res.onFrame) + view.setOnFrame(res.onFrame); + view.update(); + } + } + + function loadScript(script) { + if (/^text\/(?:x-|)paperscript$/.test(script.type) + && PaperScope.getAttribute(script, 'ignore') !== 'true') { + var canvasId = PaperScope.getAttribute(script, 'canvas'), + canvas = document.getElementById(canvasId), + src = script.src, + scopeAttribute = 'data-paper-scope'; + if (!canvas) + throw new Error('Unable to find canvas with id "' + + canvasId + '"'); + var scope = PaperScope.get(canvas.getAttribute(scopeAttribute)) + || new PaperScope().setup(canvas); + canvas.setAttribute(scopeAttribute, scope._id); + if (src) { + Http.request('get', src, function(code) { + execute(code, scope, src); + }); + } else { + execute(script.innerHTML, scope, script.baseURI); + } + script.setAttribute('data-paper-ignore', 'true'); + return scope; + } + } + + function loadAll() { + Base.each(document.getElementsByTagName('script'), loadScript); + } + + function load(script) { + return script ? loadScript(script) : loadAll(); } if (document.readyState === 'complete') { - setTimeout(load); + setTimeout(loadAll); } else { - paper.DomEvent.add(window, { load: load }); - } - - return PaperScript = { + DomEvent.add(window, { load: loadAll }); + } + + return { compile: compile, - evaluate: evaluate, + execute: execute, load: load, - lineNumberBase: 0 + parse: parse }; -})(this); +}).call(this); + +paper = new (PaperScope.inject(Base.exports, { + enumerable: true, + Base: Base, + Numerical: Numerical, + DomElement: DomElement, + DomEvent: DomEvent, + Http: Http, + Key: Key +}))(); + +if (typeof define === 'function' && define.amd) { + define('paper', paper); +} else if (typeof module === 'object' && module + && typeof module.exports === 'object') { + module.exports = paper; +} + +return paper; +};