2011-03-06 19:50:44 -05:00
|
|
|
/*
|
2013-01-28 21:03:27 -05:00
|
|
|
* Paper.js - The Swiss Army Knife of Vector Graphics Scripting.
|
2011-03-07 20:41:50 -05:00
|
|
|
* http://paperjs.org/
|
2011-06-30 06:01:51 -04:00
|
|
|
*
|
2014-01-03 19:47:16 -05:00
|
|
|
* Copyright (c) 2011 - 2014, Juerg Lehni & Jonathan Puckey
|
|
|
|
* http://scratchdisk.com/ & http://jonathanpuckey.com/
|
2011-06-30 06:01:51 -04:00
|
|
|
*
|
2011-07-01 06:17:45 -04:00
|
|
|
* Distributed under the MIT license. See LICENSE file for details.
|
|
|
|
*
|
2011-03-07 20:41:50 -05:00
|
|
|
* All rights reserved.
|
2011-03-06 19:50:44 -05:00
|
|
|
*/
|
|
|
|
|
2011-06-22 18:56:05 -04:00
|
|
|
/**
|
|
|
|
* @name CompoundPath
|
2011-06-30 06:01:51 -04:00
|
|
|
*
|
2011-06-22 18:56:05 -04:00
|
|
|
* @class A compound path contains two or more paths, holes are drawn
|
|
|
|
* where the paths overlap. All the paths in a compound path take on the
|
|
|
|
* style of the backmost path and can be accessed through its
|
|
|
|
* {@link Item#children} list.
|
2011-06-30 06:01:51 -04:00
|
|
|
*
|
2011-06-22 18:56:05 -04:00
|
|
|
* @extends PathItem
|
|
|
|
*/
|
2013-05-27 15:48:58 -04:00
|
|
|
var CompoundPath = PathItem.extend(/** @lends CompoundPath# */{
|
2014-08-16 13:24:54 -04:00
|
|
|
_class: 'CompoundPath',
|
|
|
|
_serializeFields: {
|
|
|
|
children: []
|
|
|
|
},
|
2013-02-28 22:41:13 -05:00
|
|
|
|
2014-08-16 13:24:54 -04:00
|
|
|
/**
|
|
|
|
* Creates a new compound path item and places it in the active layer.
|
|
|
|
*
|
2015-06-16 11:50:37 -04:00
|
|
|
* @param {Path[]} [paths] the paths to place within the compound path
|
2014-08-16 13:24:54 -04:00
|
|
|
*
|
|
|
|
* @example {@paperscript}
|
|
|
|
* // Create a circle shaped path with a hole in it:
|
|
|
|
* var circle = new Path.Circle({
|
|
|
|
* center: new Point(50, 50),
|
|
|
|
* radius: 30
|
|
|
|
* });
|
|
|
|
*
|
|
|
|
* var innerCircle = new Path.Circle({
|
|
|
|
* center: new Point(50, 50),
|
|
|
|
* radius: 10
|
|
|
|
* });
|
|
|
|
*
|
|
|
|
* var compoundPath = new CompoundPath([circle, innerCircle]);
|
|
|
|
* compoundPath.fillColor = 'red';
|
|
|
|
*
|
|
|
|
* // Move the inner circle 5pt to the right:
|
|
|
|
* compoundPath.children[1].position.x += 5;
|
|
|
|
*/
|
|
|
|
/**
|
|
|
|
* Creates a new compound path item from an object description and places it
|
|
|
|
* at the top of the active layer.
|
|
|
|
*
|
|
|
|
* @name CompoundPath#initialize
|
|
|
|
* @param {Object} object an object literal containing properties to
|
|
|
|
* be set on the path
|
|
|
|
* @return {CompoundPath} the newly created path
|
|
|
|
*
|
|
|
|
* @example {@paperscript}
|
|
|
|
* var path = new CompoundPath({
|
|
|
|
* children: [
|
|
|
|
* new Path.Circle({
|
|
|
|
* center: new Point(50, 50),
|
|
|
|
* radius: 30
|
|
|
|
* }),
|
|
|
|
* new Path.Circle({
|
|
|
|
* center: new Point(50, 50),
|
|
|
|
* radius: 10
|
|
|
|
* })
|
|
|
|
* ],
|
|
|
|
* fillColor: 'black',
|
|
|
|
* selected: true
|
|
|
|
* });
|
|
|
|
*/
|
|
|
|
/**
|
|
|
|
* Creates a new compound path item from SVG path-data and places it at the
|
|
|
|
* top of the active layer.
|
|
|
|
*
|
|
|
|
* @name CompoundPath#initialize
|
|
|
|
* @param {String} pathData the SVG path-data that describes the geometry
|
2015-06-16 11:50:37 -04:00
|
|
|
* of this path
|
2014-08-16 13:24:54 -04:00
|
|
|
* @return {CompoundPath} the newly created path
|
|
|
|
*
|
|
|
|
* @example {@paperscript}
|
|
|
|
* var pathData = 'M20,50c0,-16.56854 13.43146,-30 30,-30c16.56854,0 30,13.43146 30,30c0,16.56854 -13.43146,30 -30,30c-16.56854,0 -30,-13.43146 -30,-30z M50,60c5.52285,0 10,-4.47715 10,-10c0,-5.52285 -4.47715,-10 -10,-10c-5.52285,0 -10,4.47715 -10,10c0,5.52285 4.47715,10 10,10z';
|
|
|
|
* var path = new CompoundPath(pathData);
|
|
|
|
* path.fillColor = 'black';
|
|
|
|
*/
|
|
|
|
initialize: function CompoundPath(arg) {
|
|
|
|
// CompoundPath has children and supports named children.
|
|
|
|
this._children = [];
|
|
|
|
this._namedChildren = {};
|
|
|
|
if (!this._initialize(arg)) {
|
|
|
|
if (typeof arg === 'string') {
|
|
|
|
this.setPathData(arg);
|
|
|
|
} else {
|
|
|
|
this.addChildren(Array.isArray(arg) ? arg : arguments);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
2011-07-27 14:30:39 -04:00
|
|
|
|
2014-08-16 13:24:54 -04:00
|
|
|
insertChildren: function insertChildren(index, items, _preserve) {
|
2015-09-13 10:06:24 -04:00
|
|
|
// Convert CompoundPath items in the children list by adding their
|
|
|
|
// children to the list and removing their parent.
|
|
|
|
for (var i = items.length - 1; i >= 0; i--) {
|
|
|
|
var item = items[i];
|
|
|
|
if (item instanceof CompoundPath) {
|
|
|
|
items.splice.apply(items, [i, 1].concat(item.removeChildren()));
|
|
|
|
item.remove();
|
|
|
|
}
|
|
|
|
}
|
2014-08-16 13:24:54 -04:00
|
|
|
// Pass on 'path' for _type, to make sure that only paths are added as
|
|
|
|
// children.
|
|
|
|
items = insertChildren.base.call(this, index, items, _preserve, Path);
|
|
|
|
// All children except for the bottom one (first one in list) are set
|
|
|
|
// to anti-clockwise orientation, so that they appear as holes, but
|
|
|
|
// only if their orientation was not already specified before
|
|
|
|
// (= _clockwise is defined).
|
|
|
|
for (var i = 0, l = !_preserve && items && items.length; i < l; i++) {
|
|
|
|
var item = items[i];
|
|
|
|
if (item._clockwise === undefined)
|
|
|
|
item.setClockwise(item._index === 0);
|
|
|
|
}
|
|
|
|
return items;
|
|
|
|
},
|
2011-03-03 17:45:17 -05:00
|
|
|
|
2014-08-16 13:24:54 -04:00
|
|
|
/**
|
|
|
|
* Reverses the orientation of all nested paths.
|
|
|
|
*/
|
|
|
|
reverse: function() {
|
|
|
|
var children = this._children;
|
|
|
|
for (var i = 0, l = children.length; i < l; i++)
|
|
|
|
children[i].reverse();
|
|
|
|
},
|
2013-05-04 00:41:22 -04:00
|
|
|
|
2014-08-16 13:24:54 -04:00
|
|
|
smooth: function() {
|
|
|
|
for (var i = 0, l = this._children.length; i < l; i++)
|
|
|
|
this._children[i].smooth();
|
|
|
|
},
|
2011-03-03 07:23:46 -05:00
|
|
|
|
2015-09-12 05:43:41 -04:00
|
|
|
// DOCS: reduce()
|
|
|
|
// TEST: reduce()
|
2015-01-02 18:46:45 -05:00
|
|
|
reduce: function reduce() {
|
2015-09-12 05:43:41 -04:00
|
|
|
var children = this._children;
|
|
|
|
for (var i = children.length - 1; i >= 0; i--) {
|
|
|
|
var path = children[i].reduce();
|
|
|
|
if (path.isEmpty())
|
|
|
|
children.splice(i, 1);
|
|
|
|
}
|
|
|
|
if (children.length === 0) { // Replace with a simple empty Path
|
2015-01-02 18:46:45 -05:00
|
|
|
var path = new Path(Item.NO_INSERT);
|
|
|
|
path.insertAbove(this);
|
|
|
|
path.setStyle(this._style);
|
|
|
|
this.remove();
|
|
|
|
return path;
|
|
|
|
}
|
2015-09-12 05:43:41 -04:00
|
|
|
return reduce.base.call(this);
|
2015-01-02 18:46:45 -05:00
|
|
|
},
|
|
|
|
|
2014-08-16 13:24:54 -04:00
|
|
|
/**
|
|
|
|
* Specifies whether the compound path is oriented clock-wise.
|
|
|
|
*
|
|
|
|
* @type Boolean
|
|
|
|
* @bean
|
|
|
|
*/
|
|
|
|
isClockwise: function() {
|
|
|
|
var child = this.getFirstChild();
|
|
|
|
return child && child.isClockwise();
|
|
|
|
},
|
2013-05-04 00:41:22 -04:00
|
|
|
|
2014-08-16 13:24:54 -04:00
|
|
|
setClockwise: function(clockwise) {
|
|
|
|
/*jshint -W018 */
|
|
|
|
if (this.isClockwise() !== !!clockwise)
|
|
|
|
this.reverse();
|
|
|
|
},
|
2013-05-04 00:41:22 -04:00
|
|
|
|
2014-08-16 13:24:54 -04:00
|
|
|
/**
|
|
|
|
* The first Segment contained within the path.
|
|
|
|
*
|
|
|
|
* @type Segment
|
|
|
|
* @bean
|
|
|
|
*/
|
|
|
|
getFirstSegment: function() {
|
|
|
|
var first = this.getFirstChild();
|
|
|
|
return first && first.getFirstSegment();
|
|
|
|
},
|
2013-02-15 21:05:16 -05:00
|
|
|
|
2014-08-16 13:24:54 -04:00
|
|
|
/**
|
|
|
|
* The last Segment contained within the path.
|
|
|
|
*
|
|
|
|
* @type Segment
|
|
|
|
* @bean
|
|
|
|
*/
|
|
|
|
getLastSegment: function() {
|
|
|
|
var last = this.getLastChild();
|
|
|
|
return last && last.getLastSegment();
|
|
|
|
},
|
2013-02-15 21:05:16 -05:00
|
|
|
|
2014-08-16 13:24:54 -04:00
|
|
|
/**
|
|
|
|
* All the curves contained within the compound-path, from all its child
|
|
|
|
* {@link Path} items.
|
|
|
|
*
|
|
|
|
* @type Curve[]
|
|
|
|
* @bean
|
|
|
|
*/
|
|
|
|
getCurves: function() {
|
|
|
|
var children = this._children,
|
|
|
|
curves = [];
|
|
|
|
for (var i = 0, l = children.length; i < l; i++)
|
|
|
|
curves.push.apply(curves, children[i].getCurves());
|
|
|
|
return curves;
|
|
|
|
},
|
2012-12-18 08:53:38 -05:00
|
|
|
|
2014-08-16 13:24:54 -04:00
|
|
|
/**
|
|
|
|
* The first Curve contained within the path.
|
|
|
|
*
|
|
|
|
* @type Curve
|
|
|
|
* @bean
|
|
|
|
*/
|
|
|
|
getFirstCurve: function() {
|
|
|
|
var first = this.getFirstChild();
|
|
|
|
return first && first.getFirstCurve();
|
|
|
|
},
|
2013-02-15 21:05:16 -05:00
|
|
|
|
2014-08-16 13:24:54 -04:00
|
|
|
/**
|
|
|
|
* The last Curve contained within the path.
|
|
|
|
*
|
|
|
|
* @type Curve
|
|
|
|
* @bean
|
|
|
|
*/
|
|
|
|
getLastCurve: function() {
|
|
|
|
var last = this.getLastChild();
|
|
|
|
return last && last.getFirstCurve();
|
|
|
|
},
|
2013-02-15 21:05:16 -05:00
|
|
|
|
2014-08-16 13:24:54 -04:00
|
|
|
/**
|
2015-09-06 10:37:10 -04:00
|
|
|
* The area that the path's geometry is covering. Self-intersecting paths
|
|
|
|
* can contain sub-areas that cancel each other out.
|
2014-08-16 13:24:54 -04:00
|
|
|
*
|
|
|
|
* @type Number
|
|
|
|
* @bean
|
|
|
|
*/
|
|
|
|
getArea: function() {
|
|
|
|
var children = this._children,
|
|
|
|
area = 0;
|
|
|
|
for (var i = 0, l = children.length; i < l; i++)
|
|
|
|
area += children[i].getArea();
|
|
|
|
return area;
|
|
|
|
}
|
2014-04-02 14:53:18 -04:00
|
|
|
}, /** @lends CompoundPath# */{
|
2014-08-16 13:24:54 -04:00
|
|
|
// Enforce bean creation for getPathData(), as it has hidden parameters.
|
|
|
|
beans: true,
|
2013-04-25 20:37:19 -04:00
|
|
|
|
2014-08-16 13:24:54 -04:00
|
|
|
getPathData: function(_matrix, _precision) {
|
|
|
|
// NOTE: #setPathData() is defined in PathItem.
|
|
|
|
var children = this._children,
|
|
|
|
paths = [];
|
|
|
|
for (var i = 0, l = children.length; i < l; i++) {
|
|
|
|
var child = children[i],
|
|
|
|
mx = child._matrix;
|
|
|
|
paths.push(child.getPathData(_matrix && !mx.isIdentity()
|
2015-08-25 22:28:30 -04:00
|
|
|
? _matrix.chain(mx) : _matrix, _precision));
|
2014-08-16 13:24:54 -04:00
|
|
|
}
|
|
|
|
return paths.join(' ');
|
|
|
|
}
|
2014-04-02 14:53:18 -04:00
|
|
|
}, /** @lends CompoundPath# */{
|
2014-08-16 13:24:54 -04:00
|
|
|
_getChildHitTestOptions: function(options) {
|
|
|
|
// If we're not specifically asked to returns paths through
|
|
|
|
// options.class == Path, do not test children for fill, since a
|
|
|
|
// compound path forms one shape.
|
|
|
|
// Also support legacy format `type: 'path'`.
|
|
|
|
return options.class === Path || options.type === 'path'
|
|
|
|
? options
|
|
|
|
: new Base(options, { fill: false });
|
|
|
|
},
|
2012-10-22 18:48:51 -04:00
|
|
|
|
2014-08-16 13:24:54 -04:00
|
|
|
_draw: function(ctx, param, strokeMatrix) {
|
|
|
|
var children = this._children;
|
|
|
|
// Return early if the compound path doesn't have any children:
|
|
|
|
if (children.length === 0)
|
|
|
|
return;
|
2013-11-04 05:46:20 -05:00
|
|
|
|
2014-08-16 13:24:54 -04:00
|
|
|
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;
|
|
|
|
}
|
2013-11-04 05:46:20 -05:00
|
|
|
|
2014-08-16 13:24:54 -04:00
|
|
|
if (!param.clip) {
|
|
|
|
this._setStyles(ctx);
|
|
|
|
var style = this._style;
|
|
|
|
if (style.hasFill()) {
|
|
|
|
ctx.fill(style.getWindingRule());
|
|
|
|
ctx.shadowColor = 'rgba(0,0,0,0)';
|
|
|
|
}
|
|
|
|
if (style.hasStroke())
|
|
|
|
ctx.stroke();
|
|
|
|
}
|
|
|
|
},
|
2014-04-07 05:22:25 -04:00
|
|
|
|
2014-08-16 13:24:54 -04:00
|
|
|
_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));
|
|
|
|
}
|
|
|
|
}
|
2015-09-06 10:48:23 -04:00
|
|
|
},
|
|
|
|
new function() { // Injection scope for PostScript-like drawing functions
|
2014-08-16 13:24:54 -04:00
|
|
|
/**
|
|
|
|
* Helper method that returns the current path and checks if a moveTo()
|
|
|
|
* command is required first.
|
|
|
|
*/
|
|
|
|
function getCurrentPath(that, check) {
|
|
|
|
var children = that._children;
|
|
|
|
if (check && children.length === 0)
|
|
|
|
throw new Error('Use a moveTo() command first');
|
|
|
|
return children[children.length - 1];
|
|
|
|
}
|
2011-02-17 17:58:56 -05:00
|
|
|
|
2014-08-16 13:24:54 -04:00
|
|
|
var fields = {
|
|
|
|
// Note: Documentation for these methods is found in PathItem, as they
|
|
|
|
// are considered abstract methods of PathItem and need to be defined in
|
|
|
|
// all implementing classes.
|
|
|
|
moveTo: function(/* point */) {
|
|
|
|
var current = getCurrentPath(this),
|
|
|
|
// Reuse current path if nothing was added yet
|
|
|
|
path = current && current.isEmpty() ? current : new Path();
|
|
|
|
if (path !== current)
|
|
|
|
this.addChild(path);
|
|
|
|
path.moveTo.apply(path, arguments);
|
|
|
|
},
|
2011-05-07 12:11:06 -04:00
|
|
|
|
2014-08-16 13:24:54 -04:00
|
|
|
moveBy: function(/* point */) {
|
|
|
|
var current = getCurrentPath(this, true),
|
|
|
|
last = current && current.getLastSegment(),
|
|
|
|
point = Point.read(arguments);
|
|
|
|
this.moveTo(last ? point.add(last._point) : point);
|
|
|
|
},
|
2011-02-17 18:01:18 -05:00
|
|
|
|
2014-08-16 13:24:54 -04:00
|
|
|
closePath: function(join) {
|
|
|
|
getCurrentPath(this, true).closePath(join);
|
|
|
|
}
|
|
|
|
};
|
2011-02-17 18:01:18 -05:00
|
|
|
|
2014-08-16 13:24:54 -04:00
|
|
|
// Redirect all other drawing commands to the current path
|
|
|
|
Base.each(['lineTo', 'cubicCurveTo', 'quadraticCurveTo', 'curveTo', 'arcTo',
|
|
|
|
'lineBy', 'cubicCurveBy', 'quadraticCurveBy', 'curveBy', 'arcBy'],
|
|
|
|
function(key) {
|
|
|
|
fields[key] = function() {
|
|
|
|
var path = getCurrentPath(this, true);
|
|
|
|
path[key].apply(path, arguments);
|
|
|
|
};
|
|
|
|
}
|
|
|
|
);
|
2011-02-17 07:36:40 -05:00
|
|
|
|
2014-08-16 13:24:54 -04:00
|
|
|
return fields;
|
2011-03-03 07:19:43 -05:00
|
|
|
});
|