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 Path
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2013-10-16 08:19:25 -04:00
|
|
|
|
* @class The path item represents a path in a Paper.js project.
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-06-22 18:56:05 -04:00
|
|
|
|
* @extends PathItem
|
|
|
|
|
*/
|
2011-12-23 16:43:27 -05:00
|
|
|
|
// DOCS: Explain that path matrix is always applied with each transformation.
|
2013-05-27 15:48:58 -04:00
|
|
|
|
var Path = PathItem.extend(/** @lends Path# */{
|
2013-06-23 23:18:32 -04:00
|
|
|
|
_class: 'Path',
|
2013-01-20 01:01:26 -05:00
|
|
|
|
_serializeFields: {
|
2013-03-19 21:48:59 -04:00
|
|
|
|
segments: [],
|
|
|
|
|
closed: false
|
2013-01-20 01:01:26 -05:00
|
|
|
|
},
|
2012-12-23 18:39:42 -05:00
|
|
|
|
|
2011-05-22 18:26:08 -04:00
|
|
|
|
/**
|
2013-10-16 08:19:25 -04:00
|
|
|
|
* Creates a new path item and places it at the top of the active layer.
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2013-03-10 14:48:46 -04:00
|
|
|
|
* @name Path#initialize
|
2011-06-19 16:52:52 -04:00
|
|
|
|
* @param {Segment[]} [segments] An array of segments (or points to be
|
2013-10-16 08:19:25 -04:00
|
|
|
|
* converted to segments) that will be added to the path
|
|
|
|
|
* @return {Path} the newly created path
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-05-22 18:26:08 -04:00
|
|
|
|
* @example
|
2011-05-30 13:42:17 -04:00
|
|
|
|
* // Create an empty path and add segments to it:
|
|
|
|
|
* var path = new Path();
|
2011-05-22 18:26:08 -04:00
|
|
|
|
* path.strokeColor = 'black';
|
2011-05-30 13:42:17 -04:00
|
|
|
|
* path.add(new Point(30, 30));
|
|
|
|
|
* path.add(new Point(100, 100));
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-05-22 18:26:08 -04:00
|
|
|
|
* @example
|
2011-05-30 13:42:17 -04:00
|
|
|
|
* // Create a path with two segments:
|
|
|
|
|
* var segments = [new Point(30, 30), new Point(100, 100)];
|
|
|
|
|
* var path = new Path(segments);
|
2011-05-22 18:26:08 -04:00
|
|
|
|
* path.strokeColor = 'black';
|
|
|
|
|
*/
|
2013-03-10 14:48:46 -04:00
|
|
|
|
/**
|
2013-12-06 12:09:44 -05:00
|
|
|
|
* Creates a new path item from an object description and places it at the
|
|
|
|
|
* top of the active layer.
|
2013-03-10 14:48:46 -04:00
|
|
|
|
*
|
2013-10-16 08:19:25 -04:00
|
|
|
|
* @name Path#initialize
|
|
|
|
|
* @param {Object} object an object literal containing properties to
|
|
|
|
|
* be set on the path
|
|
|
|
|
* @return {Path} the newly created path
|
2013-03-10 14:48:46 -04:00
|
|
|
|
*
|
|
|
|
|
* @example {@paperscript}
|
|
|
|
|
* var path = new Path({
|
2014-04-02 15:03:35 -04:00
|
|
|
|
* segments: [[20, 20], [80, 80], [140, 20]],
|
|
|
|
|
* fillColor: 'black',
|
|
|
|
|
* closed: true
|
2013-03-10 14:48:46 -04:00
|
|
|
|
* });
|
|
|
|
|
*
|
|
|
|
|
* @example {@paperscript}
|
|
|
|
|
* var path = new Path({
|
2014-04-02 15:03:35 -04:00
|
|
|
|
* segments: [[20, 20], [80, 80], [140, 20]],
|
|
|
|
|
* strokeColor: 'red',
|
|
|
|
|
* strokeWidth: 20,
|
|
|
|
|
* strokeCap: 'round',
|
|
|
|
|
* selected: true
|
2013-03-10 14:48:46 -04:00
|
|
|
|
* });
|
|
|
|
|
*/
|
2013-12-06 12:09:44 -05:00
|
|
|
|
/**
|
|
|
|
|
* Creates a new path item from SVG path-data and places it at the top of
|
|
|
|
|
* the active layer.
|
|
|
|
|
*
|
|
|
|
|
* @name Path#initialize
|
|
|
|
|
* @param {String} pathData the SVG path-data that describes the geometry
|
|
|
|
|
* of this path.
|
|
|
|
|
* @return {Path} the newly created path
|
|
|
|
|
*
|
|
|
|
|
* @example {@paperscript}
|
|
|
|
|
* var pathData = 'M100,50c0,27.614-22.386,50-50,50S0,77.614,0,50S22.386,0,50,0S100,22.386,100,50';
|
|
|
|
|
* var path = new Path(pathData);
|
|
|
|
|
* path.fillColor = 'red';
|
|
|
|
|
*/
|
2013-05-27 15:48:58 -04:00
|
|
|
|
initialize: function Path(arg) {
|
2011-04-30 18:22:29 -04:00
|
|
|
|
this._closed = false;
|
2012-12-25 16:12:25 -05:00
|
|
|
|
this._segments = [];
|
2013-10-16 08:19:25 -04:00
|
|
|
|
// arg can either be an object literal containing properties to be set
|
2012-12-25 16:12:25 -05:00
|
|
|
|
// on the path, a list of segments to be set, or the first of multiple
|
|
|
|
|
// arguments describing separate segments.
|
2011-02-17 15:08:37 -05:00
|
|
|
|
// If it is an array, it can also be a description of a point, so
|
2012-12-25 16:12:25 -05:00
|
|
|
|
// check its first entry for object as well.
|
|
|
|
|
// But first see if segments are directly passed at all. If not, try
|
2013-02-15 21:28:49 -05:00
|
|
|
|
// _set(arg).
|
2012-12-25 16:12:25 -05:00
|
|
|
|
var segments = Array.isArray(arg)
|
|
|
|
|
? typeof arg[0] === 'object'
|
|
|
|
|
? arg
|
|
|
|
|
: arguments
|
2013-07-21 18:45:22 -04:00
|
|
|
|
// See if it behaves like a segment or a point, but filter out
|
|
|
|
|
// rectangles, as accepted by some Path.Constructor constructors.
|
2013-12-06 12:09:44 -05:00
|
|
|
|
: arg && (arg.size === undefined && (arg.x !== undefined
|
|
|
|
|
|| arg.point !== undefined))
|
2012-12-25 16:12:25 -05:00
|
|
|
|
? arguments
|
|
|
|
|
: null;
|
2013-07-21 18:45:22 -04:00
|
|
|
|
// Always call setSegments() to initialize a few related variables.
|
2014-02-26 10:15:51 -05:00
|
|
|
|
if (segments && segments.length > 0) {
|
|
|
|
|
// This sets _curves and _selectedSegmentState too!
|
|
|
|
|
this.setSegments(segments);
|
|
|
|
|
} else {
|
|
|
|
|
this._curves = undefined; // For hidden class optimization
|
|
|
|
|
this._selectedSegmentState = 0;
|
|
|
|
|
if (!segments && typeof arg === 'string') {
|
|
|
|
|
this.setPathData(arg);
|
|
|
|
|
// Erase for _initialize() call below.
|
|
|
|
|
arg = null;
|
|
|
|
|
}
|
2013-12-06 12:09:44 -05:00
|
|
|
|
}
|
2013-07-21 18:45:22 -04:00
|
|
|
|
// Only pass on arg as props if it wasn't consumed for segments already.
|
|
|
|
|
this._initialize(!segments && arg);
|
2011-02-17 15:08:37 -05:00
|
|
|
|
},
|
|
|
|
|
|
2013-10-17 07:08:54 -04:00
|
|
|
|
_equals: function(item) {
|
|
|
|
|
return Base.equals(this._segments, item._segments);
|
|
|
|
|
},
|
|
|
|
|
|
2013-07-19 21:27:00 -04:00
|
|
|
|
clone: function(insert) {
|
2014-02-26 10:15:51 -05:00
|
|
|
|
var copy = new Path(Item.NO_INSERT);
|
|
|
|
|
copy.setSegments(this._segments);
|
2011-05-19 16:34:19 -04:00
|
|
|
|
copy._closed = this._closed;
|
2011-05-19 16:56:23 -04:00
|
|
|
|
if (this._clockwise !== undefined)
|
|
|
|
|
copy._clockwise = this._clockwise;
|
2014-02-26 10:15:51 -05:00
|
|
|
|
return this._clone(copy, insert);
|
2011-05-19 16:34:19 -04:00
|
|
|
|
},
|
|
|
|
|
|
2013-07-21 18:45:22 -04:00
|
|
|
|
_changed: function _changed(flags) {
|
|
|
|
|
_changed.base.call(this, flags);
|
2012-11-05 21:11:44 -05:00
|
|
|
|
if (flags & /*#=*/ ChangeFlag.GEOMETRY) {
|
2014-03-18 06:42:38 -04:00
|
|
|
|
// The _currentPath is already cleared in Item, but clear it on the
|
|
|
|
|
// parent too, for children of CompoundPaths, and Groups (ab)used as
|
|
|
|
|
// clipping paths.
|
2014-03-17 09:35:33 -04:00
|
|
|
|
var parent = this._parent;
|
|
|
|
|
if (parent)
|
|
|
|
|
parent._currentPath = undefined;
|
2011-05-15 12:59:06 -04:00
|
|
|
|
// Clockwise state becomes undefined as soon as geometry changes.
|
2013-12-17 17:27:48 -05:00
|
|
|
|
this._length = this._clockwise = undefined;
|
2014-03-17 09:35:33 -04:00
|
|
|
|
// Only notify all curves if we're not told that only one Segment
|
|
|
|
|
// has changed and took already care of notifications.
|
|
|
|
|
if (this._curves && !(flags & /*#=*/ ChangeFlag.SEGMENTS)) {
|
2013-11-04 05:46:20 -05:00
|
|
|
|
for (var i = 0, l = this._curves.length; i < l; i++)
|
2014-03-17 09:35:33 -04:00
|
|
|
|
this._curves[i]._changed();
|
2012-03-17 13:08:06 -04:00
|
|
|
|
}
|
2013-12-25 14:38:48 -05:00
|
|
|
|
// Clear cached curves used for winding direction and containment
|
|
|
|
|
// calculation.
|
2014-02-20 14:00:46 -05:00
|
|
|
|
// NOTE: This is only needed with __options.booleanOperations
|
2014-02-19 18:24:09 -05:00
|
|
|
|
this._monoCurves = undefined;
|
2012-11-05 21:11:44 -05:00
|
|
|
|
} else if (flags & /*#=*/ ChangeFlag.STROKE) {
|
2011-11-24 09:37:44 -05:00
|
|
|
|
// TODO: We could preserve the purely geometric bounds that are not
|
|
|
|
|
// affected by stroke: _bounds.bounds and _bounds.handleBounds
|
2013-12-17 17:27:48 -05:00
|
|
|
|
this._bounds = undefined;
|
2011-05-07 08:39:17 -04:00
|
|
|
|
}
|
2011-05-01 19:17:21 -04:00
|
|
|
|
},
|
|
|
|
|
|
2013-12-28 14:56:44 -05:00
|
|
|
|
getStyle: function() {
|
2014-03-18 06:42:38 -04:00
|
|
|
|
// If this path is part of a compound-path, return the parent's style.
|
2013-12-28 14:56:44 -05:00
|
|
|
|
var parent = this._parent;
|
|
|
|
|
return (parent instanceof CompoundPath ? parent : this)._style;
|
|
|
|
|
},
|
|
|
|
|
|
2011-04-30 18:24:39 -04:00
|
|
|
|
/**
|
|
|
|
|
* The segments contained within the path.
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-05-26 10:50:13 -04:00
|
|
|
|
* @type Segment[]
|
2011-05-22 18:26:08 -04:00
|
|
|
|
* @bean
|
2011-04-30 18:24:39 -04:00
|
|
|
|
*/
|
|
|
|
|
getSegments: function() {
|
|
|
|
|
return this._segments;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
setSegments: function(segments) {
|
2013-10-17 06:03:46 -04:00
|
|
|
|
var fullySelected = this.isFullySelected();
|
2012-12-25 16:12:25 -05:00
|
|
|
|
this._segments.length = 0;
|
2013-10-17 06:03:46 -04:00
|
|
|
|
this._selectedSegmentState = 0;
|
2013-01-22 17:46:49 -05:00
|
|
|
|
// Calculate new curves next time we call getCurves()
|
2013-12-17 17:27:48 -05:00
|
|
|
|
this._curves = undefined;
|
2014-02-26 10:15:51 -05:00
|
|
|
|
if (segments && segments.length > 0)
|
|
|
|
|
this._add(Segment.readAll(segments));
|
|
|
|
|
// Preserve fullySelected state.
|
|
|
|
|
// TODO: Do we still need this?
|
2013-10-17 06:03:46 -04:00
|
|
|
|
if (fullySelected)
|
|
|
|
|
this.setFullySelected(true);
|
2011-04-30 18:24:39 -04:00
|
|
|
|
},
|
|
|
|
|
|
2011-05-22 18:26:08 -04:00
|
|
|
|
/**
|
|
|
|
|
* The first Segment contained within the path.
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-05-22 18:26:08 -04:00
|
|
|
|
* @type Segment
|
|
|
|
|
* @bean
|
|
|
|
|
*/
|
2011-05-01 08:27:53 -04:00
|
|
|
|
getFirstSegment: function() {
|
|
|
|
|
return this._segments[0];
|
|
|
|
|
},
|
|
|
|
|
|
2011-05-22 18:26:08 -04:00
|
|
|
|
/**
|
|
|
|
|
* The last Segment contained within the path.
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-05-22 18:26:08 -04:00
|
|
|
|
* @type Segment
|
|
|
|
|
* @bean
|
|
|
|
|
*/
|
2011-05-01 08:27:53 -04:00
|
|
|
|
getLastSegment: function() {
|
|
|
|
|
return this._segments[this._segments.length - 1];
|
|
|
|
|
},
|
|
|
|
|
|
2011-03-06 07:24:15 -05:00
|
|
|
|
/**
|
|
|
|
|
* The curves contained within the path.
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-05-26 10:50:13 -04:00
|
|
|
|
* @type Curve[]
|
2011-05-22 18:26:08 -04:00
|
|
|
|
* @bean
|
2011-03-06 07:24:15 -05:00
|
|
|
|
*/
|
2013-03-02 00:47:13 -05:00
|
|
|
|
getCurves: function() {
|
2012-12-15 00:39:47 -05:00
|
|
|
|
var curves = this._curves,
|
|
|
|
|
segments = this._segments;
|
|
|
|
|
if (!curves) {
|
2013-01-22 17:58:34 -05:00
|
|
|
|
var length = this._countCurves();
|
2013-01-22 17:46:49 -05:00
|
|
|
|
curves = this._curves = new Array(length);
|
2011-04-30 18:44:37 -04:00
|
|
|
|
for (var i = 0; i < length; i++)
|
2013-06-25 12:54:13 -04:00
|
|
|
|
curves[i] = new Curve(this, segments[i],
|
2011-05-02 19:25:23 -04:00
|
|
|
|
// Use first segment for segment2 of closing curve
|
|
|
|
|
segments[i + 1] || segments[0]);
|
2011-03-06 07:24:15 -05:00
|
|
|
|
}
|
2012-12-15 00:39:47 -05:00
|
|
|
|
return curves;
|
2011-03-06 07:24:15 -05:00
|
|
|
|
},
|
|
|
|
|
|
2011-05-22 18:26:08 -04:00
|
|
|
|
/**
|
|
|
|
|
* The first Curve contained within the path.
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-05-22 18:26:08 -04:00
|
|
|
|
* @type Curve
|
|
|
|
|
* @bean
|
|
|
|
|
*/
|
2011-05-01 08:27:53 -04:00
|
|
|
|
getFirstCurve: function() {
|
|
|
|
|
return this.getCurves()[0];
|
|
|
|
|
},
|
|
|
|
|
|
2011-05-22 18:26:08 -04:00
|
|
|
|
/**
|
|
|
|
|
* The last Curve contained within the path.
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-05-22 18:26:08 -04:00
|
|
|
|
* @type Curve
|
|
|
|
|
* @bean
|
|
|
|
|
*/
|
2011-05-01 08:27:53 -04:00
|
|
|
|
getLastCurve: function() {
|
|
|
|
|
var curves = this.getCurves();
|
|
|
|
|
return curves[curves.length - 1];
|
|
|
|
|
},
|
|
|
|
|
|
2011-05-22 18:26:08 -04:00
|
|
|
|
/**
|
2011-06-05 15:28:18 -04:00
|
|
|
|
* Specifies whether the path is closed. If it is closed, Paper.js connects
|
|
|
|
|
* the first and last segments.
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-05-22 18:26:08 -04:00
|
|
|
|
* @type Boolean
|
|
|
|
|
* @bean
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-06-04 09:32:28 -04:00
|
|
|
|
* @example {@paperscript}
|
2011-06-03 17:04:18 -04:00
|
|
|
|
* var myPath = new Path();
|
|
|
|
|
* myPath.strokeColor = 'black';
|
2011-06-05 15:28:18 -04:00
|
|
|
|
* myPath.add(new Point(50, 75));
|
|
|
|
|
* myPath.add(new Point(100, 25));
|
|
|
|
|
* myPath.add(new Point(150, 75));
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-06-03 17:04:18 -04:00
|
|
|
|
* // Close the path:
|
|
|
|
|
* myPath.closed = true;
|
2011-05-22 18:26:08 -04:00
|
|
|
|
*/
|
2012-11-06 00:06:47 -05:00
|
|
|
|
isClosed: function() {
|
2011-04-30 18:22:29 -04:00
|
|
|
|
return this._closed;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
setClosed: function(closed) {
|
2011-04-30 18:44:37 -04:00
|
|
|
|
// On-the-fly conversion to boolean:
|
|
|
|
|
if (this._closed != (closed = !!closed)) {
|
|
|
|
|
this._closed = closed;
|
|
|
|
|
// Update _curves length
|
|
|
|
|
if (this._curves) {
|
2013-01-22 17:58:34 -05:00
|
|
|
|
var length = this._curves.length = this._countCurves();
|
2011-04-30 18:44:37 -04:00
|
|
|
|
// If we were closing this path, we need to add a new curve now
|
|
|
|
|
if (closed)
|
2013-06-25 12:54:13 -04:00
|
|
|
|
this._curves[length - 1] = new Curve(this,
|
2012-12-31 15:43:43 -05:00
|
|
|
|
this._segments[length - 1], this._segments[0]);
|
2011-04-30 18:44:37 -04:00
|
|
|
|
}
|
2014-03-18 06:42:38 -04:00
|
|
|
|
// Use SEGMENTS notification instead of GEOMETRY since curves are
|
|
|
|
|
// up-to-date and don't need notification.
|
2014-03-17 09:35:33 -04:00
|
|
|
|
this._changed(/*#=*/ Change.SEGMENTS);
|
2011-04-30 18:44:37 -04:00
|
|
|
|
}
|
2014-04-02 14:53:18 -04:00
|
|
|
|
}
|
|
|
|
|
}, /** @lends Path# */{
|
|
|
|
|
// Enforce bean creation for getPathData(), as it has hidden parameters.
|
|
|
|
|
beans: true,
|
|
|
|
|
|
|
|
|
|
getPathData: function(_precision) {
|
|
|
|
|
// NOTE: #setPathData() is defined in PathItem.
|
|
|
|
|
var segments = this._segments,
|
|
|
|
|
f = Formatter.instance,
|
|
|
|
|
parts = [];
|
|
|
|
|
|
|
|
|
|
// TODO: Add support for H/V and/or relative commands, where appropriate
|
|
|
|
|
// and resulting in shorter strings
|
|
|
|
|
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) {
|
|
|
|
|
// L = absolute lineto: moving to a point with drawing
|
|
|
|
|
parts.push('L' + f.point(point2, _precision));
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// c = relative curveto: handle1, handle2 + end - start,
|
|
|
|
|
// end - start
|
|
|
|
|
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('');
|
|
|
|
|
}
|
|
|
|
|
}, /** @lends Path# */{
|
2011-04-30 18:22:29 -04:00
|
|
|
|
|
2011-02-22 04:25:18 -05:00
|
|
|
|
// TODO: Consider adding getSubPath(a, b), returning a part of the current
|
|
|
|
|
// path, with the added benefit that b can be < a, and closed looping is
|
|
|
|
|
// taken into account.
|
|
|
|
|
|
2012-10-10 23:11:11 -04:00
|
|
|
|
isEmpty: function() {
|
2012-11-04 02:03:49 -05:00
|
|
|
|
return this._segments.length === 0;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
isPolygon: function() {
|
|
|
|
|
for (var i = 0, l = this._segments.length; i < l; i++) {
|
|
|
|
|
if (!this._segments[i].isLinear())
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
2012-10-10 23:11:11 -04:00
|
|
|
|
},
|
|
|
|
|
|
2014-03-02 10:04:17 -05:00
|
|
|
|
_transformContent: function(matrix) {
|
2011-12-18 10:54:21 -05:00
|
|
|
|
var coords = new Array(6);
|
2013-04-19 16:13:54 -04:00
|
|
|
|
for (var i = 0, l = this._segments.length; i < l; i++)
|
2011-12-18 10:54:21 -05:00
|
|
|
|
this._segments[i]._transformCoordinates(matrix, coords, true);
|
|
|
|
|
return true;
|
2011-02-17 15:08:37 -05:00
|
|
|
|
},
|
|
|
|
|
|
2011-03-03 07:51:47 -05:00
|
|
|
|
/**
|
|
|
|
|
* Private method that adds a segment to the segment list. It assumes that
|
|
|
|
|
* the passed object is a segment already and does not perform any checks.
|
2011-05-01 08:27:53 -04:00
|
|
|
|
* If a curves list was requested, it will kept in sync with the segments
|
|
|
|
|
* list automatically.
|
2011-03-03 07:51:47 -05:00
|
|
|
|
*/
|
2011-05-05 19:18:56 -04:00
|
|
|
|
_add: function(segs, index) {
|
2011-05-02 19:25:23 -04:00
|
|
|
|
// Local short-cuts:
|
|
|
|
|
var segments = this._segments,
|
2011-05-04 13:42:40 -04:00
|
|
|
|
curves = this._curves,
|
|
|
|
|
amount = segs.length,
|
2011-05-07 11:11:05 -04:00
|
|
|
|
append = index == null,
|
2013-10-17 06:03:46 -04:00
|
|
|
|
index = append ? segments.length : index;
|
2011-05-07 10:38:36 -04:00
|
|
|
|
// Scan through segments to add first, convert if necessary and set
|
|
|
|
|
// _path and _index references on them.
|
2011-05-04 13:42:40 -04:00
|
|
|
|
for (var i = 0; i < amount; i++) {
|
|
|
|
|
var segment = segs[i];
|
|
|
|
|
// If the segments belong to another path already, clone them before
|
|
|
|
|
// adding:
|
2012-12-31 15:43:43 -05:00
|
|
|
|
if (segment._path)
|
|
|
|
|
segment = segs[i] = segment.clone();
|
2011-05-04 13:42:40 -04:00
|
|
|
|
segment._path = this;
|
|
|
|
|
segment._index = index + i;
|
2011-05-26 05:59:22 -04:00
|
|
|
|
// If parts of this segment are selected, adjust the internal
|
2011-06-14 10:37:25 -04:00
|
|
|
|
// _selectedSegmentState now
|
2011-05-26 05:59:22 -04:00
|
|
|
|
if (segment._selectionState)
|
2011-06-14 10:37:25 -04:00
|
|
|
|
this._updateSelection(segment, 0, segment._selectionState);
|
2011-05-04 13:42:40 -04:00
|
|
|
|
}
|
|
|
|
|
if (append) {
|
|
|
|
|
// Append them all at the end by using push
|
|
|
|
|
segments.push.apply(segments, segs);
|
2011-03-03 08:10:17 -05:00
|
|
|
|
} else {
|
2011-05-01 08:27:53 -04:00
|
|
|
|
// Insert somewhere else
|
2011-05-04 13:42:40 -04:00
|
|
|
|
segments.splice.apply(segments, [index, 0].concat(segs));
|
2011-05-01 08:27:53 -04:00
|
|
|
|
// Adjust the indices of the segments above.
|
2013-01-22 17:46:49 -05:00
|
|
|
|
for (var i = index + amount, l = segments.length; i < l; i++)
|
2011-05-02 19:25:23 -04:00
|
|
|
|
segments[i]._index = i;
|
2011-05-01 08:27:53 -04:00
|
|
|
|
}
|
|
|
|
|
// Keep the curves list in sync all the time in case it as requested
|
2012-12-31 16:21:50 -05:00
|
|
|
|
// already.
|
2013-01-22 17:46:49 -05:00
|
|
|
|
if (curves || segs._curves) {
|
|
|
|
|
if (!curves)
|
2013-01-22 17:58:34 -05:00
|
|
|
|
curves = this._curves = [];
|
2012-12-31 16:21:50 -05:00
|
|
|
|
// We need to step one index down from the inserted segment to
|
|
|
|
|
// get its curve, except for the first segment.
|
2013-01-22 17:46:49 -05:00
|
|
|
|
var from = index > 0 ? index - 1 : index,
|
|
|
|
|
start = from,
|
2013-01-22 17:58:34 -05:00
|
|
|
|
to = Math.min(from + amount, this._countCurves());
|
2013-01-22 17:46:49 -05:00
|
|
|
|
if (segs._curves) {
|
|
|
|
|
// Reuse removed curves.
|
|
|
|
|
curves.splice.apply(curves, [from, 0].concat(segs._curves));
|
|
|
|
|
start += segs._curves.length;
|
|
|
|
|
}
|
2013-06-25 12:58:18 -04:00
|
|
|
|
// Insert new curves, but do not initialize their segments yet,
|
|
|
|
|
// since #_adjustCurves() handles all that for us.
|
2013-01-22 17:46:49 -05:00
|
|
|
|
for (var i = start; i < to; i++)
|
2013-06-25 12:58:18 -04:00
|
|
|
|
curves.splice(i, 0, new Curve(this, null, null));
|
2012-12-31 16:21:50 -05:00
|
|
|
|
// Adjust segments for the curves before and after the removed ones
|
2013-01-22 17:46:49 -05:00
|
|
|
|
this._adjustCurves(from, to);
|
2011-03-03 08:10:17 -05:00
|
|
|
|
}
|
2014-03-18 06:42:38 -04:00
|
|
|
|
// Use SEGMENTS notification instead of GEOMETRY since curves are kept
|
|
|
|
|
// up-to-date by _adjustCurves() and don't need notification.
|
2014-03-17 09:35:33 -04:00
|
|
|
|
this._changed(/*#=*/ Change.SEGMENTS);
|
2011-05-04 13:42:40 -04:00
|
|
|
|
return segs;
|
2011-02-17 15:08:37 -05:00
|
|
|
|
},
|
|
|
|
|
|
2013-01-22 17:58:34 -05:00
|
|
|
|
/**
|
|
|
|
|
* Adjusts segments of curves before and after inserted / removed segments.
|
|
|
|
|
*/
|
|
|
|
|
_adjustCurves: function(from, to) {
|
|
|
|
|
var segments = this._segments,
|
|
|
|
|
curves = this._curves,
|
|
|
|
|
curve;
|
|
|
|
|
for (var i = from; i < to; i++) {
|
|
|
|
|
curve = curves[i];
|
|
|
|
|
curve._path = this;
|
|
|
|
|
curve._segment1 = segments[i];
|
|
|
|
|
curve._segment2 = segments[i + 1] || segments[0];
|
|
|
|
|
}
|
|
|
|
|
// If it's the first segment, correct the last segment of closed
|
|
|
|
|
// paths too:
|
|
|
|
|
if (curve = curves[this._closed && from === 0 ? segments.length - 1
|
|
|
|
|
: from - 1])
|
|
|
|
|
curve._segment2 = segments[from] || segments[0];
|
|
|
|
|
// Fix the segment after the modified range, if it exists
|
|
|
|
|
if (curve = curves[to])
|
|
|
|
|
curve._segment1 = segments[to];
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns the amount of curves this path item is supposed to have, based
|
|
|
|
|
* on its amount of #segments and #closed state.
|
|
|
|
|
*/
|
|
|
|
|
_countCurves: function() {
|
|
|
|
|
var length = this._segments.length;
|
|
|
|
|
// Reduce length by one if it's an open path:
|
|
|
|
|
return !this._closed && length > 0 ? length - 1 : length;
|
|
|
|
|
},
|
|
|
|
|
|
2011-05-29 10:05:47 -04:00
|
|
|
|
// DOCS: find a way to document the variable segment parameters of Path#add
|
2011-05-22 18:26:08 -04:00
|
|
|
|
/**
|
2011-06-05 15:28:18 -04:00
|
|
|
|
* Adds one or more segments to the end of the {@link #segments} array of
|
|
|
|
|
* this path.
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-05-22 18:26:08 -04:00
|
|
|
|
* @param {Segment|Point} segment the segment or point to be added.
|
|
|
|
|
* @return {Segment} the added segment. This is not necessarily the same
|
|
|
|
|
* object, e.g. if the segment to be added already belongs to another path.
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-06-05 15:28:18 -04:00
|
|
|
|
* @example {@paperscript}
|
|
|
|
|
* // Adding segments to a path using point objects:
|
2013-03-03 14:48:03 -05:00
|
|
|
|
* var path = new Path({
|
2014-04-02 15:03:35 -04:00
|
|
|
|
* strokeColor: 'black'
|
2013-03-03 14:48:03 -05:00
|
|
|
|
* });
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-06-05 15:28:18 -04:00
|
|
|
|
* // Add a segment at {x: 30, y: 75}
|
|
|
|
|
* path.add(new Point(30, 75));
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-06-05 15:28:18 -04:00
|
|
|
|
* // Add two segments in one go at {x: 100, y: 20}
|
|
|
|
|
* // and {x: 170, y: 75}:
|
|
|
|
|
* path.add(new Point(100, 20), new Point(170, 75));
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-06-05 15:28:18 -04:00
|
|
|
|
* @example {@paperscript}
|
|
|
|
|
* // Adding segments to a path using arrays containing number pairs:
|
2013-03-03 14:48:03 -05:00
|
|
|
|
* var path = new Path({
|
2014-04-02 15:03:35 -04:00
|
|
|
|
* strokeColor: 'black'
|
2013-03-03 14:48:03 -05:00
|
|
|
|
* });
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-06-05 15:28:18 -04:00
|
|
|
|
* // Add a segment at {x: 30, y: 75}
|
|
|
|
|
* path.add([30, 75]);
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-06-05 15:28:18 -04:00
|
|
|
|
* // Add two segments in one go at {x: 100, y: 20}
|
|
|
|
|
* // and {x: 170, y: 75}:
|
|
|
|
|
* path.add([100, 20], [170, 75]);
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-06-05 15:28:18 -04:00
|
|
|
|
* @example {@paperscript}
|
|
|
|
|
* // Adding segments to a path using objects:
|
2013-03-03 14:48:03 -05:00
|
|
|
|
* var path = new Path({
|
2014-04-02 15:03:35 -04:00
|
|
|
|
* strokeColor: 'black'
|
2013-03-03 14:48:03 -05:00
|
|
|
|
* });
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-06-05 15:28:18 -04:00
|
|
|
|
* // Add a segment at {x: 30, y: 75}
|
|
|
|
|
* path.add({x: 30, y: 75});
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-06-05 15:28:18 -04:00
|
|
|
|
* // Add two segments in one go at {x: 100, y: 20}
|
|
|
|
|
* // and {x: 170, y: 75}:
|
|
|
|
|
* path.add({x: 100, y: 20}, {x: 170, y: 75});
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-06-05 16:44:01 -04:00
|
|
|
|
* @example {@paperscript}
|
|
|
|
|
* // Adding a segment with handles to a path:
|
2013-03-03 14:48:03 -05:00
|
|
|
|
* var path = new Path({
|
2014-04-02 15:03:35 -04:00
|
|
|
|
* strokeColor: 'black'
|
2013-03-03 14:48:03 -05:00
|
|
|
|
* });
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-06-05 16:44:01 -04:00
|
|
|
|
* path.add(new Point(30, 75));
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-06-05 16:44:01 -04:00
|
|
|
|
* // Add a segment with handles:
|
|
|
|
|
* var point = new Point(100, 20);
|
|
|
|
|
* var handleIn = new Point(-50, 0);
|
|
|
|
|
* var handleOut = new Point(50, 0);
|
|
|
|
|
* var added = path.add(new Segment(point, handleIn, handleOut));
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-06-05 16:44:01 -04:00
|
|
|
|
* // Select the added segment, so we can see its handles:
|
|
|
|
|
* added.selected = true;
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-06-05 16:44:01 -04:00
|
|
|
|
* path.add(new Point(170, 75));
|
2011-05-22 18:26:08 -04:00
|
|
|
|
*/
|
2011-05-04 13:42:40 -04:00
|
|
|
|
add: function(segment1 /*, segment2, ... */) {
|
2011-05-07 09:32:27 -04:00
|
|
|
|
return arguments.length > 1 && typeof segment1 !== 'number'
|
2011-05-05 07:35:14 -04:00
|
|
|
|
// addSegments
|
2011-05-05 19:18:56 -04:00
|
|
|
|
? this._add(Segment.readAll(arguments))
|
2011-05-05 07:35:14 -04:00
|
|
|
|
// addSegment
|
2011-05-05 19:18:56 -04:00
|
|
|
|
: this._add([ Segment.read(arguments) ])[0];
|
2011-02-17 15:08:37 -05:00
|
|
|
|
},
|
|
|
|
|
|
2011-05-22 18:26:08 -04:00
|
|
|
|
/**
|
|
|
|
|
* Inserts one or more segments at a given index in the list of this path's
|
|
|
|
|
* segments.
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-05-27 15:21:49 -04:00
|
|
|
|
* @param {Number} index the index at which to insert the segment.
|
2011-05-22 18:26:08 -04:00
|
|
|
|
* @param {Segment|Point} segment the segment or point to be inserted.
|
|
|
|
|
* @return {Segment} the added segment. This is not necessarily the same
|
|
|
|
|
* object, e.g. if the segment to be added already belongs to another path.
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-06-05 15:28:18 -04:00
|
|
|
|
* @example {@paperscript}
|
|
|
|
|
* // Inserting a segment:
|
|
|
|
|
* var myPath = new Path();
|
|
|
|
|
* myPath.strokeColor = 'black';
|
|
|
|
|
* myPath.add(new Point(50, 75));
|
|
|
|
|
* myPath.add(new Point(150, 75));
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-06-05 15:28:18 -04:00
|
|
|
|
* // Insert a new segment into myPath at index 1:
|
|
|
|
|
* myPath.insert(1, new Point(100, 25));
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-06-05 15:28:18 -04:00
|
|
|
|
* // Select the segment which we just inserted:
|
|
|
|
|
* myPath.segments[1].selected = true;
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-06-05 15:28:18 -04:00
|
|
|
|
* @example {@paperscript}
|
|
|
|
|
* // Inserting multiple segments:
|
|
|
|
|
* var myPath = new Path();
|
|
|
|
|
* myPath.strokeColor = 'black';
|
|
|
|
|
* myPath.add(new Point(50, 75));
|
|
|
|
|
* myPath.add(new Point(150, 75));
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-06-05 15:28:18 -04:00
|
|
|
|
* // Insert two segments into myPath at index 1:
|
|
|
|
|
* myPath.insert(1, [80, 25], [120, 25]);
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-06-05 15:28:18 -04:00
|
|
|
|
* // Select the segments which we just inserted:
|
|
|
|
|
* myPath.segments[1].selected = true;
|
|
|
|
|
* myPath.segments[2].selected = true;
|
2011-05-22 18:26:08 -04:00
|
|
|
|
*/
|
2011-05-04 13:42:40 -04:00
|
|
|
|
insert: function(index, segment1 /*, segment2, ... */) {
|
2011-05-07 09:32:27 -04:00
|
|
|
|
return arguments.length > 2 && typeof segment1 !== 'number'
|
2011-05-05 07:35:14 -04:00
|
|
|
|
// insertSegments
|
2011-05-05 19:18:56 -04:00
|
|
|
|
? this._add(Segment.readAll(arguments, 1), index)
|
2011-05-05 07:35:14 -04:00
|
|
|
|
// insertSegment
|
2011-05-05 19:18:56 -04:00
|
|
|
|
: this._add([ Segment.read(arguments, 1) ], index)[0];
|
2011-05-05 07:35:14 -04:00
|
|
|
|
},
|
|
|
|
|
|
2013-06-24 13:15:54 -04:00
|
|
|
|
addSegment: function(/* segment */) {
|
2011-05-05 19:18:56 -04:00
|
|
|
|
return this._add([ Segment.read(arguments) ])[0];
|
2011-05-05 07:35:14 -04:00
|
|
|
|
},
|
|
|
|
|
|
2013-06-24 13:15:54 -04:00
|
|
|
|
insertSegment: function(index /*, segment */) {
|
2011-05-05 19:18:56 -04:00
|
|
|
|
return this._add([ Segment.read(arguments, 1) ], index)[0];
|
2011-05-05 07:35:14 -04:00
|
|
|
|
},
|
|
|
|
|
|
2011-05-29 10:06:23 -04:00
|
|
|
|
/**
|
|
|
|
|
* Adds an array of segments (or types that can be converted to segments)
|
|
|
|
|
* to the end of the {@link #segments} array.
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-05-30 13:42:17 -04:00
|
|
|
|
* @param {Segment[]} segments
|
|
|
|
|
* @return {Segment[]} an array of the added segments. These segments are
|
|
|
|
|
* not necessarily the same objects, e.g. if the segment to be added already
|
|
|
|
|
* belongs to another path.
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-06-05 15:28:18 -04:00
|
|
|
|
* @example {@paperscript}
|
|
|
|
|
* // Adding an array of Point objects:
|
2013-03-03 14:48:03 -05:00
|
|
|
|
* var path = new Path({
|
2014-04-02 15:03:35 -04:00
|
|
|
|
* strokeColor: 'black'
|
2013-03-03 14:48:03 -05:00
|
|
|
|
* });
|
2011-06-05 15:28:18 -04:00
|
|
|
|
* var points = [new Point(30, 50), new Point(170, 50)];
|
2011-05-29 10:06:23 -04:00
|
|
|
|
* path.addSegments(points);
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-06-05 15:28:18 -04:00
|
|
|
|
* @example {@paperscript}
|
|
|
|
|
* // Adding an array of [x, y] arrays:
|
2013-03-03 14:48:03 -05:00
|
|
|
|
* var path = new Path({
|
2014-04-02 15:03:35 -04:00
|
|
|
|
* strokeColor: 'black'
|
2013-03-03 14:48:03 -05:00
|
|
|
|
* });
|
2011-06-05 15:28:18 -04:00
|
|
|
|
* var array = [[30, 75], [100, 20], [170, 75]];
|
|
|
|
|
* path.addSegments(array);
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-06-05 15:28:18 -04:00
|
|
|
|
* @example {@paperscript}
|
|
|
|
|
* // Adding segments from one path to another:
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2013-03-03 14:48:03 -05:00
|
|
|
|
* var path = new Path({
|
2014-04-02 15:03:35 -04:00
|
|
|
|
* strokeColor: 'black'
|
2013-03-03 14:48:03 -05:00
|
|
|
|
* });
|
2011-06-05 15:28:18 -04:00
|
|
|
|
* path.addSegments([[30, 75], [100, 20], [170, 75]]);
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-06-05 15:28:18 -04:00
|
|
|
|
* var path2 = new Path();
|
|
|
|
|
* path2.strokeColor = 'red';
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-06-05 15:28:18 -04:00
|
|
|
|
* // Add the second and third segments of path to path2:
|
|
|
|
|
* path2.add(path.segments[1], path.segments[2]);
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-06-05 15:28:18 -04:00
|
|
|
|
* // Move path2 30pt to the right:
|
|
|
|
|
* path2.position.x += 30;
|
2011-05-29 10:06:23 -04:00
|
|
|
|
*/
|
2011-05-05 07:35:14 -04:00
|
|
|
|
addSegments: function(segments) {
|
2011-05-05 19:18:56 -04:00
|
|
|
|
return this._add(Segment.readAll(segments));
|
2011-05-05 07:35:14 -04:00
|
|
|
|
},
|
|
|
|
|
|
2011-05-29 10:06:23 -04:00
|
|
|
|
/**
|
|
|
|
|
* Inserts an array of segments at a given index in the path's
|
|
|
|
|
* {@link #segments} array.
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-05-29 10:06:23 -04:00
|
|
|
|
* @param {Number} index the index at which to insert the segments.
|
|
|
|
|
* @param {Segment[]} segments the segments to be inserted.
|
|
|
|
|
* @return {Segment[]} an array of the added segments. These segments are
|
|
|
|
|
* not necessarily the same objects, e.g. if the segment to be added already
|
|
|
|
|
* belongs to another path.
|
|
|
|
|
*/
|
2011-05-05 07:35:14 -04:00
|
|
|
|
insertSegments: function(index, segments) {
|
2011-05-05 19:18:56 -04:00
|
|
|
|
return this._add(Segment.readAll(segments), index);
|
2011-02-17 15:08:37 -05:00
|
|
|
|
},
|
2011-04-28 10:42:16 -04:00
|
|
|
|
|
2011-05-22 18:26:08 -04:00
|
|
|
|
/**
|
|
|
|
|
* Removes the segment at the specified index of the path's
|
|
|
|
|
* {@link #segments} array.
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-05-27 15:21:49 -04:00
|
|
|
|
* @param {Number} index the index of the segment to be removed
|
2011-05-22 18:26:08 -04:00
|
|
|
|
* @return {Segment} the removed segment
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-06-05 15:28:18 -04:00
|
|
|
|
* @example {@paperscript}
|
|
|
|
|
* // Removing a segment from a path:
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-06-05 15:28:18 -04:00
|
|
|
|
* // Create a circle shaped path at { x: 80, y: 50 }
|
|
|
|
|
* // with a radius of 35:
|
2013-03-03 14:48:03 -05:00
|
|
|
|
* var path = new Path.Circle({
|
2014-04-02 15:03:35 -04:00
|
|
|
|
* center: new Point(80, 50),
|
|
|
|
|
* radius: 35,
|
|
|
|
|
* strokeColor: 'black'
|
2013-03-03 14:48:03 -05:00
|
|
|
|
* });
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-06-05 15:28:18 -04:00
|
|
|
|
* // Remove its second segment:
|
|
|
|
|
* path.removeSegment(1);
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-06-05 15:28:18 -04:00
|
|
|
|
* // Select the path, so we can see its segments:
|
|
|
|
|
* path.selected = true;
|
2011-05-22 18:26:08 -04:00
|
|
|
|
*/
|
2011-04-28 10:42:16 -04:00
|
|
|
|
removeSegment: function(index) {
|
2012-12-31 15:45:30 -05:00
|
|
|
|
return this.removeSegments(index, index + 1)[0] || null;
|
2011-04-28 10:42:16 -04:00
|
|
|
|
},
|
2011-06-13 14:05:17 -04:00
|
|
|
|
|
2011-06-17 12:54:37 -04:00
|
|
|
|
/**
|
|
|
|
|
* Removes all segments from the path's {@link #segments} array.
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-06-17 12:54:37 -04:00
|
|
|
|
* @name Path#removeSegments
|
2013-11-01 12:52:27 -04:00
|
|
|
|
* @alias Path#clear
|
2011-06-17 12:54:37 -04:00
|
|
|
|
* @function
|
2011-06-20 09:27:54 -04:00
|
|
|
|
* @return {Segment[]} an array containing the removed segments
|
2011-06-17 12:54:37 -04:00
|
|
|
|
*/
|
2011-05-22 18:26:08 -04:00
|
|
|
|
/**
|
2011-06-17 12:46:42 -04:00
|
|
|
|
* Removes the segments from the specified {@code from} index to the
|
2011-06-17 12:54:37 -04:00
|
|
|
|
* {@code to} index from the path's {@link #segments} array.
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-06-17 12:54:37 -04:00
|
|
|
|
* @param {Number} from the beginning index, inclusive
|
2011-06-17 12:46:42 -04:00
|
|
|
|
* @param {Number} [to=segments.length] the ending index, exclusive
|
2011-06-20 09:19:08 -04:00
|
|
|
|
* @return {Segment[]} an array containing the removed segments
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-06-05 15:28:18 -04:00
|
|
|
|
* @example {@paperscript}
|
|
|
|
|
* // Removing segments from a path:
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-06-05 15:28:18 -04:00
|
|
|
|
* // Create a circle shaped path at { x: 80, y: 50 }
|
|
|
|
|
* // with a radius of 35:
|
2013-03-03 14:48:03 -05:00
|
|
|
|
* var path = new Path.Circle({
|
2014-04-02 15:03:35 -04:00
|
|
|
|
* center: new Point(80, 50),
|
|
|
|
|
* radius: 35,
|
|
|
|
|
* strokeColor: 'black'
|
2013-03-03 14:48:03 -05:00
|
|
|
|
* });
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-06-05 15:28:18 -04:00
|
|
|
|
* // Remove the segments from index 1 till index 2:
|
|
|
|
|
* path.removeSegments(1, 2);
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-06-05 15:28:18 -04:00
|
|
|
|
* // Select the path, so we can see its segments:
|
|
|
|
|
* path.selected = true;
|
2011-05-22 18:26:08 -04:00
|
|
|
|
*/
|
2014-01-05 11:40:54 -05:00
|
|
|
|
removeSegments: function(from, to, _includeCurves) {
|
2011-05-01 08:27:53 -04:00
|
|
|
|
from = from || 0;
|
2012-10-22 18:21:33 -04:00
|
|
|
|
to = Base.pick(to, this._segments.length);
|
2011-05-02 19:25:23 -04:00
|
|
|
|
var segments = this._segments,
|
|
|
|
|
curves = this._curves,
|
2013-01-22 17:46:49 -05:00
|
|
|
|
count = segments.length, // segment count before removal
|
2011-05-02 19:25:23 -04:00
|
|
|
|
removed = segments.splice(from, to - from),
|
|
|
|
|
amount = removed.length;
|
|
|
|
|
if (!amount)
|
|
|
|
|
return removed;
|
|
|
|
|
// Update selection state accordingly
|
|
|
|
|
for (var i = 0; i < amount; i++) {
|
|
|
|
|
var segment = removed[i];
|
2011-06-14 10:37:25 -04:00
|
|
|
|
if (segment._selectionState)
|
|
|
|
|
this._updateSelection(segment, segment._selectionState, 0);
|
2011-05-15 20:37:31 -04:00
|
|
|
|
// Clear the indices and path references of the removed segments
|
2013-12-17 17:27:48 -05:00
|
|
|
|
segment._index = segment._path = null;
|
2011-05-01 08:27:53 -04:00
|
|
|
|
}
|
2011-05-02 19:25:23 -04:00
|
|
|
|
// Adjust the indices of the segments above.
|
|
|
|
|
for (var i = from, l = segments.length; i < l; i++)
|
|
|
|
|
segments[i]._index = i;
|
|
|
|
|
// Keep curves in sync
|
|
|
|
|
if (curves) {
|
2013-01-22 17:46:49 -05:00
|
|
|
|
// If we're removing the last segment, remove the last curve (the
|
|
|
|
|
// one to the left of the segment, not to the right, as normally).
|
|
|
|
|
// Also take into account closed paths, which have one curve more
|
|
|
|
|
// than segments.
|
2013-04-26 11:46:57 -04:00
|
|
|
|
var index = from > 0 && to === count + (this._closed ? 1 : 0)
|
|
|
|
|
? from - 1
|
|
|
|
|
: from,
|
2013-01-22 17:46:49 -05:00
|
|
|
|
curves = curves.splice(index, amount);
|
|
|
|
|
// Return the removed curves as well, if we're asked to include
|
|
|
|
|
// them, but exclude the first curve, since that's shared with the
|
|
|
|
|
// previous segment and does not connect the returned segments.
|
2014-01-05 11:40:54 -05:00
|
|
|
|
if (_includeCurves)
|
2013-01-22 17:46:49 -05:00
|
|
|
|
removed._curves = curves.slice(1);
|
2011-05-02 19:25:23 -04:00
|
|
|
|
// Adjust segments for the curves before and after the removed ones
|
2013-01-22 17:46:49 -05:00
|
|
|
|
this._adjustCurves(index, index);
|
2011-05-02 19:25:23 -04:00
|
|
|
|
}
|
2014-03-18 06:42:38 -04:00
|
|
|
|
// Use SEGMENTS notification instead of GEOMETRY since curves are kept
|
|
|
|
|
// up-to-date by _adjustCurves() and don't need notification.
|
2014-03-17 09:35:33 -04:00
|
|
|
|
this._changed(/*#=*/ Change.SEGMENTS);
|
2011-05-02 19:25:23 -04:00
|
|
|
|
return removed;
|
2011-04-13 10:16:32 -04:00
|
|
|
|
},
|
2011-05-22 18:26:08 -04:00
|
|
|
|
|
2013-11-29 12:50:04 -05:00
|
|
|
|
// DOCS Path#clear()
|
2013-11-01 12:52:27 -04:00
|
|
|
|
clear: '#removeSegments',
|
|
|
|
|
|
2011-06-05 15:28:18 -04:00
|
|
|
|
/**
|
|
|
|
|
* Specifies whether an path is selected and will also return {@code true}
|
|
|
|
|
* if the path is partially selected, i.e. one or more of its segments is
|
|
|
|
|
* selected.
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-06-05 15:28:18 -04:00
|
|
|
|
* Paper.js draws the visual outlines of selected items on top of your
|
|
|
|
|
* project. This can be useful for debugging, as it allows you to see the
|
|
|
|
|
* construction of paths, position of path curves, individual segment points
|
|
|
|
|
* and bounding boxes of symbol and raster items.
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-06-05 15:28:18 -04:00
|
|
|
|
* @type Boolean
|
|
|
|
|
* @bean
|
|
|
|
|
* @see Project#selectedItems
|
|
|
|
|
* @see Segment#selected
|
|
|
|
|
* @see Point#selected
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-06-05 15:28:18 -04:00
|
|
|
|
* @example {@paperscript}
|
|
|
|
|
* // Selecting an item:
|
2013-03-03 14:48:03 -05:00
|
|
|
|
* var path = new Path.Circle({
|
2014-04-02 15:03:35 -04:00
|
|
|
|
* center: new Size(80, 50),
|
|
|
|
|
* radius: 35
|
2013-03-03 14:48:03 -05:00
|
|
|
|
* });
|
2011-06-05 15:28:18 -04:00
|
|
|
|
* path.selected = true; // Select the path
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-06-05 15:28:18 -04:00
|
|
|
|
* @example {@paperscript}
|
|
|
|
|
* // A path is selected, if one or more of its segments is selected:
|
2013-03-03 14:48:03 -05:00
|
|
|
|
* var path = new Path.Circle({
|
2014-04-02 15:03:35 -04:00
|
|
|
|
* center: new Size(80, 50),
|
|
|
|
|
* radius: 35
|
2013-03-03 14:48:03 -05:00
|
|
|
|
* });
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-06-05 15:28:18 -04:00
|
|
|
|
* // Select the second segment of the path:
|
|
|
|
|
* path.segments[1].selected = true;
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-06-05 15:28:18 -04:00
|
|
|
|
* // If the path is selected (which it is), set its fill color to red:
|
|
|
|
|
* if (path.selected) {
|
2014-04-02 15:03:35 -04:00
|
|
|
|
* path.fillColor = 'red';
|
2011-06-05 15:28:18 -04:00
|
|
|
|
* }
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-06-05 15:28:18 -04:00
|
|
|
|
*/
|
2011-05-22 18:26:08 -04:00
|
|
|
|
/**
|
2013-10-17 06:03:46 -04:00
|
|
|
|
* Specifies whether the path and all its segments are selected. Cannot be
|
|
|
|
|
* {@code true} on an empty path.
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-05-30 13:42:17 -04:00
|
|
|
|
* @type Boolean
|
2011-05-22 18:26:08 -04:00
|
|
|
|
* @bean
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-06-05 15:28:18 -04:00
|
|
|
|
* @example {@paperscript}
|
|
|
|
|
* // A path is fully selected, if all of its segments are selected:
|
2013-03-03 14:48:03 -05:00
|
|
|
|
* var path = new Path.Circle({
|
2014-04-02 15:03:35 -04:00
|
|
|
|
* center: new Size(80, 50),
|
|
|
|
|
* radius: 35
|
2013-03-03 14:48:03 -05:00
|
|
|
|
* });
|
2011-06-14 10:37:25 -04:00
|
|
|
|
* path.fullySelected = true;
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2013-03-03 14:48:03 -05:00
|
|
|
|
* var path2 = new Path.Circle({
|
2014-04-02 15:03:35 -04:00
|
|
|
|
* center: new Size(180, 50),
|
|
|
|
|
* radius: 35
|
2013-03-03 14:48:03 -05:00
|
|
|
|
* });
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-06-05 15:28:18 -04:00
|
|
|
|
* // Deselect the second segment of the second path:
|
|
|
|
|
* path2.segments[1].selected = false;
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-06-05 15:28:18 -04:00
|
|
|
|
* // If the path is fully selected (which it is),
|
|
|
|
|
* // set its fill color to red:
|
|
|
|
|
* if (path.fullySelected) {
|
2014-04-02 15:03:35 -04:00
|
|
|
|
* path.fillColor = 'red';
|
2011-06-05 15:28:18 -04:00
|
|
|
|
* }
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-06-05 15:28:18 -04:00
|
|
|
|
* // If the second path is fully selected (which it isn't, since we just
|
|
|
|
|
* // deselected its second segment),
|
|
|
|
|
* // set its fill color to red:
|
|
|
|
|
* if (path2.fullySelected) {
|
2014-04-02 15:03:35 -04:00
|
|
|
|
* path2.fillColor = 'red';
|
2011-06-05 15:28:18 -04:00
|
|
|
|
* }
|
2011-05-22 18:26:08 -04:00
|
|
|
|
*/
|
2011-04-21 12:06:06 -04:00
|
|
|
|
isFullySelected: function() {
|
2013-10-17 06:03:46 -04:00
|
|
|
|
var length = this._segments.length;
|
|
|
|
|
return this._selected && length > 0 && this._selectedSegmentState
|
2013-12-08 12:06:31 -05:00
|
|
|
|
=== length * /*#=*/ SelectionState.SEGMENT;
|
2011-04-21 12:06:06 -04:00
|
|
|
|
},
|
2011-06-12 13:40:30 -04:00
|
|
|
|
|
2011-04-21 12:06:06 -04:00
|
|
|
|
setFullySelected: function(selected) {
|
2013-03-01 23:25:09 -05:00
|
|
|
|
// No need to call _selectSegments() when selected is false, since
|
|
|
|
|
// #setSelected() does that for us
|
|
|
|
|
if (selected)
|
|
|
|
|
this._selectSegments(true);
|
|
|
|
|
this.setSelected(selected);
|
|
|
|
|
},
|
|
|
|
|
|
2013-05-27 13:04:05 -04:00
|
|
|
|
setSelected: function setSelected(selected) {
|
2013-03-01 23:25:09 -05:00
|
|
|
|
// Deselect all segments when path is marked as not selected
|
|
|
|
|
if (!selected)
|
|
|
|
|
this._selectSegments(false);
|
|
|
|
|
// No need to pass true for noChildren since Path has none anyway.
|
2013-05-27 13:04:05 -04:00
|
|
|
|
setSelected.base.call(this, selected);
|
2013-03-01 23:25:09 -05:00
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
_selectSegments: function(selected) {
|
2011-06-14 10:37:25 -04:00
|
|
|
|
var length = this._segments.length;
|
|
|
|
|
this._selectedSegmentState = selected
|
2013-12-08 12:06:31 -05:00
|
|
|
|
? length * /*#=*/ SelectionState.SEGMENT : 0;
|
2011-06-14 10:37:25 -04:00
|
|
|
|
for (var i = 0; i < length; i++)
|
|
|
|
|
this._segments[i]._selectionState = selected
|
2013-12-08 12:06:31 -05:00
|
|
|
|
? /*#=*/ SelectionState.SEGMENT : 0;
|
2011-04-21 12:06:06 -04:00
|
|
|
|
},
|
2011-06-05 08:21:00 -04:00
|
|
|
|
|
2011-06-14 10:37:25 -04:00
|
|
|
|
_updateSelection: function(segment, oldState, newState) {
|
|
|
|
|
segment._selectionState = newState;
|
2011-06-20 14:08:34 -04:00
|
|
|
|
var total = this._selectedSegmentState += newState - oldState;
|
2012-11-02 19:27:59 -04:00
|
|
|
|
// Set this path as selected in case we have selected segments. Do not
|
|
|
|
|
// unselect if we're down to 0, as the path itself can still remain
|
|
|
|
|
// selected even when empty.
|
|
|
|
|
if (total > 0)
|
|
|
|
|
this.setSelected(true);
|
2011-06-14 10:37:25 -04:00
|
|
|
|
},
|
|
|
|
|
|
2011-06-05 15:28:18 -04:00
|
|
|
|
/**
|
2011-06-20 13:17:07 -04:00
|
|
|
|
* Converts the curves in a path to straight lines with an even distribution
|
|
|
|
|
* of points. The distance between the produced segments is as close as
|
|
|
|
|
* possible to the value specified by the {@code maxDistance} parameter.
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-06-05 15:28:18 -04:00
|
|
|
|
* @param {Number} maxDistance the maximum distance between the points
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-06-05 15:28:18 -04:00
|
|
|
|
* @example {@paperscript}
|
2011-06-20 13:17:07 -04:00
|
|
|
|
* // Flattening a circle shaped path:
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-06-05 15:28:18 -04:00
|
|
|
|
* // Create a circle shaped path at { x: 80, y: 50 }
|
|
|
|
|
* // with a radius of 35:
|
2013-03-03 14:48:03 -05:00
|
|
|
|
* var path = new Path.Circle({
|
2014-04-02 15:03:35 -04:00
|
|
|
|
* center: new Size(80, 50),
|
|
|
|
|
* radius: 35
|
2013-03-03 14:48:03 -05:00
|
|
|
|
* });
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-06-05 15:28:18 -04:00
|
|
|
|
* // Select the path, so we can inspect its segments:
|
|
|
|
|
* path.selected = true;
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-06-05 15:28:18 -04:00
|
|
|
|
* // Create a copy of the path and move it 150 points to the right:
|
|
|
|
|
* var copy = path.clone();
|
|
|
|
|
* copy.position.x += 150;
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-06-05 15:28:18 -04:00
|
|
|
|
* // Convert its curves to points, with a max distance of 20:
|
2011-06-20 13:17:07 -04:00
|
|
|
|
* copy.flatten(20);
|
2011-06-05 15:28:18 -04:00
|
|
|
|
*/
|
2011-06-20 13:17:07 -04:00
|
|
|
|
flatten: function(maxDistance) {
|
2011-06-05 08:21:00 -04:00
|
|
|
|
var flattener = new PathFlattener(this),
|
2011-06-05 14:27:18 -04:00
|
|
|
|
pos = 0,
|
|
|
|
|
// Adapt step = maxDistance so the points distribute evenly.
|
|
|
|
|
step = flattener.length / Math.ceil(flattener.length / maxDistance),
|
2011-07-09 03:51:06 -04:00
|
|
|
|
// Add/remove half of step to end, so imprecisions are ok too.
|
|
|
|
|
// For closed paths, remove it, because we don't want to add last
|
|
|
|
|
// segment again
|
|
|
|
|
end = flattener.length + (this._closed ? -step : step) / 2;
|
2011-06-05 08:21:00 -04:00
|
|
|
|
// Iterate over path and evaluate and add points at given offsets
|
2011-06-05 14:27:18 -04:00
|
|
|
|
var segments = [];
|
|
|
|
|
while (pos <= end) {
|
|
|
|
|
segments.push(new Segment(flattener.evaluate(pos, 0)));
|
|
|
|
|
pos += step;
|
|
|
|
|
}
|
2012-12-31 15:42:14 -05:00
|
|
|
|
this.setSegments(segments);
|
2011-06-05 14:27:18 -04:00
|
|
|
|
},
|
|
|
|
|
|
2014-02-19 20:50:39 -05:00
|
|
|
|
/**
|
|
|
|
|
* Reduces the path by removing curves that have a lenght of 0.
|
|
|
|
|
*/
|
|
|
|
|
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;
|
|
|
|
|
},
|
|
|
|
|
|
2011-06-09 18:07:25 -04:00
|
|
|
|
/**
|
2011-06-20 13:17:07 -04:00
|
|
|
|
* Smooths a path by simplifying it. The {@link Path#segments} array is
|
|
|
|
|
* analyzed and replaced by a more optimal set of segments, reducing memory
|
|
|
|
|
* usage and speeding up drawing.
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-06-09 18:07:25 -04:00
|
|
|
|
* @param {Number} [tolerance=2.5]
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-06-09 18:07:25 -04:00
|
|
|
|
* @example {@paperscript height=300}
|
|
|
|
|
* // Click and drag below to draw to draw a line, when you release the
|
2011-06-20 13:17:07 -04:00
|
|
|
|
* // mouse, the is made smooth using path.simplify():
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-06-09 18:07:25 -04:00
|
|
|
|
* var path;
|
|
|
|
|
* function onMouseDown(event) {
|
2014-04-02 15:03:35 -04:00
|
|
|
|
* // If we already made a path before, deselect it:
|
|
|
|
|
* if (path) {
|
|
|
|
|
* path.selected = false;
|
|
|
|
|
* }
|
2013-03-03 14:48:03 -05:00
|
|
|
|
*
|
2014-04-02 15:03:35 -04:00
|
|
|
|
* // Create a new path and add the position of the mouse
|
|
|
|
|
* // as its first segment. Select it, so we can see the
|
|
|
|
|
* // segment points:
|
|
|
|
|
* path = new Path({
|
|
|
|
|
* segments: [event.point],
|
|
|
|
|
* strokeColor: 'black',
|
|
|
|
|
* selected: true
|
|
|
|
|
* });
|
2011-06-09 18:07:25 -04:00
|
|
|
|
* }
|
2013-03-03 14:48:03 -05:00
|
|
|
|
*
|
2011-06-09 18:07:25 -04:00
|
|
|
|
* function onMouseDrag(event) {
|
2014-04-02 15:03:35 -04:00
|
|
|
|
* // On every drag event, add a segment to the path
|
|
|
|
|
* // at the position of the mouse:
|
|
|
|
|
* path.add(event.point);
|
2011-06-09 18:07:25 -04:00
|
|
|
|
* }
|
2013-03-03 14:48:03 -05:00
|
|
|
|
*
|
2011-06-09 18:07:25 -04:00
|
|
|
|
* function onMouseUp(event) {
|
2014-04-02 15:03:35 -04:00
|
|
|
|
* // When the mouse is released, simplify the path:
|
|
|
|
|
* path.simplify();
|
|
|
|
|
* path.selected = true;
|
2011-06-09 18:07:25 -04:00
|
|
|
|
* }
|
|
|
|
|
*/
|
2011-06-20 13:17:07 -04:00
|
|
|
|
simplify: function(tolerance) {
|
2011-06-27 16:35:02 -04:00
|
|
|
|
if (this._segments.length > 2) {
|
|
|
|
|
var fitter = new PathFitter(this, tolerance || 2.5);
|
|
|
|
|
this.setSegments(fitter.fit());
|
|
|
|
|
}
|
2011-06-05 08:21:00 -04:00
|
|
|
|
},
|
|
|
|
|
|
2011-04-12 08:28:18 -04:00
|
|
|
|
// TODO: reduceSegments([flatness])
|
2012-12-31 16:28:41 -05:00
|
|
|
|
|
2013-03-16 12:59:31 -04:00
|
|
|
|
/**
|
|
|
|
|
* Splits the path at the given offset. After splitting, the path will be
|
|
|
|
|
* open. If the path was open already, splitting will result in two paths.
|
|
|
|
|
*
|
|
|
|
|
* @name Path#split
|
|
|
|
|
* @function
|
|
|
|
|
* @param {Number} offset the offset at which to split the path
|
|
|
|
|
* as a number between 0 and {@link Path#length}
|
2013-08-23 22:45:28 -04:00
|
|
|
|
* @return {Path} the newly created path after splitting, if any
|
2013-03-16 12:59:31 -04:00
|
|
|
|
*
|
|
|
|
|
* @example {@paperscript} // Splitting an open path
|
|
|
|
|
* var path = new Path();
|
|
|
|
|
* path.strokeColor = 'black';
|
|
|
|
|
* path.add(20, 20);
|
|
|
|
|
*
|
|
|
|
|
* // Add an arc through {x: 90, y: 80} to {x: 160, y: 20}
|
|
|
|
|
* path.arcTo([90, 80], [160, 20]);
|
|
|
|
|
*
|
|
|
|
|
* // Split the path at 30% of its length:
|
|
|
|
|
* var path2 = path.split(path.length * 0.3);
|
|
|
|
|
* path2.strokeColor = 'red';
|
|
|
|
|
*
|
|
|
|
|
* // Move the newly created path 40px to the right:
|
|
|
|
|
* path2.position.x += 40;
|
|
|
|
|
*
|
|
|
|
|
* @example {@paperscript} // Splitting a closed path
|
|
|
|
|
* var path = new Path.Rectangle({
|
2014-04-02 15:03:35 -04:00
|
|
|
|
* from: [20, 20],
|
|
|
|
|
* to: [80, 80],
|
|
|
|
|
* strokeColor: 'black'
|
2013-03-16 12:59:31 -04:00
|
|
|
|
* });
|
|
|
|
|
*
|
|
|
|
|
* // Split the path at 60% of its length:
|
|
|
|
|
* path.split(path.length * 0.6);
|
|
|
|
|
*
|
|
|
|
|
* // Move the first segment, to show where the path
|
|
|
|
|
* // was split:
|
|
|
|
|
* path.firstSegment.point.x += 20;
|
|
|
|
|
*
|
|
|
|
|
* // Select the first segment:
|
|
|
|
|
* path.firstSegment.selected = true;
|
|
|
|
|
*/
|
|
|
|
|
/**
|
|
|
|
|
* Splits the path at the given curve location. After splitting, the path
|
|
|
|
|
* will be open. If the path was open already, splitting will result in two
|
|
|
|
|
* paths.
|
|
|
|
|
*
|
|
|
|
|
* @name Path#split
|
|
|
|
|
* @function
|
|
|
|
|
* @param {CurveLocation} location the curve location at which to split
|
|
|
|
|
* the path
|
2013-08-23 22:45:28 -04:00
|
|
|
|
* @return {Path} the newly created path after splitting, if any
|
2013-03-16 12:59:31 -04:00
|
|
|
|
*
|
|
|
|
|
* @example {@paperscript}
|
|
|
|
|
* var path = new Path.Circle({
|
2014-04-02 15:03:35 -04:00
|
|
|
|
* center: view.center,
|
|
|
|
|
* radius: 40,
|
|
|
|
|
* strokeColor: 'black'
|
2013-03-16 12:59:31 -04:00
|
|
|
|
* });
|
|
|
|
|
*
|
|
|
|
|
* var pointOnCircle = view.center + {
|
2014-04-02 15:03:35 -04:00
|
|
|
|
* length: 40,
|
|
|
|
|
* angle: 30
|
2013-03-16 12:59:31 -04:00
|
|
|
|
* };
|
|
|
|
|
*
|
|
|
|
|
* var curveLocation = path.getNearestLocation(pointOnCircle);
|
|
|
|
|
*
|
|
|
|
|
* path.split(curveLocation);
|
|
|
|
|
* path.lastSegment.selected = true;
|
|
|
|
|
*/
|
|
|
|
|
/**
|
2013-06-27 20:18:57 -04:00
|
|
|
|
* Splits the path at the given curve index and parameter. After splitting,
|
|
|
|
|
* the path will be open. If the path was open already, splitting will
|
|
|
|
|
* result in two paths.
|
2013-03-16 12:59:31 -04:00
|
|
|
|
*
|
|
|
|
|
* @example {@paperscript} // Splitting an open path
|
|
|
|
|
* // Draw a V shaped path:
|
|
|
|
|
* var path = new Path([20, 20], [50, 80], [80, 20]);
|
|
|
|
|
* path.strokeColor = 'black';
|
|
|
|
|
*
|
|
|
|
|
* // Split the path half-way down its second curve:
|
|
|
|
|
* var path2 = path.split(1, 0.5);
|
|
|
|
|
*
|
|
|
|
|
* // Give the resulting path a red stroke-color
|
|
|
|
|
* // and move it 20px to the right:
|
|
|
|
|
* path2.strokeColor = 'red';
|
|
|
|
|
* path2.position.x += 20;
|
|
|
|
|
*
|
|
|
|
|
* @example {@paperscript} // Splitting a closed path
|
|
|
|
|
* var path = new Path.Rectangle({
|
2014-04-02 15:03:35 -04:00
|
|
|
|
* from: [20, 20],
|
|
|
|
|
* to: [80, 80],
|
|
|
|
|
* strokeColor: 'black'
|
2013-03-16 12:59:31 -04:00
|
|
|
|
* });
|
|
|
|
|
*
|
|
|
|
|
* // Split the path half-way down its second curve:
|
|
|
|
|
* path.split(2, 0.5);
|
|
|
|
|
*
|
|
|
|
|
* // Move the first segment, to show where the path
|
|
|
|
|
* // was split:
|
|
|
|
|
* path.firstSegment.point.x += 20;
|
|
|
|
|
*
|
|
|
|
|
* // Select the first segment:
|
|
|
|
|
* path.firstSegment.selected = true;
|
|
|
|
|
*
|
|
|
|
|
* @param {Number} index the index of the curve in the {@link Path#curves}
|
|
|
|
|
* array at which to split
|
|
|
|
|
* @param {Number} parameter the parameter at which the curve will be split
|
2013-08-23 22:45:28 -04:00
|
|
|
|
* @return {Path} the newly created path after splitting, if any
|
2013-03-16 12:59:31 -04:00
|
|
|
|
*/
|
2012-12-31 16:28:41 -05:00
|
|
|
|
split: function(index, parameter) {
|
2013-01-22 17:46:49 -05:00
|
|
|
|
if (parameter === null)
|
|
|
|
|
return;
|
2013-06-27 20:18:57 -04:00
|
|
|
|
if (arguments.length === 1) {
|
2012-12-31 16:28:41 -05:00
|
|
|
|
var arg = index;
|
|
|
|
|
// split(offset), convert offset to location
|
|
|
|
|
if (typeof arg === 'number')
|
|
|
|
|
arg = this.getLocationAt(arg);
|
|
|
|
|
// split(location)
|
|
|
|
|
index = arg.index;
|
|
|
|
|
parameter = arg.parameter;
|
|
|
|
|
}
|
2014-02-15 17:37:41 -05:00
|
|
|
|
var tolerance = /*#=*/ Numerical.TOLERANCE;
|
|
|
|
|
if (parameter >= 1 - tolerance) {
|
2012-12-31 16:28:41 -05:00
|
|
|
|
// t == 1 is the same as t == 0 and index ++
|
|
|
|
|
index++;
|
|
|
|
|
parameter--;
|
|
|
|
|
}
|
|
|
|
|
var curves = this.getCurves();
|
|
|
|
|
if (index >= 0 && index < curves.length) {
|
|
|
|
|
// Only divide curves if we're not on an existing segment already.
|
2014-02-15 17:37:41 -05:00
|
|
|
|
if (parameter > tolerance) {
|
2012-12-31 16:28:41 -05:00
|
|
|
|
// Divide the curve with the index at given parameter.
|
|
|
|
|
// Increase because dividing adds more segments to the path.
|
2013-07-04 22:39:55 -04:00
|
|
|
|
curves[index++].divide(parameter, true);
|
2012-12-31 16:28:41 -05:00
|
|
|
|
}
|
|
|
|
|
// Create the new path with the segments to the right of given
|
2013-01-22 17:46:49 -05:00
|
|
|
|
// parameter, which are removed from the current path. Pass true
|
|
|
|
|
// for includeCurves, since we want to preserve and move them to
|
|
|
|
|
// the new path through _add(), allowing us to have CurveLocation
|
|
|
|
|
// keep the connection to the new path through moved curves.
|
|
|
|
|
var segs = this.removeSegments(index, this._segments.length, true),
|
|
|
|
|
path;
|
2012-12-31 16:28:41 -05:00
|
|
|
|
if (this._closed) {
|
2013-01-22 17:46:49 -05:00
|
|
|
|
// If the path is closed, open it and move the segments round,
|
|
|
|
|
// otherwise create two paths.
|
2012-12-31 16:28:41 -05:00
|
|
|
|
this.setClosed(false);
|
2013-01-22 17:46:49 -05:00
|
|
|
|
// Just have path point to this. The moving around of segments
|
|
|
|
|
// will happen below.
|
|
|
|
|
path = this;
|
2012-12-31 16:28:41 -05:00
|
|
|
|
} else if (index > 0) {
|
2013-04-19 14:54:16 -04:00
|
|
|
|
// Pass true for _preserve, in case of CompoundPath, to avoid
|
2013-01-22 17:46:49 -05:00
|
|
|
|
// reversing of path direction, which would mess with segs!
|
|
|
|
|
// Use _clone to copy over all other attributes, including style
|
|
|
|
|
path = this._clone(new Path().insertAbove(this, true));
|
2012-12-31 16:28:41 -05:00
|
|
|
|
}
|
2013-01-22 17:46:49 -05:00
|
|
|
|
path._add(segs, 0);
|
|
|
|
|
// Add dividing segment again. In case of a closed path, that's the
|
|
|
|
|
// beginning segment again at the end, since we opened it.
|
|
|
|
|
this.addSegment(segs[0]);
|
|
|
|
|
return path;
|
2012-12-31 16:28:41 -05:00
|
|
|
|
}
|
|
|
|
|
return null;
|
|
|
|
|
},
|
2011-05-15 12:59:06 -04:00
|
|
|
|
|
|
|
|
|
/**
|
2011-05-22 18:26:08 -04:00
|
|
|
|
* Specifies whether the path is oriented clock-wise.
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-05-30 13:42:17 -04:00
|
|
|
|
* @type Boolean
|
2011-05-22 18:26:08 -04:00
|
|
|
|
* @bean
|
2011-05-15 12:59:06 -04:00
|
|
|
|
*/
|
|
|
|
|
isClockwise: function() {
|
|
|
|
|
if (this._clockwise !== undefined)
|
|
|
|
|
return this._clockwise;
|
2013-04-24 22:27:31 -04:00
|
|
|
|
return Path.isClockwise(this._segments);
|
2011-05-15 12:59:06 -04:00
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
setClockwise: function(clockwise) {
|
2013-05-04 00:41:22 -04:00
|
|
|
|
// Only revers the path if its clockwise orientation is not the same
|
|
|
|
|
// as what it is now demanded to be.
|
2011-05-15 12:59:06 -04:00
|
|
|
|
// On-the-fly conversion to boolean:
|
2013-05-04 00:41:22 -04:00
|
|
|
|
if (this.isClockwise() != (clockwise = !!clockwise))
|
2011-05-15 12:59:06 -04:00
|
|
|
|
this.reverse();
|
2012-12-01 14:31:03 -05:00
|
|
|
|
// Reverse only flips _clockwise state if it was already set, so let's
|
|
|
|
|
// always set this here now.
|
|
|
|
|
this._clockwise = clockwise;
|
2011-05-15 12:59:06 -04:00
|
|
|
|
},
|
|
|
|
|
|
2011-04-11 17:30:08 -04:00
|
|
|
|
/**
|
2013-05-04 00:41:22 -04:00
|
|
|
|
* Reverses the orientation of the path, by reversing all its segments.
|
2011-04-11 17:30:08 -04:00
|
|
|
|
*/
|
|
|
|
|
reverse: function() {
|
2011-05-15 12:59:06 -04:00
|
|
|
|
this._segments.reverse();
|
2011-04-27 09:49:06 -04:00
|
|
|
|
// Reverse the handles:
|
2011-05-15 12:59:06 -04:00
|
|
|
|
for (var i = 0, l = this._segments.length; i < l; i++) {
|
|
|
|
|
var segment = this._segments[i];
|
2011-04-27 09:49:06 -04:00
|
|
|
|
var handleIn = segment._handleIn;
|
|
|
|
|
segment._handleIn = segment._handleOut;
|
|
|
|
|
segment._handleOut = handleIn;
|
2012-02-18 18:13:23 -05:00
|
|
|
|
segment._index = i;
|
2011-04-27 09:49:06 -04:00
|
|
|
|
}
|
2013-05-04 00:33:17 -04:00
|
|
|
|
// Clear curves since it all has changed.
|
2013-12-17 17:27:48 -05:00
|
|
|
|
this._curves = null;
|
2011-05-15 12:59:06 -04:00
|
|
|
|
// Flip clockwise state if it's defined
|
|
|
|
|
if (this._clockwise !== undefined)
|
|
|
|
|
this._clockwise = !this._clockwise;
|
2011-04-11 17:30:08 -04:00
|
|
|
|
},
|
2011-04-27 16:23:57 -04:00
|
|
|
|
|
2014-03-31 12:14:58 -04:00
|
|
|
|
// DOCS: document Path#join(path) in more detail.
|
|
|
|
|
// DOCS: document Path#join() (joining with itself)
|
|
|
|
|
// TODO: Consider adding a distance / tolerance parameter for merging.
|
2011-05-22 18:26:08 -04:00
|
|
|
|
/**
|
2011-06-05 10:11:13 -04:00
|
|
|
|
* Joins the path with the specified path, which will be removed in the
|
|
|
|
|
* process.
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-06-05 10:11:13 -04:00
|
|
|
|
* @param {Path} path
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-06-05 10:11:13 -04:00
|
|
|
|
* @example {@paperscript}
|
|
|
|
|
* // Joining two paths:
|
2013-03-03 14:48:03 -05:00
|
|
|
|
* var path = new Path({
|
2014-04-02 15:03:35 -04:00
|
|
|
|
* segments: [[30, 25], [30, 75]],
|
|
|
|
|
* strokeColor: 'black'
|
2013-03-03 14:48:03 -05:00
|
|
|
|
* });
|
|
|
|
|
*
|
|
|
|
|
* var path2 = new Path({
|
2014-04-02 15:03:35 -04:00
|
|
|
|
* segments: [[200, 25], [200, 75]],
|
|
|
|
|
* strokeColor: 'black'
|
2013-03-03 14:48:03 -05:00
|
|
|
|
* });
|
|
|
|
|
*
|
2011-06-05 10:11:13 -04:00
|
|
|
|
* // Join the paths:
|
|
|
|
|
* path.join(path2);
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-06-05 10:11:13 -04:00
|
|
|
|
* @example {@paperscript}
|
|
|
|
|
* // Joining two paths that share a point at the start or end of their
|
|
|
|
|
* // segments array:
|
2013-03-03 14:48:03 -05:00
|
|
|
|
* var path = new Path({
|
2014-04-02 15:03:35 -04:00
|
|
|
|
* segments: [[30, 25], [30, 75]],
|
|
|
|
|
* strokeColor: 'black'
|
2013-03-03 14:48:03 -05:00
|
|
|
|
* });
|
|
|
|
|
*
|
|
|
|
|
* var path2 = new Path({
|
2014-04-02 15:03:35 -04:00
|
|
|
|
* segments: [[30, 25], [80, 25]],
|
|
|
|
|
* strokeColor: 'black'
|
2013-03-03 14:48:03 -05:00
|
|
|
|
* });
|
|
|
|
|
*
|
2011-06-05 10:11:13 -04:00
|
|
|
|
* // Join the paths:
|
|
|
|
|
* path.join(path2);
|
2013-03-03 14:48:03 -05:00
|
|
|
|
*
|
2011-06-05 10:11:13 -04:00
|
|
|
|
* // After joining, path with have 3 segments, since it
|
|
|
|
|
* // shared its first segment point with the first
|
|
|
|
|
* // segment point of path2.
|
2013-03-03 14:48:03 -05:00
|
|
|
|
*
|
2011-06-05 10:11:13 -04:00
|
|
|
|
* // Select the path to show that they have joined:
|
|
|
|
|
* path.selected = true;
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-06-05 10:11:13 -04:00
|
|
|
|
* @example {@paperscript}
|
|
|
|
|
* // Joining two paths that connect at two points:
|
2013-03-03 14:48:03 -05:00
|
|
|
|
* var path = new Path({
|
2014-04-02 15:03:35 -04:00
|
|
|
|
* segments: [[30, 25], [80, 25], [80, 75]],
|
|
|
|
|
* strokeColor: 'black'
|
2013-03-03 14:48:03 -05:00
|
|
|
|
* });
|
|
|
|
|
*
|
|
|
|
|
* var path2 = new Path({
|
2014-04-02 15:03:35 -04:00
|
|
|
|
* segments: [[30, 25], [30, 75], [80, 75]],
|
|
|
|
|
* strokeColor: 'black'
|
2013-03-03 14:48:03 -05:00
|
|
|
|
* });
|
|
|
|
|
*
|
2011-06-05 10:11:13 -04:00
|
|
|
|
* // Join the paths:
|
|
|
|
|
* path.join(path2);
|
2013-03-03 14:48:03 -05:00
|
|
|
|
*
|
2011-06-05 10:11:13 -04:00
|
|
|
|
* // Because the paths were joined at two points, the path is closed
|
|
|
|
|
* // and has 4 segments.
|
2013-03-03 14:48:03 -05:00
|
|
|
|
*
|
2011-06-05 10:11:13 -04:00
|
|
|
|
* // Select the path to show that they have joined:
|
|
|
|
|
* path.selected = true;
|
|
|
|
|
*/
|
2011-04-11 17:30:08 -04:00
|
|
|
|
join: function(path) {
|
2011-04-26 12:49:54 -04:00
|
|
|
|
if (path) {
|
2011-05-03 03:47:52 -04:00
|
|
|
|
var segments = path._segments,
|
2011-04-21 15:01:31 -04:00
|
|
|
|
last1 = this.getLastSegment(),
|
|
|
|
|
last2 = path.getLastSegment();
|
2011-04-21 12:43:22 -04:00
|
|
|
|
if (last1._point.equals(last2._point))
|
2011-04-11 17:30:08 -04:00
|
|
|
|
path.reverse();
|
2013-06-27 19:06:24 -04:00
|
|
|
|
var first1,
|
|
|
|
|
first2 = path.getFirstSegment();
|
2011-04-21 12:43:22 -04:00
|
|
|
|
if (last1._point.equals(first2._point)) {
|
2011-04-21 15:01:31 -04:00
|
|
|
|
last1.setHandleOut(first2._handleOut);
|
2011-05-04 13:42:40 -04:00
|
|
|
|
this._add(segments.slice(1));
|
2011-04-11 17:30:08 -04:00
|
|
|
|
} else {
|
2013-06-27 19:06:24 -04:00
|
|
|
|
first1 = this.getFirstSegment();
|
2011-04-21 12:43:22 -04:00
|
|
|
|
if (first1._point.equals(first2._point))
|
2011-04-11 17:30:08 -04:00
|
|
|
|
path.reverse();
|
2011-06-05 09:56:37 -04:00
|
|
|
|
last2 = path.getLastSegment();
|
2011-04-21 12:43:22 -04:00
|
|
|
|
if (first1._point.equals(last2._point)) {
|
|
|
|
|
first1.setHandleIn(last2._handleIn);
|
2011-06-05 09:56:37 -04:00
|
|
|
|
// Prepend all segments from path except the last one
|
2011-05-05 19:14:09 -04:00
|
|
|
|
this._add(segments.slice(0, segments.length - 1), 0);
|
2011-04-11 17:30:08 -04:00
|
|
|
|
} else {
|
2013-04-06 12:39:17 -04:00
|
|
|
|
this._add(segments.slice());
|
2011-04-11 17:30:08 -04:00
|
|
|
|
}
|
|
|
|
|
}
|
2013-06-27 18:43:26 -04:00
|
|
|
|
if (path.closed)
|
|
|
|
|
this._add([segments[0]]);
|
2011-04-11 17:30:08 -04:00
|
|
|
|
path.remove();
|
|
|
|
|
}
|
2014-03-31 12:14:58 -04:00
|
|
|
|
// Close the resulting path and merge first and last segment if they
|
|
|
|
|
// touch, meaning the touched at path ends. Also do this if no path
|
|
|
|
|
// argument was provided, in which cases the path is joined with itself
|
|
|
|
|
// only if its ends touch.
|
|
|
|
|
var first = this.getFirstSegment(),
|
|
|
|
|
last = this.getLastSegment();
|
|
|
|
|
if (first !== last && first._point.equals(last._point)) {
|
|
|
|
|
first.setHandleIn(last._handleIn);
|
|
|
|
|
last.remove();
|
|
|
|
|
this.setClosed(true);
|
|
|
|
|
}
|
2011-04-11 17:30:08 -04:00
|
|
|
|
},
|
2011-04-27 16:23:57 -04:00
|
|
|
|
|
2011-05-22 18:26:08 -04:00
|
|
|
|
/**
|
2013-04-25 20:37:19 -04:00
|
|
|
|
* The approximate length of the path in points.
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-05-27 14:15:15 -04:00
|
|
|
|
* @type Number
|
2011-05-22 18:26:08 -04:00
|
|
|
|
* @bean
|
|
|
|
|
*/
|
2011-04-27 15:08:57 -04:00
|
|
|
|
getLength: function() {
|
2011-05-01 19:17:21 -04:00
|
|
|
|
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;
|
2011-04-27 15:08:57 -04:00
|
|
|
|
},
|
|
|
|
|
|
2013-04-25 20:37:19 -04:00
|
|
|
|
/**
|
|
|
|
|
* The area of the path in square points. Self-intersecting paths can
|
|
|
|
|
* contain sub-areas that cancel each other out.
|
|
|
|
|
*
|
|
|
|
|
* @type Number
|
|
|
|
|
* @bean
|
|
|
|
|
*/
|
|
|
|
|
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;
|
|
|
|
|
},
|
|
|
|
|
|
2011-04-27 15:08:57 -04:00
|
|
|
|
_getOffset: function(location) {
|
|
|
|
|
var index = location && location.getIndex();
|
2011-04-26 12:49:54 -04:00
|
|
|
|
if (index != null) {
|
2011-04-27 15:08:57 -04:00
|
|
|
|
var curves = this.getCurves(),
|
|
|
|
|
offset = 0;
|
2011-04-11 17:30:08 -04:00
|
|
|
|
for (var i = 0; i < index; i++)
|
2011-04-27 15:08:57 -04:00
|
|
|
|
offset += curves[i].getLength();
|
2014-03-30 08:17:02 -04:00
|
|
|
|
var curve = curves[index],
|
|
|
|
|
parameter = location.getParameter();
|
|
|
|
|
if (parameter > 0)
|
|
|
|
|
offset += curve.getPartLength(0, parameter);
|
|
|
|
|
return offset;
|
2011-04-11 17:30:08 -04:00
|
|
|
|
}
|
2011-04-27 15:08:57 -04:00
|
|
|
|
return null;
|
2011-04-11 17:30:08 -04:00
|
|
|
|
},
|
2011-04-27 14:28:39 -04:00
|
|
|
|
|
2013-03-16 13:39:53 -04:00
|
|
|
|
/**
|
|
|
|
|
* Returns the curve location of the specified point if it lies on the
|
|
|
|
|
* path, {@code null} otherwise.
|
|
|
|
|
* @param {Point} point the point on the path.
|
|
|
|
|
* @return {CurveLocation} the curve location of the specified point.
|
|
|
|
|
*/
|
2013-12-17 17:28:55 -05:00
|
|
|
|
getLocationOf: function(point) { // TODO: Fix argument assignment!
|
|
|
|
|
var point = Point.read(arguments),
|
|
|
|
|
curves = this.getCurves();
|
2011-07-09 11:12:27 -04:00
|
|
|
|
for (var i = 0, l = curves.length; i < l; i++) {
|
2012-12-27 15:08:03 -05:00
|
|
|
|
var loc = curves[i].getLocationOf(point);
|
2013-01-22 17:46:49 -05:00
|
|
|
|
if (loc)
|
2012-12-27 15:08:03 -05:00
|
|
|
|
return loc;
|
2011-07-09 11:12:27 -04:00
|
|
|
|
}
|
|
|
|
|
return null;
|
|
|
|
|
},
|
|
|
|
|
|
2011-05-22 18:26:08 -04:00
|
|
|
|
// DOCS: document Path#getLocationAt
|
|
|
|
|
/**
|
2011-06-03 17:05:22 -04:00
|
|
|
|
* {@grouptitle Positions on Paths and Curves}
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-05-27 15:21:49 -04:00
|
|
|
|
* @param {Number} offset
|
2011-05-30 13:42:17 -04:00
|
|
|
|
* @param {Boolean} [isParameter=false]
|
2011-06-05 09:04:34 -04:00
|
|
|
|
* @return {CurveLocation}
|
2011-05-22 18:26:08 -04:00
|
|
|
|
*/
|
2011-04-27 16:40:52 -04:00
|
|
|
|
getLocationAt: function(offset, isParameter) {
|
|
|
|
|
var curves = this.getCurves(),
|
|
|
|
|
length = 0;
|
|
|
|
|
if (isParameter) {
|
|
|
|
|
// offset consists of curve index and curve parameter, before and
|
|
|
|
|
// after the fractional digit.
|
|
|
|
|
var index = ~~offset; // = Math.floor()
|
2012-12-27 15:08:03 -05:00
|
|
|
|
return curves[index].getLocationAt(offset - index, true);
|
2011-04-27 16:40:52 -04:00
|
|
|
|
}
|
|
|
|
|
for (var i = 0, l = curves.length; i < l; i++) {
|
|
|
|
|
var start = length,
|
|
|
|
|
curve = curves[i];
|
|
|
|
|
length += curve.getLength();
|
2014-02-15 17:37:41 -05:00
|
|
|
|
if (length > offset) {
|
2011-04-27 16:40:52 -04:00
|
|
|
|
// Found the segment within which the length lies
|
2012-12-27 15:08:03 -05:00
|
|
|
|
return curve.getLocationAt(offset - start);
|
2011-04-27 16:40:52 -04:00
|
|
|
|
}
|
|
|
|
|
}
|
2014-02-15 17:37:41 -05:00
|
|
|
|
// It may be that through imprecision of getLength, that the end of the
|
|
|
|
|
// last curve was missed:
|
2011-04-27 16:40:52 -04:00
|
|
|
|
if (offset <= this.getLength())
|
|
|
|
|
return new CurveLocation(curves[curves.length - 1], 1);
|
|
|
|
|
return null;
|
|
|
|
|
},
|
|
|
|
|
|
2011-04-27 14:28:39 -04:00
|
|
|
|
/**
|
2013-01-28 19:47:45 -05:00
|
|
|
|
* Calculates the point on the path at the given offset.
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-05-27 15:21:49 -04:00
|
|
|
|
* @param {Number} offset
|
2011-05-30 13:42:17 -04:00
|
|
|
|
* @param {Boolean} [isParameter=false]
|
2011-05-22 18:26:08 -04:00
|
|
|
|
* @return {Point} the point at the given offset
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-06-05 09:00:43 -04:00
|
|
|
|
* @example {@paperscript height=150}
|
|
|
|
|
* // Finding the point on a path at a given offset:
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-06-05 09:00:43 -04:00
|
|
|
|
* // Create an arc shaped path:
|
2013-03-03 14:48:03 -05:00
|
|
|
|
* var path = new Path({
|
2014-04-02 15:03:35 -04:00
|
|
|
|
* strokeColor: 'black'
|
2013-03-03 14:48:03 -05:00
|
|
|
|
* });
|
|
|
|
|
*
|
2011-06-05 09:00:43 -04:00
|
|
|
|
* path.add(new Point(40, 100));
|
|
|
|
|
* path.arcTo(new Point(150, 100));
|
2013-03-03 14:48:03 -05:00
|
|
|
|
*
|
2011-06-05 09:00:43 -04:00
|
|
|
|
* // We're going to be working with a third of the length
|
|
|
|
|
* // of the path as the offset:
|
|
|
|
|
* var offset = path.length / 3;
|
2013-03-03 14:48:03 -05:00
|
|
|
|
*
|
2011-06-05 09:00:43 -04:00
|
|
|
|
* // Find the point on the path:
|
|
|
|
|
* var point = path.getPointAt(offset);
|
2013-03-03 14:48:03 -05:00
|
|
|
|
*
|
2011-06-05 09:00:43 -04:00
|
|
|
|
* // Create a small circle shaped path at the point:
|
2013-03-03 14:48:03 -05:00
|
|
|
|
* var circle = new Path.Circle({
|
2014-04-02 15:03:35 -04:00
|
|
|
|
* center: point,
|
|
|
|
|
* radius: 3,
|
|
|
|
|
* fillColor: 'red'
|
2013-03-03 14:48:03 -05:00
|
|
|
|
* });
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-06-05 09:00:43 -04:00
|
|
|
|
* @example {@paperscript height=150}
|
|
|
|
|
* // Iterating over the length of a path:
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-06-05 09:00:43 -04:00
|
|
|
|
* // Create an arc shaped path:
|
2013-03-03 14:48:03 -05:00
|
|
|
|
* var path = new Path({
|
2014-04-02 15:03:35 -04:00
|
|
|
|
* strokeColor: 'black'
|
2013-03-03 14:48:03 -05:00
|
|
|
|
* });
|
|
|
|
|
*
|
2011-06-05 09:00:43 -04:00
|
|
|
|
* path.add(new Point(40, 100));
|
|
|
|
|
* path.arcTo(new Point(150, 100));
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-06-05 09:00:43 -04:00
|
|
|
|
* var amount = 5;
|
|
|
|
|
* var length = path.length;
|
|
|
|
|
* for (var i = 0; i < amount + 1; i++) {
|
2014-04-02 15:03:35 -04:00
|
|
|
|
* var offset = i / amount * length;
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2014-04-02 15:03:35 -04:00
|
|
|
|
* // Find the point on the path at the given offset:
|
|
|
|
|
* var point = path.getPointAt(offset);
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2014-04-02 15:03:35 -04:00
|
|
|
|
* // Create a small circle shaped path at the point:
|
|
|
|
|
* var circle = new Path.Circle({
|
|
|
|
|
* center: point,
|
|
|
|
|
* radius: 3,
|
|
|
|
|
* fillColor: 'red'
|
|
|
|
|
* });
|
2011-06-05 09:00:43 -04:00
|
|
|
|
* }
|
2011-04-27 14:28:39 -04:00
|
|
|
|
*/
|
2011-04-27 16:40:52 -04:00
|
|
|
|
getPointAt: function(offset, isParameter) {
|
|
|
|
|
var loc = this.getLocationAt(offset, isParameter);
|
2011-04-27 14:28:39 -04:00
|
|
|
|
return loc && loc.getPoint();
|
|
|
|
|
},
|
2011-06-13 14:09:10 -04:00
|
|
|
|
|
2011-04-11 17:30:08 -04:00
|
|
|
|
/**
|
2013-01-28 19:47:45 -05:00
|
|
|
|
* Calculates the tangent to the path at the given offset as a vector point.
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-05-27 15:21:49 -04:00
|
|
|
|
* @param {Number} offset
|
2011-05-30 13:42:17 -04:00
|
|
|
|
* @param {Boolean} [isParameter=false]
|
2011-05-22 18:26:08 -04:00
|
|
|
|
* @return {Point} the tangent vector at the given offset
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-06-05 09:00:43 -04:00
|
|
|
|
* @example {@paperscript height=150}
|
|
|
|
|
* // Working with the tangent vector at a given offset:
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-06-05 09:00:43 -04:00
|
|
|
|
* // Create an arc shaped path:
|
2013-03-03 14:48:03 -05:00
|
|
|
|
* var path = new Path({
|
2014-04-02 15:03:35 -04:00
|
|
|
|
* strokeColor: 'black'
|
2013-03-03 14:48:03 -05:00
|
|
|
|
* });
|
|
|
|
|
*
|
2011-06-05 09:00:43 -04:00
|
|
|
|
* path.add(new Point(40, 100));
|
|
|
|
|
* path.arcTo(new Point(150, 100));
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-06-05 09:00:43 -04:00
|
|
|
|
* // We're going to be working with a third of the length
|
|
|
|
|
* // of the path as the offset:
|
|
|
|
|
* var offset = path.length / 3;
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-06-05 09:00:43 -04:00
|
|
|
|
* // Find the point on the path:
|
|
|
|
|
* var point = path.getPointAt(offset);
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-06-05 09:00:43 -04:00
|
|
|
|
* // Find the tangent vector at the given offset:
|
|
|
|
|
* var tangent = path.getTangentAt(offset);
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-06-05 09:00:43 -04:00
|
|
|
|
* // Make the tangent vector 60pt long:
|
|
|
|
|
* tangent.length = 60;
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2013-03-04 06:09:58 -05:00
|
|
|
|
* var line = new Path({
|
2014-04-02 15:03:35 -04:00
|
|
|
|
* segments: [point, point + tangent],
|
|
|
|
|
* strokeColor: 'red'
|
2013-03-04 06:09:58 -05:00
|
|
|
|
* })
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-06-05 09:00:43 -04:00
|
|
|
|
* @example {@paperscript height=200}
|
|
|
|
|
* // Iterating over the length of a path:
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-06-05 09:00:43 -04:00
|
|
|
|
* // Create an arc shaped path:
|
2013-03-03 14:48:03 -05:00
|
|
|
|
* var path = new Path({
|
2014-04-02 15:03:35 -04:00
|
|
|
|
* strokeColor: 'black'
|
2013-03-03 14:48:03 -05:00
|
|
|
|
* });
|
|
|
|
|
*
|
2011-06-05 09:00:43 -04:00
|
|
|
|
* path.add(new Point(40, 100));
|
|
|
|
|
* path.arcTo(new Point(150, 100));
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-06-05 09:00:43 -04:00
|
|
|
|
* var amount = 6;
|
|
|
|
|
* var length = path.length;
|
|
|
|
|
* for (var i = 0; i < amount + 1; i++) {
|
2014-04-02 15:03:35 -04:00
|
|
|
|
* var offset = i / amount * length;
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2014-04-02 15:03:35 -04:00
|
|
|
|
* // Find the point on the path at the given offset:
|
|
|
|
|
* var point = path.getPointAt(offset);
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2014-04-02 15:03:35 -04:00
|
|
|
|
* // Find the normal vector on the path at the given offset:
|
|
|
|
|
* var tangent = path.getTangentAt(offset);
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2014-04-02 15:03:35 -04:00
|
|
|
|
* // Make the tangent vector 60pt long:
|
|
|
|
|
* tangent.length = 60;
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2014-04-02 15:03:35 -04:00
|
|
|
|
* var line = new Path({
|
|
|
|
|
* segments: [point, point + tangent],
|
|
|
|
|
* strokeColor: 'red'
|
|
|
|
|
* })
|
2013-03-04 06:37:55 -05:00
|
|
|
|
* }
|
2011-04-11 17:30:08 -04:00
|
|
|
|
*/
|
2011-04-27 16:40:52 -04:00
|
|
|
|
getTangentAt: function(offset, isParameter) {
|
|
|
|
|
var loc = this.getLocationAt(offset, isParameter);
|
2011-04-27 14:26:03 -04:00
|
|
|
|
return loc && loc.getTangent();
|
2011-04-11 17:30:08 -04:00
|
|
|
|
},
|
2011-06-13 14:09:10 -04:00
|
|
|
|
|
2011-04-11 17:30:08 -04:00
|
|
|
|
/**
|
2013-01-28 19:47:45 -05:00
|
|
|
|
* Calculates the normal to the path at the given offset as a vector point.
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-05-27 15:21:49 -04:00
|
|
|
|
* @param {Number} offset
|
2011-05-30 13:42:17 -04:00
|
|
|
|
* @param {Boolean} [isParameter=false]
|
2011-05-22 18:26:08 -04:00
|
|
|
|
* @return {Point} the normal vector at the given offset
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-06-05 09:00:43 -04:00
|
|
|
|
* @example {@paperscript height=150}
|
|
|
|
|
* // Working with the normal vector at a given offset:
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-06-05 09:00:43 -04:00
|
|
|
|
* // Create an arc shaped path:
|
2013-03-03 14:48:03 -05:00
|
|
|
|
* var path = new Path({
|
2014-04-02 15:03:35 -04:00
|
|
|
|
* strokeColor: 'black'
|
2013-03-03 14:48:03 -05:00
|
|
|
|
* });
|
|
|
|
|
*
|
2011-06-05 09:00:43 -04:00
|
|
|
|
* path.add(new Point(40, 100));
|
|
|
|
|
* path.arcTo(new Point(150, 100));
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-06-05 09:00:43 -04:00
|
|
|
|
* // We're going to be working with a third of the length
|
|
|
|
|
* // of the path as the offset:
|
|
|
|
|
* var offset = path.length / 3;
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-06-05 09:00:43 -04:00
|
|
|
|
* // Find the point on the path:
|
|
|
|
|
* var point = path.getPointAt(offset);
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-06-05 09:00:43 -04:00
|
|
|
|
* // Find the normal vector at the given offset:
|
|
|
|
|
* var normal = path.getNormalAt(offset);
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-06-05 09:00:43 -04:00
|
|
|
|
* // Make the normal vector 30pt long:
|
|
|
|
|
* normal.length = 30;
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2013-03-04 06:09:58 -05:00
|
|
|
|
* var line = new Path({
|
2014-04-02 15:03:35 -04:00
|
|
|
|
* segments: [point, point + normal],
|
|
|
|
|
* strokeColor: 'red'
|
2013-03-04 06:09:58 -05:00
|
|
|
|
* });
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-06-05 09:00:43 -04:00
|
|
|
|
* @example {@paperscript height=200}
|
|
|
|
|
* // Iterating over the length of a path:
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-06-05 09:00:43 -04:00
|
|
|
|
* // Create an arc shaped path:
|
2013-03-03 14:48:03 -05:00
|
|
|
|
* var path = new Path({
|
2014-04-02 15:03:35 -04:00
|
|
|
|
* strokeColor: 'black'
|
2013-03-03 14:48:03 -05:00
|
|
|
|
* });
|
|
|
|
|
*
|
2011-06-05 09:00:43 -04:00
|
|
|
|
* path.add(new Point(40, 100));
|
|
|
|
|
* path.arcTo(new Point(150, 100));
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-06-05 09:00:43 -04:00
|
|
|
|
* var amount = 10;
|
|
|
|
|
* var length = path.length;
|
|
|
|
|
* for (var i = 0; i < amount + 1; i++) {
|
2014-04-02 15:03:35 -04:00
|
|
|
|
* var offset = i / amount * length;
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2014-04-02 15:03:35 -04:00
|
|
|
|
* // Find the point on the path at the given offset:
|
|
|
|
|
* var point = path.getPointAt(offset);
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2014-04-02 15:03:35 -04:00
|
|
|
|
* // Find the normal vector on the path at the given offset:
|
|
|
|
|
* var normal = path.getNormalAt(offset);
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2014-04-02 15:03:35 -04:00
|
|
|
|
* // Make the normal vector 30pt long:
|
|
|
|
|
* normal.length = 30;
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2014-04-02 15:03:35 -04:00
|
|
|
|
* var line = new Path({
|
|
|
|
|
* segments: [point, point + normal],
|
|
|
|
|
* strokeColor: 'red'
|
|
|
|
|
* });
|
2013-03-04 06:09:58 -05:00
|
|
|
|
* }
|
2011-04-11 17:30:08 -04:00
|
|
|
|
*/
|
2011-04-27 16:40:52 -04:00
|
|
|
|
getNormalAt: function(offset, isParameter) {
|
|
|
|
|
var loc = this.getLocationAt(offset, isParameter);
|
2011-04-27 14:26:03 -04:00
|
|
|
|
return loc && loc.getNormal();
|
2011-07-04 17:32:15 -04:00
|
|
|
|
},
|
|
|
|
|
|
2011-08-01 06:48:27 -04:00
|
|
|
|
/**
|
|
|
|
|
* Returns the nearest location on the path to the specified point.
|
|
|
|
|
*
|
|
|
|
|
* @function
|
2013-08-23 22:45:28 -04:00
|
|
|
|
* @param point {Point} the point for which we search the nearest location
|
|
|
|
|
* @return {CurveLocation} the location on the path that's the closest to
|
2011-08-01 06:48:27 -04:00
|
|
|
|
* the specified point
|
|
|
|
|
*/
|
2013-12-17 17:28:55 -05:00
|
|
|
|
getNearestLocation: function(point) { // TODO: Fix argument assignment!
|
|
|
|
|
var point = Point.read(arguments),
|
|
|
|
|
curves = this.getCurves(),
|
2011-07-06 16:25:20 -04:00
|
|
|
|
minDist = Infinity,
|
|
|
|
|
minLoc = null;
|
|
|
|
|
for (var i = 0, l = curves.length; i < l; i++) {
|
2012-12-30 13:43:35 -05:00
|
|
|
|
var loc = curves[i].getNearestLocation(point);
|
2011-07-06 16:25:20 -04:00
|
|
|
|
if (loc._distance < minDist) {
|
|
|
|
|
minDist = loc._distance;
|
|
|
|
|
minLoc = loc;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return minLoc;
|
|
|
|
|
},
|
|
|
|
|
|
2011-08-01 06:48:27 -04:00
|
|
|
|
/**
|
|
|
|
|
* Returns the nearest point on the path to the specified point.
|
|
|
|
|
*
|
|
|
|
|
* @function
|
2013-08-23 22:45:28 -04:00
|
|
|
|
* @param point {Point} the point for which we search the nearest point
|
|
|
|
|
* @return {Point} the point on the path that's the closest to the specified
|
2011-08-01 06:48:27 -04:00
|
|
|
|
* point
|
2013-03-03 14:48:03 -05:00
|
|
|
|
*
|
|
|
|
|
* @example {@paperscript height=200}
|
|
|
|
|
* var star = new Path.Star({
|
2014-04-02 15:03:35 -04:00
|
|
|
|
* center: view.center,
|
|
|
|
|
* points: 10,
|
|
|
|
|
* radius1: 30,
|
|
|
|
|
* radius2: 60,
|
|
|
|
|
* strokeColor: 'black'
|
2013-03-03 14:48:03 -05:00
|
|
|
|
* });
|
|
|
|
|
*
|
|
|
|
|
* var circle = new Path.Circle({
|
2014-04-02 15:03:35 -04:00
|
|
|
|
* center: view.center,
|
|
|
|
|
* radius: 3,
|
|
|
|
|
* fillColor: 'red'
|
2013-03-04 06:09:58 -05:00
|
|
|
|
* });
|
2013-03-03 14:48:03 -05:00
|
|
|
|
*
|
|
|
|
|
* function onMouseMove(event) {
|
2014-04-02 15:03:35 -04:00
|
|
|
|
* // Get the nearest point from the mouse position
|
|
|
|
|
* // to the star shaped path:
|
|
|
|
|
* var nearestPoint = star.getNearestPoint(event.point);
|
2013-03-03 14:48:03 -05:00
|
|
|
|
*
|
2014-04-02 15:03:35 -04:00
|
|
|
|
* // Move the red circle to the nearest point:
|
|
|
|
|
* circle.position = nearestPoint;
|
2013-03-03 14:48:03 -05:00
|
|
|
|
* }
|
2011-08-01 06:48:27 -04:00
|
|
|
|
*/
|
2013-12-17 17:28:55 -05:00
|
|
|
|
getNearestPoint: function(point) { // TODO: Fix argument assignment!
|
2013-06-24 13:15:54 -04:00
|
|
|
|
// We need to use point to avoid minification issues and prevent method
|
|
|
|
|
// from turning into a bean (by removal of the point argument).
|
2013-12-17 17:28:55 -05:00
|
|
|
|
var point = Point.read(arguments);
|
2012-12-30 13:43:35 -05:00
|
|
|
|
return this.getNearestLocation(point).getPoint();
|
2011-07-06 16:25:20 -04:00
|
|
|
|
},
|
|
|
|
|
|
2013-10-16 17:35:50 -04:00
|
|
|
|
// DOCS: toShape
|
|
|
|
|
|
|
|
|
|
toShape: function(insert) {
|
2013-10-16 17:10:03 -04:00
|
|
|
|
if (!this._closed)
|
|
|
|
|
return null;
|
|
|
|
|
|
|
|
|
|
var segments = this._segments,
|
2013-10-16 17:35:50 -04:00
|
|
|
|
type,
|
|
|
|
|
size,
|
|
|
|
|
radius,
|
2013-10-16 17:10:03 -04:00
|
|
|
|
topCenter;
|
|
|
|
|
|
|
|
|
|
function isColinear(i, j) {
|
|
|
|
|
return segments[i].isColinear(segments[j]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function isOrthogonal(i) {
|
|
|
|
|
return segments[i].isOrthogonal();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function isArc(i) {
|
|
|
|
|
return segments[i].isArc();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getDistance(i, j) {
|
|
|
|
|
return segments[i]._point.getDistance(segments[j]._point);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// See if actually have any curves in the path. Differentiate
|
|
|
|
|
// between straight objects (line, polyline, rect, and polygon) and
|
|
|
|
|
// objects with curves(circle, ellipse, roundedRectangle).
|
|
|
|
|
if (this.isPolygon() && segments.length === 4
|
|
|
|
|
&& isColinear(0, 2) && isColinear(1, 3) && isOrthogonal(1)) {
|
2013-10-16 17:35:50 -04:00
|
|
|
|
type = Shape.Rectangle;
|
|
|
|
|
size = new Size(getDistance(0, 3), getDistance(0, 1));
|
2013-10-16 17:10:03 -04:00
|
|
|
|
topCenter = segments[1]._point.add(segments[2]._point).divide(2);
|
|
|
|
|
} else if (segments.length === 8 && isArc(0) && isArc(2) && isArc(4)
|
|
|
|
|
&& isArc(6) && isColinear(1, 5) && isColinear(3, 7)) {
|
|
|
|
|
// It's a rounded rectangle.
|
2013-10-16 17:35:50 -04:00
|
|
|
|
type = Shape.Rectangle;
|
|
|
|
|
size = new Size(getDistance(1, 6), getDistance(0, 3));
|
|
|
|
|
// Subtract side lengths from total width and divide by 2 to get the
|
|
|
|
|
// corner radius size.
|
|
|
|
|
radius = size.subtract(new Size(getDistance(0, 7),
|
|
|
|
|
getDistance(1, 2))).divide(2);
|
2013-10-16 17:10:03 -04:00
|
|
|
|
topCenter = segments[3]._point.add(segments[4]._point).divide(2);
|
|
|
|
|
} else if (segments.length === 4
|
|
|
|
|
&& isArc(0) && isArc(1) && isArc(2) && isArc(3)) {
|
|
|
|
|
// If the distance between (point0 and point2) and (point1
|
|
|
|
|
// and point3) are equal, then it is a circle
|
|
|
|
|
if (Numerical.isZero(getDistance(0, 2) - getDistance(1, 3))) {
|
2013-10-16 17:35:50 -04:00
|
|
|
|
type = Shape.Circle;
|
|
|
|
|
radius = getDistance(0, 2) / 2;
|
2013-10-16 17:10:03 -04:00
|
|
|
|
} else {
|
2013-10-16 17:35:50 -04:00
|
|
|
|
type = Shape.Ellipse;
|
|
|
|
|
radius = new Size(getDistance(2, 0) / 2, getDistance(3, 1) / 2);
|
2013-10-16 17:10:03 -04:00
|
|
|
|
}
|
|
|
|
|
topCenter = segments[1]._point;
|
|
|
|
|
}
|
|
|
|
|
|
2013-10-16 17:35:50 -04:00
|
|
|
|
if (type) {
|
|
|
|
|
var center = this.getPosition(true),
|
|
|
|
|
shape = new type({
|
|
|
|
|
center: center,
|
|
|
|
|
size: size,
|
|
|
|
|
radius: radius,
|
2013-10-29 11:45:50 -04:00
|
|
|
|
insert: false
|
2013-10-16 17:35:50 -04:00
|
|
|
|
});
|
2013-10-16 17:10:03 -04:00
|
|
|
|
// Determine and apply the shape's angle of rotation.
|
|
|
|
|
shape.rotate(topCenter.subtract(center).getAngle() + 90);
|
|
|
|
|
shape.setStyle(this._style);
|
2013-10-29 11:45:50 -04:00
|
|
|
|
// Insert is true by default.
|
|
|
|
|
if (insert || insert === undefined)
|
|
|
|
|
shape.insertAbove(this);
|
2013-10-16 17:35:50 -04:00
|
|
|
|
return shape;
|
2013-10-16 17:10:03 -04:00
|
|
|
|
}
|
2013-10-16 17:35:50 -04:00
|
|
|
|
return null;
|
2013-10-16 17:10:03 -04:00
|
|
|
|
},
|
|
|
|
|
|
2011-12-24 18:19:01 -05:00
|
|
|
|
_hitTest: function(point, options) {
|
2013-12-08 06:15:10 -05:00
|
|
|
|
var that = this,
|
|
|
|
|
style = this.getStyle(),
|
2013-06-15 08:06:09 -04:00
|
|
|
|
segments = this._segments,
|
2014-04-04 06:08:20 -04:00
|
|
|
|
numSegments = segments.length,
|
2013-06-15 08:06:09 -04:00
|
|
|
|
closed = this._closed,
|
2013-12-10 06:52:42 -05:00
|
|
|
|
// transformed tolerance padding, see Item#hitTest. We will add
|
|
|
|
|
// stroke padding on top if stroke is defined.
|
2013-12-10 08:23:05 -05:00
|
|
|
|
tolerancePadding = options._tolerancePadding,
|
|
|
|
|
strokePadding = tolerancePadding,
|
2013-12-08 06:15:10 -05:00
|
|
|
|
join, cap, miterLimit,
|
|
|
|
|
area, loc, res,
|
|
|
|
|
hasStroke = options.stroke && style.hasStroke(),
|
|
|
|
|
hasFill = options.fill && style.hasFill(),
|
|
|
|
|
radius = hasStroke ? style.getStrokeWidth() / 2
|
|
|
|
|
// Set radius to 0 so when we're hit-testing, the tolerance
|
|
|
|
|
// is used for fill too, through stroke functionality.
|
|
|
|
|
: hasFill ? 0 : null;
|
|
|
|
|
if (radius != null) {
|
2013-10-18 14:27:47 -04:00
|
|
|
|
if (radius > 0) {
|
|
|
|
|
join = style.getStrokeJoin();
|
|
|
|
|
cap = style.getStrokeCap();
|
|
|
|
|
miterLimit = radius * style.getMiterLimit();
|
2013-12-10 06:52:42 -05:00
|
|
|
|
// Add the stroke radius to tolerance padding.
|
2013-12-10 08:23:05 -05:00
|
|
|
|
strokePadding = tolerancePadding.add(new Point(radius, radius));
|
2013-10-18 14:27:47 -04:00
|
|
|
|
} else {
|
|
|
|
|
join = cap = 'round';
|
|
|
|
|
}
|
2013-12-10 06:52:42 -05:00
|
|
|
|
// Using tolerance padding for fill tests will also work if there is
|
|
|
|
|
// no stroke, in which case radius = 0 and we will test for stroke
|
|
|
|
|
// locations to extend the fill area by tolerance.
|
|
|
|
|
}
|
|
|
|
|
|
2013-12-10 08:23:05 -05:00
|
|
|
|
function isCloseEnough(pt, padding) {
|
2013-12-10 06:52:42 -05:00
|
|
|
|
return point.subtract(pt).divide(padding).length <= 1;
|
2013-06-15 08:06:09 -04:00
|
|
|
|
}
|
|
|
|
|
|
2013-12-10 13:34:25 -05:00
|
|
|
|
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
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
2011-12-25 06:34:51 -05:00
|
|
|
|
}
|
2013-06-13 17:30:52 -04:00
|
|
|
|
|
2013-06-15 08:06:09 -04:00
|
|
|
|
function checkSegmentPoints(seg, ends) {
|
2011-12-25 06:34:51 -05:00
|
|
|
|
// Note, when checking for ends, we don't also check for handles,
|
|
|
|
|
// since this will happen afterwards in a separate loop, see below.
|
2013-12-10 13:34:25 -05:00
|
|
|
|
return (ends || options.segments)
|
|
|
|
|
&& checkSegmentPoint(seg, seg._point, 'segment')
|
2011-12-25 06:34:51 -05:00
|
|
|
|
|| (!ends && options.handles) && (
|
2013-12-10 13:34:25 -05:00
|
|
|
|
checkSegmentPoint(seg, seg._handleIn, 'handle-in') ||
|
|
|
|
|
checkSegmentPoint(seg, seg._handleOut, 'handle-out'));
|
2011-07-08 17:26:21 -04:00
|
|
|
|
}
|
2013-06-13 17:30:52 -04:00
|
|
|
|
|
2013-06-15 05:24:59 -04:00
|
|
|
|
// Code to check stroke join / cap areas
|
|
|
|
|
|
2014-02-26 10:15:51 -05:00
|
|
|
|
function addToArea(point) {
|
2013-12-10 08:23:05 -05:00
|
|
|
|
area.add(point);
|
2013-06-15 05:24:59 -04:00
|
|
|
|
}
|
|
|
|
|
|
2013-06-15 08:06:09 -04:00
|
|
|
|
function checkSegmentStroke(segment) {
|
|
|
|
|
// Handle joins / caps that are not round specificelly, by
|
|
|
|
|
// hit-testing their polygon areas.
|
|
|
|
|
if (join !== 'round' || cap !== 'round') {
|
2013-12-10 08:23:05 -05:00
|
|
|
|
// Create an 'internal' path without id and outside the DOM
|
|
|
|
|
// to run the hit-test on it.
|
|
|
|
|
area = new Path({ internal: true, closed: true });
|
2013-06-15 08:06:09 -04:00
|
|
|
|
if (closed || segment._index > 0
|
2014-04-04 06:08:20 -04:00
|
|
|
|
&& segment._index < numSegments - 1) {
|
2013-06-15 08:06:09 -04:00
|
|
|
|
// It's a join. See that it's not a round one (one of
|
|
|
|
|
// the handles has to be zero too for this!)
|
|
|
|
|
if (join !== 'round' && (segment._handleIn.isZero()
|
|
|
|
|
|| segment._handleOut.isZero()))
|
2014-02-28 12:10:58 -05:00
|
|
|
|
// _addBevelJoin() handles both 'bevel' and 'miter'!
|
|
|
|
|
Path._addBevelJoin(segment, join, radius, miterLimit,
|
2014-02-26 10:15:51 -05:00
|
|
|
|
addToArea, true);
|
2013-06-15 08:06:09 -04:00
|
|
|
|
} else if (cap !== 'round') {
|
|
|
|
|
// It's a cap
|
2014-02-26 10:15:51 -05:00
|
|
|
|
Path._addSquareCap(segment, cap, radius, addToArea, true);
|
2013-06-15 08:06:09 -04:00
|
|
|
|
}
|
|
|
|
|
// See if the above produced an area to check for
|
2013-12-10 08:23:05 -05:00
|
|
|
|
if (!area.isEmpty()) {
|
|
|
|
|
// Also use stroke check with tolerancePadding if the point
|
|
|
|
|
// is not inside the area itself, to use test caps and joins
|
|
|
|
|
// with same tolerance.
|
|
|
|
|
var loc;
|
|
|
|
|
return area.contains(point)
|
|
|
|
|
|| (loc = area.getNearestLocation(point))
|
|
|
|
|
&& isCloseEnough(loc.getPoint(), tolerancePadding);
|
|
|
|
|
}
|
2013-06-15 08:06:09 -04:00
|
|
|
|
}
|
2013-12-10 06:52:42 -05:00
|
|
|
|
// Fallback scenario is a round join / cap.
|
2013-12-10 08:23:05 -05:00
|
|
|
|
return isCloseEnough(segment._point, strokePadding);
|
2013-06-15 08:06:09 -04:00
|
|
|
|
}
|
|
|
|
|
|
2013-06-13 17:30:52 -04:00
|
|
|
|
// If we're asked to query for segments, ends or handles, do all that
|
|
|
|
|
// before stroke or fill.
|
2013-06-15 08:06:09 -04:00
|
|
|
|
if (options.ends && !options.segments && !closed) {
|
2013-06-15 08:21:17 -04:00
|
|
|
|
if (res = checkSegmentPoints(segments[0], true)
|
2014-04-04 06:08:20 -04:00
|
|
|
|
|| checkSegmentPoints(segments[numSegments - 1], true))
|
2011-07-08 17:26:21 -04:00
|
|
|
|
return res;
|
|
|
|
|
} else if (options.segments || options.handles) {
|
2014-04-04 06:08:20 -04:00
|
|
|
|
for (var i = 0; i < numSegments; i++)
|
2013-06-15 08:06:09 -04:00
|
|
|
|
if (res = checkSegmentPoints(segments[i]))
|
2011-07-08 17:26:21 -04:00
|
|
|
|
return res;
|
|
|
|
|
}
|
2011-07-07 16:14:58 -04:00
|
|
|
|
// If we're querying for stroke, perform that before fill
|
2013-12-10 06:52:42 -05:00
|
|
|
|
if (radius != null) {
|
2012-12-30 13:43:35 -05:00
|
|
|
|
loc = this.getNearestLocation(point);
|
2014-04-04 06:08:20 -04:00
|
|
|
|
// Note that paths need at least two segments to have an actual
|
|
|
|
|
// stroke. But we still check for segments with the radius fallback
|
|
|
|
|
// check if there is only one segment.
|
2013-06-15 05:24:59 -04:00
|
|
|
|
if (loc) {
|
2013-06-15 08:15:48 -04:00
|
|
|
|
// Now see if we're on a segment, and if so, check for its
|
|
|
|
|
// stroke join / cap first. If not, do a normal radius check
|
|
|
|
|
// for round strokes.
|
2013-06-15 08:21:17 -04:00
|
|
|
|
var parameter = loc.getParameter();
|
2014-04-04 06:08:20 -04:00
|
|
|
|
if (parameter === 0 || parameter === 1 && numSegments > 1) {
|
2013-06-15 08:06:09 -04:00
|
|
|
|
if (!checkSegmentStroke(loc.getSegment()))
|
2013-06-15 05:24:59 -04:00
|
|
|
|
loc = null;
|
2013-12-10 08:23:05 -05:00
|
|
|
|
} else if (!isCloseEnough(loc.getPoint(), strokePadding)) {
|
2013-06-15 06:12:57 -04:00
|
|
|
|
loc = null;
|
2013-06-15 08:06:09 -04:00
|
|
|
|
}
|
|
|
|
|
}
|
2013-06-15 08:15:48 -04:00
|
|
|
|
// If we have miter joins, we may not be done yet, since they can be
|
|
|
|
|
// longer than the radius. Check for each segment within reach now.
|
2014-04-04 06:08:20 -04:00
|
|
|
|
if (!loc && join === 'miter' && numSegments > 1) {
|
|
|
|
|
for (var i = 0; i < numSegments; i++) {
|
2013-06-15 08:06:09 -04:00
|
|
|
|
var segment = segments[i];
|
2013-06-15 08:08:12 -04:00
|
|
|
|
if (point.getDistance(segment._point) <= miterLimit
|
2013-06-15 08:21:17 -04:00
|
|
|
|
&& checkSegmentStroke(segment)) {
|
2013-06-15 08:06:09 -04:00
|
|
|
|
loc = segment.getLocation();
|
2013-06-15 08:21:17 -04:00
|
|
|
|
break;
|
|
|
|
|
}
|
2013-06-15 08:06:09 -04:00
|
|
|
|
}
|
2013-06-15 05:24:59 -04:00
|
|
|
|
}
|
2013-06-13 17:30:52 -04:00
|
|
|
|
}
|
2011-07-07 16:14:58 -04:00
|
|
|
|
// Don't process loc yet, as we also need to query for stroke after fill
|
|
|
|
|
// in some cases. Simply skip fill query if we already have a matching
|
2013-12-08 06:15:10 -05:00
|
|
|
|
// stroke. If we have a loc and no stroke then it's a result for fill.
|
|
|
|
|
return !loc && hasFill && this._contains(point) || loc && !hasStroke
|
2013-06-13 17:30:52 -04:00
|
|
|
|
? new HitResult('fill', this)
|
2013-06-15 08:06:09 -04:00
|
|
|
|
: loc
|
2013-12-11 11:10:32 -05:00
|
|
|
|
? new HitResult('stroke', this, {
|
|
|
|
|
location: loc,
|
|
|
|
|
// It's fine performance wise to call getPoint() again
|
|
|
|
|
// since it was already called before.
|
|
|
|
|
point: loc.getPoint()
|
|
|
|
|
})
|
2013-06-13 17:30:52 -04:00
|
|
|
|
: null;
|
2011-07-04 19:15:45 -04:00
|
|
|
|
}
|
2011-07-01 11:40:29 -04:00
|
|
|
|
|
|
|
|
|
// TODO: intersects(item)
|
|
|
|
|
// TODO: contains(item)
|
|
|
|
|
// TODO: intersect(item)
|
|
|
|
|
// TODO: unite(item)
|
|
|
|
|
// TODO: exclude(item)
|
2011-04-17 12:46:35 -04:00
|
|
|
|
}, new function() { // Scope for drawing
|
2011-04-26 12:57:12 -04:00
|
|
|
|
|
|
|
|
|
// Note that in the code below we're often accessing _x and _y on point
|
|
|
|
|
// objects that were read from segments. This is because the SegmentPoint
|
|
|
|
|
// class overrides the plain x / y properties with getter / setters and
|
|
|
|
|
// stores the values in these private properties internally. To avoid
|
2011-08-16 07:36:58 -04:00
|
|
|
|
// calling of getter functions all the time we directly access these private
|
2011-04-26 12:57:12 -04:00
|
|
|
|
// properties here. The distinction between normal Point objects and
|
2011-08-16 07:36:58 -04:00
|
|
|
|
// SegmentPoint objects maybe seem a bit tedious but is worth the benefit in
|
|
|
|
|
// performance.
|
2011-04-26 12:57:12 -04:00
|
|
|
|
|
2013-03-01 21:28:22 -05:00
|
|
|
|
function drawHandles(ctx, segments, matrix, size) {
|
|
|
|
|
var half = size / 2;
|
|
|
|
|
|
2013-02-08 21:23:33 -05:00
|
|
|
|
function drawHandle(index) {
|
|
|
|
|
var hX = coords[index],
|
|
|
|
|
hY = coords[index + 1];
|
|
|
|
|
if (pX != hX || pY != hY) {
|
|
|
|
|
ctx.beginPath();
|
|
|
|
|
ctx.moveTo(pX, pY);
|
|
|
|
|
ctx.lineTo(hX, hY);
|
|
|
|
|
ctx.stroke();
|
|
|
|
|
ctx.beginPath();
|
2013-03-01 21:28:22 -05:00
|
|
|
|
ctx.arc(hX, hY, half, 0, Math.PI * 2, true);
|
2013-02-08 21:23:33 -05:00
|
|
|
|
ctx.fill();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2011-12-20 05:40:31 -05:00
|
|
|
|
var coords = new Array(6);
|
2011-04-17 12:46:35 -04:00
|
|
|
|
for (var i = 0, l = segments.length; i < l; i++) {
|
2011-12-20 05:40:31 -05:00
|
|
|
|
var segment = segments[i];
|
|
|
|
|
segment._transformCoordinates(matrix, coords, false);
|
|
|
|
|
var state = segment._selectionState,
|
|
|
|
|
pX = coords[0],
|
|
|
|
|
pY = coords[1];
|
2013-12-08 12:06:31 -05:00
|
|
|
|
if (state & /*#=*/ SelectionState.HANDLE_IN)
|
2011-12-20 05:40:31 -05:00
|
|
|
|
drawHandle(2);
|
2013-12-08 12:06:31 -05:00
|
|
|
|
if (state & /*#=*/ SelectionState.HANDLE_OUT)
|
2011-12-20 05:40:31 -05:00
|
|
|
|
drawHandle(4);
|
2011-04-17 12:46:35 -04:00
|
|
|
|
// Draw a rectangle at segment.point:
|
2014-03-04 03:26:55 -05:00
|
|
|
|
ctx.fillRect(pX - half, pY - half, size, size);
|
2011-06-03 05:33:34 -04:00
|
|
|
|
// If the point is not selected, draw a white square that is 1 px
|
|
|
|
|
// smaller on all sides:
|
2013-12-08 12:06:31 -05:00
|
|
|
|
if (!(state & /*#=*/ SelectionState.POINT)) {
|
2014-03-04 03:26:55 -05:00
|
|
|
|
var fillStyle = ctx.fillStyle;
|
2011-04-21 12:06:06 -04:00
|
|
|
|
ctx.fillStyle = '#ffffff';
|
2014-03-04 03:26:55 -05:00
|
|
|
|
ctx.fillRect(pX - half + 1, pY - half + 1, size - 2, size - 2);
|
|
|
|
|
ctx.fillStyle = fillStyle;
|
2011-04-21 12:06:06 -04:00
|
|
|
|
}
|
2011-04-17 12:46:35 -04:00
|
|
|
|
}
|
|
|
|
|
}
|
2011-06-13 14:09:10 -04:00
|
|
|
|
|
2011-12-20 05:40:31 -05:00
|
|
|
|
function drawSegments(ctx, path, matrix) {
|
2011-06-05 06:20:28 -04:00
|
|
|
|
var segments = path._segments,
|
|
|
|
|
length = segments.length,
|
2011-12-20 05:40:31 -05:00
|
|
|
|
coords = new Array(6),
|
|
|
|
|
first = true,
|
2012-10-05 22:08:31 -04:00
|
|
|
|
curX, curY,
|
|
|
|
|
prevX, prevY,
|
2011-12-20 05:40:31 -05:00
|
|
|
|
inX, inY,
|
|
|
|
|
outX, outY;
|
2011-06-04 10:28:06 -04:00
|
|
|
|
|
|
|
|
|
function drawSegment(i) {
|
2011-12-20 05:40:31 -05:00
|
|
|
|
var segment = segments[i];
|
|
|
|
|
// Optimise code when no matrix is provided by accessing semgent
|
|
|
|
|
// points hand handles directly, since this is the default when
|
|
|
|
|
// drawing paths. Matrix is only used for drawing selections.
|
|
|
|
|
if (matrix) {
|
|
|
|
|
segment._transformCoordinates(matrix, coords, false);
|
2012-10-05 22:08:31 -04:00
|
|
|
|
curX = coords[0];
|
|
|
|
|
curY = coords[1];
|
2011-12-20 05:40:31 -05:00
|
|
|
|
} else {
|
|
|
|
|
var point = segment._point;
|
2012-10-05 22:08:31 -04:00
|
|
|
|
curX = point._x;
|
|
|
|
|
curY = point._y;
|
2011-12-20 05:40:31 -05:00
|
|
|
|
}
|
|
|
|
|
if (first) {
|
2012-10-05 22:08:31 -04:00
|
|
|
|
ctx.moveTo(curX, curY);
|
2011-12-20 05:40:31 -05:00
|
|
|
|
first = false;
|
2011-06-04 10:28:06 -04:00
|
|
|
|
} else {
|
2011-12-20 05:40:31 -05:00
|
|
|
|
if (matrix) {
|
|
|
|
|
inX = coords[2];
|
|
|
|
|
inY = coords[3];
|
2011-06-04 10:28:06 -04:00
|
|
|
|
} else {
|
2011-12-20 05:40:31 -05:00
|
|
|
|
var handle = segment._handleIn;
|
2012-10-05 22:08:31 -04:00
|
|
|
|
inX = curX + handle._x;
|
|
|
|
|
inY = curY + handle._y;
|
2011-06-04 10:28:06 -04:00
|
|
|
|
}
|
2012-10-05 22:08:31 -04:00
|
|
|
|
if (inX == curX && inY == curY && outX == prevX && outY == prevY) {
|
|
|
|
|
ctx.lineTo(curX, curY);
|
2011-12-20 05:40:31 -05:00
|
|
|
|
} else {
|
2012-10-05 22:08:31 -04:00
|
|
|
|
ctx.bezierCurveTo(outX, outY, inX, inY, curX, curY);
|
2011-12-20 05:40:31 -05:00
|
|
|
|
}
|
|
|
|
|
}
|
2012-10-05 22:08:31 -04:00
|
|
|
|
prevX = curX;
|
|
|
|
|
prevY = curY;
|
2011-12-20 05:40:31 -05:00
|
|
|
|
if (matrix) {
|
|
|
|
|
outX = coords[4];
|
|
|
|
|
outY = coords[5];
|
|
|
|
|
} else {
|
|
|
|
|
var handle = segment._handleOut;
|
2012-10-05 22:08:31 -04:00
|
|
|
|
outX = prevX + handle._x;
|
|
|
|
|
outY = prevY + handle._y;
|
2011-06-04 10:28:06 -04:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2011-06-05 06:20:28 -04:00
|
|
|
|
for (var i = 0; i < length; i++)
|
2011-06-04 10:28:06 -04:00
|
|
|
|
drawSegment(i);
|
|
|
|
|
// Close path by drawing first segment again
|
2013-12-08 16:16:27 -05:00
|
|
|
|
if (path._closed && length > 0)
|
2011-06-04 10:28:06 -04:00
|
|
|
|
drawSegment(0);
|
|
|
|
|
}
|
|
|
|
|
|
2011-04-17 12:46:35 -04:00
|
|
|
|
return {
|
2013-04-18 19:58:35 -04:00
|
|
|
|
_draw: function(ctx, param) {
|
2014-03-17 09:51:47 -04:00
|
|
|
|
var dontStart = param.dontStart,
|
|
|
|
|
dontPaint = param.dontFinish || param.clip;
|
|
|
|
|
if (!dontStart)
|
2011-04-17 12:46:35 -04:00
|
|
|
|
ctx.beginPath();
|
2011-06-04 10:16:21 -04:00
|
|
|
|
|
2013-06-12 23:12:08 -04:00
|
|
|
|
var style = this.getStyle(),
|
2013-10-29 15:05:39 -04:00
|
|
|
|
hasFill = style.hasFill(),
|
|
|
|
|
hasStroke = style.hasStroke(),
|
2013-04-19 22:31:29 -04:00
|
|
|
|
dashArray = style.getDashArray(),
|
2013-10-08 04:50:59 -04:00
|
|
|
|
// dashLength is only set if we can't draw dashes natively
|
2013-10-29 15:05:39 -04:00
|
|
|
|
dashLength = !paper.support.nativeDash && hasStroke
|
2013-04-09 22:08:41 -04:00
|
|
|
|
&& dashArray && dashArray.length;
|
2011-12-19 16:40:14 -05:00
|
|
|
|
|
2013-10-08 04:50:59 -04:00
|
|
|
|
function getOffset(i) {
|
|
|
|
|
// Negative modulo is necessary since we're stepping back
|
|
|
|
|
// in the dash sequence first.
|
|
|
|
|
return dashArray[((i % dashLength) + dashLength) % dashLength];
|
|
|
|
|
}
|
|
|
|
|
|
2014-03-17 09:51:47 -04:00
|
|
|
|
if (!dontStart && this._currentPath) {
|
2013-12-06 06:49:46 -05:00
|
|
|
|
ctx.currentPath = this._currentPath;
|
2014-03-17 09:51:47 -04:00
|
|
|
|
} else if (hasFill || hasStroke && !dashLength || dontPaint) {
|
2013-12-06 06:49:46 -05:00
|
|
|
|
// Prepare the canvas path if we have any situation that
|
|
|
|
|
// requires it to be defined.
|
2013-11-06 06:53:10 -05:00
|
|
|
|
drawSegments(ctx, this);
|
2013-12-06 06:49:46 -05:00
|
|
|
|
if (this._closed)
|
|
|
|
|
ctx.closePath();
|
|
|
|
|
// CompoundPath collects its own _currentPath
|
2014-03-17 09:51:47 -04:00
|
|
|
|
if (!dontStart)
|
2013-12-06 06:49:46 -05:00
|
|
|
|
this._currentPath = ctx.currentPath;
|
|
|
|
|
}
|
2012-03-01 17:25:00 -05:00
|
|
|
|
|
2014-03-17 09:51:47 -04:00
|
|
|
|
if (!dontPaint && (hasFill || hasStroke)) {
|
2011-04-26 12:57:12 -04:00
|
|
|
|
// If the path is part of a compound path or doesn't have a fill
|
|
|
|
|
// or stroke, there is no need to continue.
|
2011-06-04 10:28:06 -04:00
|
|
|
|
this._setStyles(ctx);
|
2013-10-29 19:00:04 -04:00
|
|
|
|
if (hasFill) {
|
2013-10-18 08:54:13 -04:00
|
|
|
|
ctx.fill(style.getWindingRule());
|
2013-11-04 05:46:20 -05:00
|
|
|
|
// If shadowColor is defined, clear it after fill, so it
|
|
|
|
|
// won't be applied to both fill and stroke. If the path is
|
|
|
|
|
// only stroked, we don't have to clear it.
|
2013-10-29 19:00:04 -04:00
|
|
|
|
ctx.shadowColor = 'rgba(0,0,0,0)';
|
|
|
|
|
}
|
2013-10-29 15:05:39 -04:00
|
|
|
|
if (hasStroke) {
|
2013-10-08 04:50:59 -04:00
|
|
|
|
if (dashLength) {
|
2011-06-04 11:08:40 -04:00
|
|
|
|
// We cannot use the path created by drawSegments above
|
|
|
|
|
// Use CurveFlatteners to draw dashed paths:
|
2013-11-04 05:46:20 -05:00
|
|
|
|
// NOTE: We don't cache this path in another currentPath
|
|
|
|
|
// since browsers that support currentPath also support
|
|
|
|
|
// native dashes.
|
2014-03-17 09:51:47 -04:00
|
|
|
|
if (!dontStart)
|
|
|
|
|
ctx.beginPath();
|
2011-12-27 14:18:02 -05:00
|
|
|
|
var flattener = new PathFlattener(this),
|
2013-10-08 04:50:59 -04:00
|
|
|
|
length = flattener.length,
|
|
|
|
|
from = -style.getDashOffset(), to,
|
2011-12-27 14:18:02 -05:00
|
|
|
|
i = 0;
|
2013-10-08 04:50:59 -04:00
|
|
|
|
from = from % length;
|
|
|
|
|
// Step backwards in the dash sequence first until the
|
|
|
|
|
// from parameter is below 0.
|
|
|
|
|
while (from > 0) {
|
|
|
|
|
from -= getOffset(i--) + getOffset(i--);
|
|
|
|
|
}
|
|
|
|
|
while (from < length) {
|
|
|
|
|
to = from + getOffset(i++);
|
|
|
|
|
if (from > 0 || to > 0)
|
|
|
|
|
flattener.drawPart(ctx,
|
|
|
|
|
Math.max(from, 0), Math.max(to, 0));
|
|
|
|
|
from = to + getOffset(i++);
|
2011-12-27 14:18:02 -05:00
|
|
|
|
}
|
2011-06-04 11:08:40 -04:00
|
|
|
|
}
|
2011-06-04 10:28:06 -04:00
|
|
|
|
ctx.stroke();
|
2011-04-17 12:46:35 -04:00
|
|
|
|
}
|
2011-03-03 07:19:43 -05:00
|
|
|
|
}
|
2011-12-20 05:42:00 -05:00
|
|
|
|
},
|
|
|
|
|
|
2013-04-18 20:50:53 -04:00
|
|
|
|
_drawSelected: function(ctx, matrix) {
|
2011-12-20 05:42:00 -05:00
|
|
|
|
ctx.beginPath();
|
|
|
|
|
drawSegments(ctx, this, matrix);
|
|
|
|
|
// Now stroke it and draw its handles:
|
|
|
|
|
ctx.stroke();
|
2014-03-04 03:29:28 -05:00
|
|
|
|
drawHandles(ctx, this._segments, matrix, paper.settings.handleSize);
|
2011-03-03 07:19:43 -05:00
|
|
|
|
}
|
2011-04-17 12:46:35 -04:00
|
|
|
|
};
|
2011-12-19 15:23:28 -05:00
|
|
|
|
}, new function() { // Path Smoothing
|
2011-03-02 12:27:20 -05:00
|
|
|
|
|
2011-02-17 17:46:28 -05:00
|
|
|
|
/**
|
2011-02-17 07:36:40 -05:00
|
|
|
|
* Solves a tri-diagonal system for one of coordinates (x or y) of first
|
|
|
|
|
* bezier control points.
|
2011-06-30 06:01:51 -04:00
|
|
|
|
*
|
2011-02-17 07:36:40 -05:00
|
|
|
|
* @param rhs right hand side vector.
|
|
|
|
|
* @return Solution vector.
|
|
|
|
|
*/
|
2011-03-02 12:23:45 -05:00
|
|
|
|
function getFirstControlPoints(rhs) {
|
2011-04-21 15:01:31 -04:00
|
|
|
|
var n = rhs.length,
|
|
|
|
|
x = [], // Solution vector.
|
|
|
|
|
tmp = [], // Temporary workspace.
|
|
|
|
|
b = 2;
|
2011-02-17 07:36:40 -05:00
|
|
|
|
x[0] = rhs[0] / b;
|
|
|
|
|
// Decomposition and forward substitution.
|
|
|
|
|
for (var i = 1; i < n; i++) {
|
|
|
|
|
tmp[i] = 1 / b;
|
2011-04-28 08:13:33 -04:00
|
|
|
|
b = (i < n - 1 ? 4 : 2) - tmp[i];
|
2011-02-17 07:36:40 -05:00
|
|
|
|
x[i] = (rhs[i] - x[i - 1]) / b;
|
|
|
|
|
}
|
|
|
|
|
// Back-substitution.
|
|
|
|
|
for (var i = 1; i < n; i++) {
|
|
|
|
|
x[n - i - 1] -= tmp[n - i] * x[n - i];
|
|
|
|
|
}
|
|
|
|
|
return x;
|
2013-02-08 21:46:22 -05:00
|
|
|
|
}
|
2011-02-13 21:05:54 -05:00
|
|
|
|
|
2011-02-17 15:08:37 -05:00
|
|
|
|
return {
|
2011-06-16 17:07:00 -04:00
|
|
|
|
// Note: Documentation for smooth() is in PathItem
|
2011-02-17 07:36:40 -05:00
|
|
|
|
smooth: function() {
|
|
|
|
|
// This code is based on the work by Oleg V. Polikarpotchkin,
|
|
|
|
|
// http://ov-p.spaces.live.com/blog/cns!39D56F0C7A08D703!147.entry
|
|
|
|
|
// It was extended to support closed paths by averaging overlapping
|
|
|
|
|
// beginnings and ends. The result of this approach is very close to
|
|
|
|
|
// Polikarpotchkin's closed curve solution, but reuses the same
|
|
|
|
|
// algorithm as for open paths, and is probably executing faster as
|
|
|
|
|
// well, so it is preferred.
|
2011-04-21 15:01:31 -04:00
|
|
|
|
var segments = this._segments,
|
|
|
|
|
size = segments.length,
|
2013-11-26 11:32:52 -05:00
|
|
|
|
closed = this._closed,
|
2011-04-21 15:01:31 -04:00
|
|
|
|
n = size,
|
|
|
|
|
// Add overlapping ends for averaging handles in closed paths
|
2013-11-26 11:32:52 -05:00
|
|
|
|
overlap = 0;
|
2011-02-17 07:36:40 -05:00
|
|
|
|
if (size <= 2)
|
|
|
|
|
return;
|
2013-11-26 11:32:52 -05:00
|
|
|
|
if (closed) {
|
2011-02-17 07:36:40 -05:00
|
|
|
|
// Overlap up to 4 points since averaging beziers affect the 4
|
|
|
|
|
// neighboring points
|
|
|
|
|
overlap = Math.min(size, 4);
|
|
|
|
|
n += Math.min(size, overlap) * 2;
|
|
|
|
|
}
|
|
|
|
|
var knots = [];
|
|
|
|
|
for (var i = 0; i < size; i++)
|
2011-03-06 05:57:14 -05:00
|
|
|
|
knots[i + overlap] = segments[i]._point;
|
2013-11-26 11:32:52 -05:00
|
|
|
|
if (closed) {
|
2011-02-17 15:08:37 -05:00
|
|
|
|
// If we're averaging, add the 4 last points again at the
|
|
|
|
|
// beginning, and the 4 first ones at the end.
|
2011-02-17 07:36:40 -05:00
|
|
|
|
for (var i = 0; i < overlap; i++) {
|
2011-03-06 05:57:14 -05:00
|
|
|
|
knots[i] = segments[i + size - overlap]._point;
|
|
|
|
|
knots[i + size + overlap] = segments[i]._point;
|
2011-02-17 07:36:40 -05:00
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
n--;
|
|
|
|
|
}
|
|
|
|
|
// Calculate first Bezier control points
|
|
|
|
|
// Right hand side vector
|
|
|
|
|
var rhs = [];
|
|
|
|
|
|
|
|
|
|
// Set right hand side X values
|
|
|
|
|
for (var i = 1; i < n - 1; i++)
|
2011-04-21 09:25:25 -04:00
|
|
|
|
rhs[i] = 4 * knots[i]._x + 2 * knots[i + 1]._x;
|
|
|
|
|
rhs[0] = knots[0]._x + 2 * knots[1]._x;
|
|
|
|
|
rhs[n - 1] = 3 * knots[n - 1]._x;
|
2011-02-17 07:36:40 -05:00
|
|
|
|
// Get first control points X-values
|
|
|
|
|
var x = getFirstControlPoints(rhs);
|
|
|
|
|
|
|
|
|
|
// Set right hand side Y values
|
|
|
|
|
for (var i = 1; i < n - 1; i++)
|
2011-04-21 09:25:25 -04:00
|
|
|
|
rhs[i] = 4 * knots[i]._y + 2 * knots[i + 1]._y;
|
|
|
|
|
rhs[0] = knots[0]._y + 2 * knots[1]._y;
|
|
|
|
|
rhs[n - 1] = 3 * knots[n - 1]._y;
|
2011-02-17 07:36:40 -05:00
|
|
|
|
// Get first control points Y-values
|
|
|
|
|
var y = getFirstControlPoints(rhs);
|
|
|
|
|
|
2013-11-26 11:32:52 -05:00
|
|
|
|
if (closed) {
|
2011-02-17 07:36:40 -05:00
|
|
|
|
// Do the actual averaging simply by linearly fading between the
|
|
|
|
|
// overlapping values.
|
|
|
|
|
for (var i = 0, j = size; i < overlap; i++, j++) {
|
2012-11-06 23:18:59 -05:00
|
|
|
|
var f1 = i / overlap,
|
|
|
|
|
f2 = 1 - f1,
|
|
|
|
|
ie = i + overlap,
|
|
|
|
|
je = j + overlap;
|
2011-02-17 07:36:40 -05:00
|
|
|
|
// Beginning
|
|
|
|
|
x[j] = x[i] * f1 + x[j] * f2;
|
|
|
|
|
y[j] = y[i] * f1 + y[j] * f2;
|
|
|
|
|
// End
|
|
|
|
|
x[je] = x[ie] * f2 + x[je] * f1;
|
|
|
|
|
y[je] = y[ie] * f2 + y[je] * f1;
|
|
|
|
|
}
|
|
|
|
|
n--;
|
|
|
|
|
}
|
|
|
|
|
var handleIn = null;
|
|
|
|
|
// Now set the calculated handles
|
|
|
|
|
for (var i = overlap; i <= n - overlap; i++) {
|
|
|
|
|
var segment = segments[i - overlap];
|
2011-03-06 05:57:14 -05:00
|
|
|
|
if (handleIn)
|
|
|
|
|
segment.setHandleIn(handleIn.subtract(segment._point));
|
2011-02-17 07:36:40 -05:00
|
|
|
|
if (i < n) {
|
2011-03-06 05:57:14 -05:00
|
|
|
|
segment.setHandleOut(
|
2013-05-28 02:57:31 -04:00
|
|
|
|
new Point(x[i], y[i]).subtract(segment._point));
|
2013-11-26 11:32:52 -05:00
|
|
|
|
handleIn = i < n - 1
|
|
|
|
|
? new Point(
|
2011-04-21 09:25:25 -04:00
|
|
|
|
2 * knots[i + 1]._x - x[i + 1],
|
2013-11-26 11:32:52 -05:00
|
|
|
|
2 * knots[i + 1]._y - y[i + 1])
|
|
|
|
|
: new Point(
|
2011-04-21 09:25:25 -04:00
|
|
|
|
(knots[n]._x + x[n - 1]) / 2,
|
|
|
|
|
(knots[n]._y + y[n - 1]) / 2);
|
2011-02-17 07:36:40 -05:00
|
|
|
|
}
|
|
|
|
|
}
|
2013-11-26 11:32:52 -05:00
|
|
|
|
if (closed && handleIn) {
|
2011-02-17 07:36:40 -05:00
|
|
|
|
var segment = this._segments[0];
|
2011-03-06 05:57:14 -05:00
|
|
|
|
segment.setHandleIn(handleIn.subtract(segment._point));
|
2011-02-17 07:36:40 -05:00
|
|
|
|
}
|
2011-02-13 21:05:54 -05:00
|
|
|
|
}
|
2011-04-11 17:30:08 -04:00
|
|
|
|
};
|
2011-03-06 16:13:55 -05:00
|
|
|
|
}, new function() { // PostScript-style drawing commands
|
2011-06-16 17:07:00 -04:00
|
|
|
|
/**
|
|
|
|
|
* Helper method that returns the current segment and checks if a moveTo()
|
|
|
|
|
* command is required first.
|
|
|
|
|
*/
|
2011-03-06 10:21:12 -05:00
|
|
|
|
function getCurrentSegment(that) {
|
|
|
|
|
var segments = that._segments;
|
2014-03-04 03:48:41 -05:00
|
|
|
|
if (segments.length === 0)
|
2011-06-17 06:26:35 -04:00
|
|
|
|
throw new Error('Use a moveTo() command first');
|
2011-03-06 10:21:12 -05:00
|
|
|
|
return segments[segments.length - 1];
|
|
|
|
|
}
|
2011-03-06 10:15:13 -05:00
|
|
|
|
|
2011-03-06 10:21:12 -05:00
|
|
|
|
return {
|
2011-06-16 17:07:00 -04:00
|
|
|
|
// 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.
|
2013-06-24 13:15:54 -04:00
|
|
|
|
moveTo: function(/* point */) {
|
2012-11-03 22:45:02 -04:00
|
|
|
|
// moveTo should only be called at the beginning of paths. But it
|
|
|
|
|
// can ce called again if there is nothing drawn yet, in which case
|
|
|
|
|
// the first segment gets readjusted.
|
2014-03-03 11:27:47 -05:00
|
|
|
|
var segments = this._segments;
|
|
|
|
|
if (segments.length === 1)
|
2012-11-03 22:45:02 -04:00
|
|
|
|
this.removeSegment(0);
|
2011-05-03 04:12:07 -04:00
|
|
|
|
// Let's not be picky about calling moveTo() when not at the
|
|
|
|
|
// beginning of a path, just bail out:
|
2014-03-03 11:27:47 -05:00
|
|
|
|
if (!segments.length)
|
2011-05-05 19:18:56 -04:00
|
|
|
|
this._add([ new Segment(Point.read(arguments)) ]);
|
2011-03-06 10:21:12 -05:00
|
|
|
|
},
|
2011-03-06 10:15:13 -05:00
|
|
|
|
|
2013-06-24 13:15:54 -04:00
|
|
|
|
moveBy: function(/* point */) {
|
2011-06-16 17:07:00 -04:00
|
|
|
|
throw new Error('moveBy() is unsupported on Path items.');
|
|
|
|
|
},
|
|
|
|
|
|
2013-06-24 13:15:54 -04:00
|
|
|
|
lineTo: function(/* point */) {
|
2011-05-03 04:12:07 -04:00
|
|
|
|
// Let's not be picky about calling moveTo() first:
|
2011-05-05 19:18:56 -04:00
|
|
|
|
this._add([ new Segment(Point.read(arguments)) ]);
|
2011-03-06 10:21:12 -05:00
|
|
|
|
},
|
2011-03-06 10:15:13 -05:00
|
|
|
|
|
2013-06-24 13:15:54 -04:00
|
|
|
|
cubicCurveTo: function(/* handle1, handle2, to */) {
|
|
|
|
|
var handle1 = Point.read(arguments),
|
|
|
|
|
handle2 = Point.read(arguments),
|
2013-10-29 20:43:55 -04:00
|
|
|
|
to = Point.read(arguments),
|
|
|
|
|
// First modify the current segment:
|
|
|
|
|
current = getCurrentSegment(this);
|
2011-03-06 10:21:12 -05:00
|
|
|
|
// Convert to relative values:
|
2013-06-24 13:15:54 -04:00
|
|
|
|
current.setHandleOut(handle1.subtract(current._point));
|
2011-03-06 10:21:12 -05:00
|
|
|
|
// And add the new segment, with handleIn set to c2
|
2013-06-24 13:15:54 -04:00
|
|
|
|
this._add([ new Segment(to, handle2.subtract(to)) ]);
|
2011-03-06 10:21:12 -05:00
|
|
|
|
},
|
2011-03-06 10:15:13 -05:00
|
|
|
|
|
2013-06-24 13:15:54 -04:00
|
|
|
|
quadraticCurveTo: function(/* handle, to */) {
|
|
|
|
|
var handle = Point.read(arguments),
|
2013-10-29 20:43:55 -04:00
|
|
|
|
to = Point.read(arguments),
|
|
|
|
|
current = getCurrentSegment(this)._point;
|
2011-03-06 10:21:12 -05:00
|
|
|
|
// This is exact:
|
|
|
|
|
// If we have the three quad points: A E D,
|
|
|
|
|
// and the cubic is A B C D,
|
|
|
|
|
// B = E + 1/3 (A - E)
|
|
|
|
|
// C = E + 1/3 (D - E)
|
|
|
|
|
this.cubicCurveTo(
|
2013-06-24 13:15:54 -04:00
|
|
|
|
handle.add(current.subtract(handle).multiply(1 / 3)),
|
|
|
|
|
handle.add(to.subtract(handle).multiply(1 / 3)),
|
2012-10-18 17:29:53 -04:00
|
|
|
|
to
|
2011-03-06 10:21:12 -05:00
|
|
|
|
);
|
|
|
|
|
},
|
2011-03-06 10:15:13 -05:00
|
|
|
|
|
2013-06-24 13:15:54 -04:00
|
|
|
|
curveTo: function(/* through, to, parameter */) {
|
|
|
|
|
var through = Point.read(arguments),
|
|
|
|
|
to = Point.read(arguments),
|
2012-12-30 10:07:20 -05:00
|
|
|
|
t = Base.pick(Base.read(arguments), 0.5),
|
2011-04-28 08:12:21 -04:00
|
|
|
|
t1 = 1 - t,
|
|
|
|
|
current = getCurrentSegment(this)._point,
|
|
|
|
|
// handle = (through - (1 - t)^2 * current - t^2 * to) /
|
|
|
|
|
// (2 * (1 - t) * t)
|
2013-06-24 13:15:54 -04:00
|
|
|
|
handle = through.subtract(current.multiply(t1 * t1))
|
|
|
|
|
.subtract(to.multiply(t * t)).divide(2 * t * t1);
|
2011-03-06 10:21:12 -05:00
|
|
|
|
if (handle.isNaN())
|
|
|
|
|
throw new Error(
|
2011-06-17 06:26:35 -04:00
|
|
|
|
'Cannot put a curve through points with parameter = ' + t);
|
2013-06-24 13:15:54 -04:00
|
|
|
|
this.quadraticCurveTo(handle, to);
|
2011-03-06 10:21:12 -05:00
|
|
|
|
},
|
2011-03-06 10:15:13 -05:00
|
|
|
|
|
2014-03-12 18:00:47 -04:00
|
|
|
|
arcTo: function(/* to, clockwise | through, to
|
2014-03-13 11:35:30 -04:00
|
|
|
|
| to, radius, rotation, clockwise, large */) {
|
2011-03-06 10:21:12 -05:00
|
|
|
|
// Get the start point:
|
2011-04-28 08:12:21 -04:00
|
|
|
|
var current = getCurrentSegment(this),
|
2011-06-14 04:00:55 -04:00
|
|
|
|
from = current._point,
|
2013-11-24 19:04:51 -05:00
|
|
|
|
to = Point.read(arguments),
|
2014-03-12 18:00:47 -04:00
|
|
|
|
through,
|
|
|
|
|
// Peek at next value to see if it's clockwise, with true as the
|
|
|
|
|
// default value.
|
|
|
|
|
peek = Base.peek(arguments),
|
|
|
|
|
clockwise = Base.pick(peek, true),
|
|
|
|
|
center, extent, vector, matrix;
|
|
|
|
|
// We're handling three different approaches to drawing arcs in one
|
|
|
|
|
// large function:
|
2013-11-24 19:04:51 -05:00
|
|
|
|
if (typeof clockwise === 'boolean') {
|
2014-03-12 18:00:47 -04:00
|
|
|
|
// #1: arcTo(to, clockwise)
|
2011-06-14 04:00:55 -04:00
|
|
|
|
var middle = from.add(to).divide(2),
|
2011-06-16 18:48:46 -04:00
|
|
|
|
through = middle.add(middle.subtract(from).rotate(
|
|
|
|
|
clockwise ? -90 : 90));
|
2014-03-12 18:00:47 -04:00
|
|
|
|
} else if (Base.remain(arguments) <= 2) {
|
|
|
|
|
// #2: arcTo(through, to)
|
2013-11-24 19:04:51 -05:00
|
|
|
|
through = to;
|
2012-10-18 17:24:15 -04:00
|
|
|
|
to = Point.read(arguments);
|
2014-03-12 18:00:47 -04:00
|
|
|
|
} else {
|
2014-03-13 11:35:30 -04:00
|
|
|
|
// #3: arcTo(to, radius, rotation, clockwise, large)
|
2014-03-12 18:00:47 -04:00
|
|
|
|
// Drawing arcs in SVG style:
|
|
|
|
|
var radius = Size.read(arguments);
|
|
|
|
|
// If rx = 0 or ry = 0 then this arc is treated as a
|
|
|
|
|
// straight line joining the endpoints.
|
|
|
|
|
if (radius.isZero())
|
|
|
|
|
return this.lineTo(to);
|
|
|
|
|
// See for an explanation of the following calculations:
|
|
|
|
|
// http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes
|
2014-03-12 18:05:54 -04:00
|
|
|
|
var rotation = Base.read(arguments),
|
2014-03-13 11:35:30 -04:00
|
|
|
|
clockwise = !!Base.read(arguments),
|
2014-03-12 18:00:47 -04:00
|
|
|
|
large = !!Base.read(arguments),
|
|
|
|
|
middle = from.add(to).divide(2),
|
2014-03-12 18:05:54 -04:00
|
|
|
|
pt = from.subtract(middle).rotate(-rotation),
|
2014-03-12 18:00:47 -04:00
|
|
|
|
x = pt.x,
|
|
|
|
|
y = pt.y,
|
|
|
|
|
abs = Math.abs,
|
|
|
|
|
EPSILON = /*#=*/ Numerical.EPSILON,
|
|
|
|
|
rx = abs(radius.width),
|
|
|
|
|
ry = abs(radius.height),
|
|
|
|
|
rxSq = rx * rx,
|
|
|
|
|
rySq = ry * ry,
|
|
|
|
|
xSq = x * x,
|
|
|
|
|
ySq = y * y;
|
|
|
|
|
// "...ensure radii are large enough"
|
|
|
|
|
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)
|
|
|
|
|
// "...where the + sign is chosen if fA != fS,
|
|
|
|
|
// and the − sign is chosen if fA = fS."
|
2014-03-13 11:35:30 -04:00
|
|
|
|
.multiply((large === clockwise ? -1 : 1)
|
|
|
|
|
* Math.sqrt(factor))
|
2014-03-12 18:05:54 -04:00
|
|
|
|
.rotate(rotation).add(middle);
|
2014-03-12 18:00:47 -04:00
|
|
|
|
// Now create a matrix that maps the unit circle to the ellipse,
|
|
|
|
|
// for easier construction below.
|
2014-03-12 18:05:54 -04:00
|
|
|
|
matrix = new Matrix().translate(center).rotate(rotation)
|
2014-03-12 18:00:47 -04:00
|
|
|
|
.scale(rx, ry);
|
|
|
|
|
// Transform from and to to the unit circle coordinate space
|
|
|
|
|
// and calculcate start vector and extend from there.
|
|
|
|
|
vector = matrix._inverseTransform(from);
|
|
|
|
|
extent = vector.getDirectedAngle(matrix._inverseTransform(to));
|
2014-03-13 11:35:30 -04:00
|
|
|
|
// "...if fS = 0 and extent is > 0, then subtract 360, whereas
|
|
|
|
|
// if fS = 1 and extend is < 0, then add 360."
|
|
|
|
|
if (!clockwise && extent > 0)
|
2014-03-12 18:00:47 -04:00
|
|
|
|
extent -= 360;
|
2014-03-13 11:35:30 -04:00
|
|
|
|
else if (clockwise && extent < 0)
|
2014-03-12 18:00:47 -04:00
|
|
|
|
extent += 360;
|
2011-03-06 10:15:13 -05:00
|
|
|
|
}
|
2014-03-12 18:00:47 -04:00
|
|
|
|
if (through) {
|
|
|
|
|
// Calculate center, vector and extend for non SVG versions:
|
|
|
|
|
// Construct the two perpendicular middle lines to
|
|
|
|
|
// (from, through) and (through, to), and intersect them to get
|
|
|
|
|
// the center.
|
|
|
|
|
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);
|
2011-06-14 07:45:37 -04:00
|
|
|
|
// If the two lines are colinear, there cannot be an arc as the
|
2011-06-16 17:38:43 -04:00
|
|
|
|
// circle is infinitely big and has no center point. If side is
|
|
|
|
|
// 0, the connecting arc line of this huge circle is a line
|
|
|
|
|
// between the two points, so we can use #lineTo instead.
|
|
|
|
|
// Otherwise we bail out:
|
2014-03-12 18:00:47 -04:00
|
|
|
|
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) {
|
|
|
|
|
// If the center is lying on the line, we might have gotten
|
|
|
|
|
// the wrong sign for extent above. Use the sign of the side
|
|
|
|
|
// of the through point.
|
|
|
|
|
extent = throughSide * Math.abs(extent);
|
|
|
|
|
} else if (throughSide === centerSide) {
|
|
|
|
|
// If the center is on the same side of the line (from, to)
|
|
|
|
|
// as the through point, we're extending bellow 180 degrees
|
|
|
|
|
// and need to adapt extent.
|
2014-03-13 11:35:30 -04:00
|
|
|
|
extent += extent < 0 ? 360 : -360;
|
2014-03-12 18:00:47 -04:00
|
|
|
|
}
|
2011-06-16 18:50:14 -04:00
|
|
|
|
}
|
2011-03-06 10:21:12 -05:00
|
|
|
|
var ext = Math.abs(extent),
|
2011-06-14 07:19:14 -04:00
|
|
|
|
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)),
|
2011-05-05 20:26:23 -04:00
|
|
|
|
segments = [];
|
2011-06-14 07:19:14 -04:00
|
|
|
|
for (var i = 0; i <= count; i++) {
|
2014-04-02 14:53:18 -04:00
|
|
|
|
// Explicitly use to point for last segment, since depending
|
2011-06-14 04:12:18 -04:00
|
|
|
|
// on values the calculation adds imprecision:
|
2014-03-12 18:00:47 -04:00
|
|
|
|
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) {
|
2011-03-06 10:21:12 -05:00
|
|
|
|
// Modify startSegment
|
|
|
|
|
current.setHandleOut(out);
|
|
|
|
|
} else {
|
|
|
|
|
// Add new Segment
|
2014-03-12 18:00:47 -04:00
|
|
|
|
var _in = vector.rotate(-90).multiply(z);
|
|
|
|
|
if (matrix) {
|
|
|
|
|
_in = matrix._transformPoint(vector.add(_in))
|
|
|
|
|
.subtract(pt);
|
|
|
|
|
}
|
|
|
|
|
segments.push(new Segment(pt, _in, out));
|
2011-03-06 10:21:12 -05:00
|
|
|
|
}
|
2011-06-14 07:19:14 -04:00
|
|
|
|
vector = vector.rotate(inc);
|
2011-03-06 10:21:12 -05:00
|
|
|
|
}
|
2011-05-04 13:42:40 -04:00
|
|
|
|
// Add all segments at once at the end for higher performance
|
2011-05-05 19:18:56 -04:00
|
|
|
|
this._add(segments);
|
2011-03-06 10:21:12 -05:00
|
|
|
|
},
|
2011-03-06 10:15:13 -05:00
|
|
|
|
|
2013-10-29 20:43:55 -04:00
|
|
|
|
lineBy: function(/* to */) {
|
|
|
|
|
var to = Point.read(arguments),
|
|
|
|
|
current = getCurrentSegment(this)._point;
|
|
|
|
|
this.lineTo(current.add(to));
|
2011-03-06 10:21:12 -05:00
|
|
|
|
},
|
2011-03-06 10:15:13 -05:00
|
|
|
|
|
2013-10-29 20:43:55 -04:00
|
|
|
|
curveBy: function(/* through, to, parameter */) {
|
|
|
|
|
var through = Point.read(arguments),
|
|
|
|
|
to = Point.read(arguments),
|
|
|
|
|
parameter = Base.read(arguments),
|
|
|
|
|
current = getCurrentSegment(this)._point;
|
|
|
|
|
this.curveTo(current.add(through), current.add(to), parameter);
|
2011-03-06 10:21:12 -05:00
|
|
|
|
},
|
2011-03-06 10:15:13 -05:00
|
|
|
|
|
2013-10-29 20:43:55 -04:00
|
|
|
|
cubicCurveBy: function(/* handle1, handle2, to */) {
|
|
|
|
|
var handle1 = Point.read(arguments),
|
|
|
|
|
handle2 = Point.read(arguments),
|
|
|
|
|
to = Point.read(arguments),
|
|
|
|
|
current = getCurrentSegment(this)._point;
|
|
|
|
|
this.cubicCurveTo(current.add(handle1), current.add(handle2),
|
|
|
|
|
current.add(to));
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
quadraticCurveBy: function(/* handle, to */) {
|
|
|
|
|
var handle = Point.read(arguments),
|
|
|
|
|
to = Point.read(arguments),
|
|
|
|
|
current = getCurrentSegment(this)._point;
|
|
|
|
|
this.quadraticCurveTo(current.add(handle), current.add(to));
|
|
|
|
|
},
|
|
|
|
|
|
2014-03-13 12:46:31 -04:00
|
|
|
|
// TODO: Implement version for: (to, radius, rotation, clockwise, large)
|
2013-11-24 19:04:51 -05:00
|
|
|
|
arcBy: function(/* to, clockwise | through, to */) {
|
|
|
|
|
var current = getCurrentSegment(this)._point,
|
|
|
|
|
point = current.add(Point.read(arguments)),
|
|
|
|
|
// Peek at next value to see if it's clockwise, with true as
|
|
|
|
|
// default value.
|
|
|
|
|
clockwise = Base.pick(Base.peek(arguments), true);
|
|
|
|
|
if (typeof clockwise === 'boolean') {
|
|
|
|
|
this.arcTo(point, clockwise);
|
|
|
|
|
} else {
|
|
|
|
|
this.arcTo(point, current.add(Point.read(arguments)));
|
|
|
|
|
}
|
2011-03-06 10:21:12 -05:00
|
|
|
|
},
|
|
|
|
|
|
2014-03-31 13:33:38 -04:00
|
|
|
|
closePath: function(join) {
|
2011-04-30 18:22:29 -04:00
|
|
|
|
this.setClosed(true);
|
2014-03-31 13:33:38 -04:00
|
|
|
|
if (join)
|
|
|
|
|
this.join();
|
2011-03-06 10:21:12 -05:00
|
|
|
|
}
|
|
|
|
|
};
|
2012-12-15 11:27:37 -05:00
|
|
|
|
}, { // A dedicated scope for the tricky bounds calculations
|
|
|
|
|
// We define all the different getBounds functions as static methods on Path
|
|
|
|
|
// and have #_getBounds directly access these. All static bounds functions
|
|
|
|
|
// below have the same first four parameters: segments, closed, style,
|
|
|
|
|
// matrix, so they can be called from #_getBounds() and also be used in
|
|
|
|
|
// Curve. But not all of them use all these parameters, and some define
|
|
|
|
|
// additional ones after.
|
|
|
|
|
|
|
|
|
|
_getBounds: function(getter, matrix) {
|
|
|
|
|
// See #draw() for an explanation of why we can access _style
|
|
|
|
|
// properties directly here:
|
2013-06-12 23:12:08 -04:00
|
|
|
|
return Path[getter](this._segments, this._closed, this.getStyle(),
|
|
|
|
|
matrix);
|
2012-12-15 11:27:37 -05:00
|
|
|
|
},
|
|
|
|
|
|
2013-10-16 09:03:29 -04:00
|
|
|
|
// Mess with indentation in order to get more line-space below:
|
2012-12-15 11:27:37 -05:00
|
|
|
|
statics: {
|
2013-04-24 22:27:31 -04:00
|
|
|
|
/**
|
2013-08-08 18:21:35 -04:00
|
|
|
|
* Determines whether the segments describe a path in clockwise or counter-
|
2013-04-24 22:27:31 -04:00
|
|
|
|
* clockwise orientation.
|
|
|
|
|
*
|
|
|
|
|
* @private
|
|
|
|
|
*/
|
|
|
|
|
isClockwise: function(segments) {
|
2013-10-18 08:22:59 -04:00
|
|
|
|
var sum = 0;
|
2013-10-18 13:51:54 -04:00
|
|
|
|
// Method derived from:
|
|
|
|
|
// http://stackoverflow.com/questions/1165647
|
|
|
|
|
// We treat the curve points and handles as the outline of a polygon of
|
|
|
|
|
// which we determine the orientation using the method of calculating
|
|
|
|
|
// the sum over the edges. This will work even with non-convex polygons,
|
|
|
|
|
// telling you whether it's mostly clockwise
|
2013-04-24 22:27:31 -04:00
|
|
|
|
// TODO: Check if this works correctly for all open paths.
|
2013-10-18 13:51:54 -04:00
|
|
|
|
for (var i = 0, l = segments.length; i < l; i++) {
|
|
|
|
|
var v = Curve.getValues(
|
|
|
|
|
segments[i], segments[i + 1 < l ? i + 1 : 0]);
|
|
|
|
|
for (var j = 2; j < 8; j += 2)
|
|
|
|
|
sum += (v[j - 2] - v[j]) * (v[j + 1] + v[j - 1]);
|
|
|
|
|
}
|
2013-04-24 22:27:31 -04:00
|
|
|
|
return sum > 0;
|
|
|
|
|
},
|
|
|
|
|
|
2011-11-24 09:18:57 -05:00
|
|
|
|
/**
|
|
|
|
|
* Returns the bounding rectangle of the item excluding stroke width.
|
2013-04-24 22:27:31 -04:00
|
|
|
|
*
|
|
|
|
|
* @private
|
2011-11-24 09:18:57 -05:00
|
|
|
|
*/
|
2012-12-15 11:27:37 -05:00
|
|
|
|
getBounds: function(segments, closed, style, matrix, strokePadding) {
|
2012-12-15 11:19:10 -05:00
|
|
|
|
var first = segments[0];
|
2012-12-09 21:00:23 -05:00
|
|
|
|
// If there are no segments, return "empty" rectangle, just like groups,
|
|
|
|
|
// since #bounds is assumed to never return null.
|
2011-11-24 09:18:57 -05:00
|
|
|
|
if (!first)
|
2012-12-09 21:00:23 -05:00
|
|
|
|
return new Rectangle();
|
2011-11-24 09:18:57 -05:00
|
|
|
|
var coords = new Array(6),
|
2012-12-27 11:43:21 -05:00
|
|
|
|
// Make coordinates for first segment available in prevCoords.
|
|
|
|
|
prevCoords = first._transformCoordinates(matrix, new Array(6), false),
|
2012-12-30 10:07:20 -05:00
|
|
|
|
min = prevCoords.slice(0, 2), // Start with values of first point
|
2013-04-06 12:39:17 -04:00
|
|
|
|
max = min.slice(), // clone
|
2012-12-27 12:38:55 -05:00
|
|
|
|
roots = new Array(2);
|
2012-12-27 11:43:21 -05:00
|
|
|
|
|
2011-11-24 09:18:57 -05:00
|
|
|
|
function processSegment(segment) {
|
|
|
|
|
segment._transformCoordinates(matrix, coords, false);
|
|
|
|
|
for (var i = 0; i < 2; i++) {
|
2012-12-27 12:38:55 -05:00
|
|
|
|
Curve._addBounds(
|
|
|
|
|
prevCoords[i], // prev.point
|
|
|
|
|
prevCoords[i + 4], // prev.handleOut
|
|
|
|
|
coords[i + 2], // segment.handleIn
|
|
|
|
|
coords[i], // segment.point,
|
|
|
|
|
i, strokePadding ? strokePadding[i] : 0, min, max, roots);
|
2011-11-24 09:18:57 -05:00
|
|
|
|
}
|
2012-12-15 10:58:20 -05:00
|
|
|
|
// Swap coordinate buffers.
|
2011-11-24 09:18:57 -05:00
|
|
|
|
var tmp = prevCoords;
|
|
|
|
|
prevCoords = coords;
|
|
|
|
|
coords = tmp;
|
|
|
|
|
}
|
2012-12-27 11:43:21 -05:00
|
|
|
|
|
2011-11-24 09:18:57 -05:00
|
|
|
|
for (var i = 1, l = segments.length; i < l; i++)
|
|
|
|
|
processSegment(segments[i]);
|
2012-12-15 11:19:10 -05:00
|
|
|
|
if (closed)
|
2011-11-24 09:18:57 -05:00
|
|
|
|
processSegment(first);
|
2013-05-28 02:57:31 -04:00
|
|
|
|
return new Rectangle(min[0], min[1], max[0] - min[0], max[1] - min[1]);
|
2012-12-15 11:27:37 -05:00
|
|
|
|
},
|
2011-03-06 19:36:44 -05:00
|
|
|
|
|
2011-11-24 09:18:57 -05:00
|
|
|
|
/**
|
|
|
|
|
* Returns the bounding rectangle of the item including stroke width.
|
2013-04-24 22:27:31 -04:00
|
|
|
|
*
|
|
|
|
|
* @private
|
2011-11-24 09:18:57 -05:00
|
|
|
|
*/
|
2012-12-15 11:27:37 -05:00
|
|
|
|
getStrokeBounds: function(segments, closed, style, matrix) {
|
2011-11-24 09:18:57 -05:00
|
|
|
|
// TODO: Find a way to reuse 'bounds' cache instead?
|
2013-10-29 15:05:39 -04:00
|
|
|
|
if (!style.hasStroke())
|
2012-12-15 11:27:37 -05:00
|
|
|
|
return Path.getBounds(segments, closed, style, matrix);
|
2013-04-24 22:27:31 -04:00
|
|
|
|
var length = segments.length - (closed ? 0 : 1),
|
|
|
|
|
radius = style.getStrokeWidth() / 2,
|
2013-12-10 05:43:05 -05:00
|
|
|
|
padding = Path._getPenPadding(radius, matrix),
|
2012-12-15 11:27:37 -05:00
|
|
|
|
bounds = Path.getBounds(segments, closed, style, matrix, padding),
|
2013-04-19 22:31:29 -04:00
|
|
|
|
join = style.getStrokeJoin(),
|
|
|
|
|
cap = style.getStrokeCap(),
|
2013-06-15 08:08:12 -04:00
|
|
|
|
miterLimit = radius * style.getMiterLimit();
|
2011-11-24 09:18:57 -05:00
|
|
|
|
// Create a rectangle of padding size, used for union with bounds
|
|
|
|
|
// further down
|
|
|
|
|
var joinBounds = new Rectangle(new Size(padding).multiply(2));
|
|
|
|
|
|
|
|
|
|
function add(point) {
|
|
|
|
|
bounds = bounds.include(matrix
|
2011-12-26 05:06:36 -05:00
|
|
|
|
? matrix._transformPoint(point, point) : point);
|
2011-11-24 09:18:57 -05:00
|
|
|
|
}
|
2011-11-24 09:13:21 -05:00
|
|
|
|
|
2014-02-28 12:01:50 -05:00
|
|
|
|
function addRound(segment) {
|
|
|
|
|
bounds = bounds.unite(joinBounds.setCenter(matrix
|
|
|
|
|
? matrix._transformPoint(segment._point) : segment._point));
|
|
|
|
|
}
|
|
|
|
|
|
2011-11-24 09:18:57 -05:00
|
|
|
|
function addJoin(segment, join) {
|
2014-02-28 12:01:50 -05:00
|
|
|
|
// When both handles are set in a segment and they are collinear,
|
|
|
|
|
// the join setting is ignored and round is always used.
|
|
|
|
|
var handleIn = segment._handleIn,
|
|
|
|
|
handleOut = segment._handleOut
|
|
|
|
|
if (join === 'round' || !handleIn.isZero() && !handleOut.isZero()
|
|
|
|
|
&& handleIn.isColinear(handleOut)) {
|
|
|
|
|
addRound(segment);
|
2013-06-15 01:57:14 -04:00
|
|
|
|
} else {
|
2014-02-28 12:10:58 -05:00
|
|
|
|
Path._addBevelJoin(segment, join, radius, miterLimit, add);
|
2011-03-06 19:36:44 -05:00
|
|
|
|
}
|
2011-11-24 09:18:57 -05:00
|
|
|
|
}
|
2011-03-06 19:36:44 -05:00
|
|
|
|
|
2013-06-15 08:06:09 -04:00
|
|
|
|
function addCap(segment, cap) {
|
2013-12-07 10:11:02 -05:00
|
|
|
|
if (cap === 'round') {
|
2014-02-28 12:01:50 -05:00
|
|
|
|
addRound(segment);
|
2013-12-07 10:11:02 -05:00
|
|
|
|
} else {
|
2013-06-15 08:06:09 -04:00
|
|
|
|
Path._addSquareCap(segment, cap, radius, add);
|
2011-03-06 19:36:44 -05:00
|
|
|
|
}
|
2011-11-24 09:18:57 -05:00
|
|
|
|
}
|
2011-03-06 19:36:44 -05:00
|
|
|
|
|
2013-04-24 22:27:31 -04:00
|
|
|
|
for (var i = 1; i < length; i++)
|
2011-11-24 09:18:57 -05:00
|
|
|
|
addJoin(segments[i], join);
|
2012-12-15 11:19:10 -05:00
|
|
|
|
if (closed) {
|
2011-11-24 09:18:57 -05:00
|
|
|
|
addJoin(segments[0], join);
|
2013-12-07 10:07:51 -05:00
|
|
|
|
} else if (length > 0) {
|
2013-06-15 08:06:09 -04:00
|
|
|
|
addCap(segments[0], cap);
|
|
|
|
|
addCap(segments[segments.length - 1], cap);
|
2011-11-24 09:18:57 -05:00
|
|
|
|
}
|
|
|
|
|
return bounds;
|
2012-12-15 11:27:37 -05:00
|
|
|
|
},
|
2011-03-06 19:36:44 -05:00
|
|
|
|
|
2013-12-10 05:43:05 -05:00
|
|
|
|
/**
|
|
|
|
|
* Returns the horizontal and vertical padding that a transformed round
|
|
|
|
|
* stroke adds to the bounding box, by calculating the dimensions of a
|
|
|
|
|
* rotated ellipse.
|
|
|
|
|
*/
|
|
|
|
|
_getPenPadding: function(radius, matrix) {
|
|
|
|
|
if (!matrix)
|
|
|
|
|
return [radius, radius];
|
|
|
|
|
// If a matrix is provided, we need to rotate the stroke circle
|
|
|
|
|
// and calculate the bounding box of the resulting rotated elipse:
|
|
|
|
|
// Get rotated hor and ver vectors, and determine rotation angle
|
|
|
|
|
// and elipse values from them:
|
|
|
|
|
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();
|
|
|
|
|
// Formula for rotated ellipses:
|
|
|
|
|
// x = cx + a*cos(t)*cos(phi) - b*sin(t)*sin(phi)
|
|
|
|
|
// y = cy + b*sin(t)*cos(phi) + a*cos(t)*sin(phi)
|
|
|
|
|
// Derivates (by Wolfram Alpha):
|
|
|
|
|
// derivative of x = cx + a*cos(t)*cos(phi) - b*sin(t)*sin(phi)
|
|
|
|
|
// dx/dt = a sin(t) cos(phi) + b cos(t) sin(phi) = 0
|
|
|
|
|
// derivative of y = cy + b*sin(t)*cos(phi) + a*cos(t)*sin(phi)
|
|
|
|
|
// dy/dt = b cos(t) cos(phi) - a sin(t) sin(phi) = 0
|
|
|
|
|
// This can be simplified to:
|
|
|
|
|
// tan(t) = -b * tan(phi) / a // x
|
|
|
|
|
// tan(t) = b * cot(phi) / a // y
|
|
|
|
|
// Solving for t gives:
|
|
|
|
|
// t = pi * n - arctan(b * tan(phi) / a) // x
|
|
|
|
|
// t = pi * n + arctan(b * cot(phi) / a)
|
|
|
|
|
// = pi * n + arctan(b / tan(phi) / a) // y
|
|
|
|
|
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));
|
|
|
|
|
// Due to symetry, we don't need to cycle through pi * n solutions:
|
|
|
|
|
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)];
|
|
|
|
|
},
|
|
|
|
|
|
2014-02-28 12:10:58 -05:00
|
|
|
|
_addBevelJoin: function(segment, join, radius, miterLimit, addPoint, area) {
|
|
|
|
|
// Handles both 'bevel' and 'miter' joins, as they share a lot of code.
|
2013-06-15 02:11:50 -04:00
|
|
|
|
var curve2 = segment.getCurve(),
|
|
|
|
|
curve1 = curve2.getPrevious(),
|
|
|
|
|
point = curve2.getPointAt(0, true),
|
|
|
|
|
normal1 = curve1.getNormalAt(1, true),
|
|
|
|
|
normal2 = curve2.getNormalAt(0, true),
|
2013-06-15 02:13:54 -04:00
|
|
|
|
step = normal1.getDirectedAngle(normal2) < 0 ? -radius : radius;
|
2013-06-15 02:11:50 -04:00
|
|
|
|
normal1.setLength(step);
|
|
|
|
|
normal2.setLength(step);
|
2013-06-15 05:24:59 -04:00
|
|
|
|
if (area) {
|
|
|
|
|
addPoint(point);
|
|
|
|
|
addPoint(point.add(normal1));
|
2013-06-15 02:11:50 -04:00
|
|
|
|
}
|
|
|
|
|
if (join === 'miter') {
|
|
|
|
|
// Intersect the two lines
|
|
|
|
|
var corner = new Line(
|
|
|
|
|
point.add(normal1),
|
|
|
|
|
new Point(-normal1.y, normal1.x), true
|
|
|
|
|
).intersect(new Line(
|
|
|
|
|
point.add(normal2),
|
|
|
|
|
new Point(-normal2.y, normal2.x), true
|
|
|
|
|
), true);
|
2013-06-15 02:13:54 -04:00
|
|
|
|
// See if we actually get a bevel point and if its distance is below
|
|
|
|
|
// the miterLimit. If not, make a normal bevel.
|
2013-06-15 08:08:12 -04:00
|
|
|
|
if (corner && point.getDistance(corner) <= miterLimit) {
|
2013-06-15 05:24:59 -04:00
|
|
|
|
addPoint(corner);
|
|
|
|
|
if (!area)
|
2013-06-15 02:11:50 -04:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// Produce a normal bevel
|
2013-06-15 05:24:59 -04:00
|
|
|
|
if (!area)
|
|
|
|
|
addPoint(point.add(normal1));
|
|
|
|
|
addPoint(point.add(normal2));
|
2013-06-15 02:11:50 -04:00
|
|
|
|
},
|
|
|
|
|
|
2013-06-15 08:06:09 -04:00
|
|
|
|
_addSquareCap: function(segment, cap, radius, addPoint, area) {
|
2014-02-28 12:10:58 -05:00
|
|
|
|
// Handles both 'square' and 'butt' caps, as they share a lot of code.
|
2013-06-15 02:11:50 -04:00
|
|
|
|
// Calculate the corner points of butt and square caps
|
2013-06-15 08:06:09 -04:00
|
|
|
|
var point = segment._point,
|
|
|
|
|
loc = segment.getLocation(),
|
2013-12-07 10:07:51 -05:00
|
|
|
|
normal = loc.getNormal().normalize(radius);
|
2013-06-15 05:24:59 -04:00
|
|
|
|
if (area) {
|
|
|
|
|
addPoint(point.subtract(normal));
|
|
|
|
|
addPoint(point.add(normal));
|
2013-06-15 02:11:50 -04:00
|
|
|
|
}
|
2013-06-15 02:13:54 -04:00
|
|
|
|
// For square caps, we need to step away from point in the direction of
|
2013-06-15 08:06:09 -04:00
|
|
|
|
// the tangent, which is the rotated normal.
|
2013-08-08 18:21:35 -04:00
|
|
|
|
// Checking loc.getParameter() for 0 is to see whether this is the first
|
2013-06-17 18:52:16 -04:00
|
|
|
|
// or the last segment of the open path, in order to determine in which
|
|
|
|
|
// direction to move the point.
|
2013-06-15 02:11:50 -04:00
|
|
|
|
if (cap === 'square')
|
2013-06-15 08:06:09 -04:00
|
|
|
|
point = point.add(normal.rotate(loc.getParameter() == 0 ? -90 : 90));
|
2013-06-15 05:24:59 -04:00
|
|
|
|
addPoint(point.add(normal));
|
|
|
|
|
addPoint(point.subtract(normal));
|
2013-06-15 02:11:50 -04:00
|
|
|
|
},
|
|
|
|
|
|
2011-11-24 09:18:57 -05:00
|
|
|
|
/**
|
|
|
|
|
* Returns the bounding rectangle of the item including handles.
|
2013-04-24 22:27:31 -04:00
|
|
|
|
*
|
|
|
|
|
* @private
|
2011-11-24 09:18:57 -05:00
|
|
|
|
*/
|
2012-12-15 11:27:37 -05:00
|
|
|
|
getHandleBounds: function(segments, closed, style, matrix, strokePadding,
|
|
|
|
|
joinPadding) {
|
2011-11-24 09:18:57 -05:00
|
|
|
|
var coords = new Array(6),
|
|
|
|
|
x1 = Infinity,
|
|
|
|
|
x2 = -x1,
|
|
|
|
|
y1 = x1,
|
|
|
|
|
y2 = x2;
|
2012-12-15 11:19:10 -05:00
|
|
|
|
for (var i = 0, l = segments.length; i < l; i++) {
|
|
|
|
|
var segment = segments[i];
|
2011-11-24 09:18:57 -05:00
|
|
|
|
segment._transformCoordinates(matrix, coords, false);
|
|
|
|
|
for (var j = 0; j < 6; j += 2) {
|
|
|
|
|
// Use different padding for points or handles
|
2012-12-15 00:40:25 -05:00
|
|
|
|
var padding = j == 0 ? joinPadding : strokePadding,
|
2013-12-10 05:43:05 -05:00
|
|
|
|
paddingX = padding ? padding[0] : 0,
|
|
|
|
|
paddingY = padding ? padding[1] : 0,
|
2011-11-24 09:18:57 -05:00
|
|
|
|
x = coords[j],
|
|
|
|
|
y = coords[j + 1],
|
2013-12-10 05:43:05 -05:00
|
|
|
|
xn = x - paddingX,
|
|
|
|
|
xx = x + paddingX,
|
|
|
|
|
yn = y - paddingY,
|
|
|
|
|
yx = y + paddingY;
|
2011-11-24 09:18:57 -05:00
|
|
|
|
if (xn < x1) x1 = xn;
|
|
|
|
|
if (xx > x2) x2 = xx;
|
|
|
|
|
if (yn < y1) y1 = yn;
|
|
|
|
|
if (yx > y2) y2 = yx;
|
2011-03-06 19:36:44 -05:00
|
|
|
|
}
|
2011-11-24 09:18:57 -05:00
|
|
|
|
}
|
2013-05-28 02:57:31 -04:00
|
|
|
|
return new Rectangle(x1, y1, x2 - x1, y2 - y1);
|
2012-12-15 11:27:37 -05:00
|
|
|
|
},
|
2011-03-06 19:36:44 -05:00
|
|
|
|
|
2011-11-24 09:18:57 -05:00
|
|
|
|
/**
|
2012-12-18 08:19:22 -05:00
|
|
|
|
* Returns the rough bounding rectangle of the item that is sure to include
|
2011-11-24 09:18:57 -05:00
|
|
|
|
* all of the drawing, including stroke width.
|
2013-04-24 22:27:31 -04:00
|
|
|
|
*
|
|
|
|
|
* @private
|
2011-11-24 09:18:57 -05:00
|
|
|
|
*/
|
2012-12-15 11:27:37 -05:00
|
|
|
|
getRoughBounds: function(segments, closed, style, matrix) {
|
2011-11-24 09:18:57 -05:00
|
|
|
|
// Delegate to handleBounds, but pass on radius values for stroke and
|
|
|
|
|
// joins. Hanlde miter joins specially, by passing the largets radius
|
|
|
|
|
// possible.
|
2013-12-10 05:43:05 -05:00
|
|
|
|
var strokeRadius = style.hasStroke() ? style.getStrokeWidth() / 2 : 0,
|
|
|
|
|
joinRadius = strokeRadius;
|
|
|
|
|
if (strokeRadius > 0) {
|
2013-06-15 08:21:17 -04:00
|
|
|
|
if (style.getStrokeJoin() === 'miter')
|
2013-12-10 05:43:05 -05:00
|
|
|
|
joinRadius = strokeRadius * style.getMiterLimit();
|
2013-06-15 08:21:17 -04:00
|
|
|
|
if (style.getStrokeCap() === 'square')
|
2013-12-10 05:43:05 -05:00
|
|
|
|
joinRadius = Math.max(joinRadius, strokeRadius * Math.sqrt(2));
|
2013-06-15 08:21:17 -04:00
|
|
|
|
}
|
2012-12-15 11:27:37 -05:00
|
|
|
|
return Path.getHandleBounds(segments, closed, style, matrix,
|
2013-12-10 05:43:05 -05:00
|
|
|
|
Path._getPenPadding(strokeRadius, matrix),
|
|
|
|
|
Path._getPenPadding(joinRadius, matrix));
|
2011-11-24 09:18:57 -05:00
|
|
|
|
}
|
2012-12-15 11:27:37 -05:00
|
|
|
|
}});
|