2011-03-06 19:50:44 -05:00
|
|
|
/*
|
|
|
|
* Paper.js
|
2011-06-30 06:01:51 -04:00
|
|
|
*
|
2011-03-06 19:50:44 -05:00
|
|
|
* This file is part of Paper.js, a JavaScript Vector Graphics Library,
|
|
|
|
* based on Scriptographer.org and designed to be largely API compatible.
|
2011-03-07 20:41:50 -05:00
|
|
|
* http://paperjs.org/
|
2011-03-06 19:50:44 -05:00
|
|
|
* http://scriptographer.org/
|
2011-06-30 06:01:51 -04:00
|
|
|
*
|
2011-03-06 19:50:44 -05:00
|
|
|
* Copyright (c) 2011, Juerg Lehni & Jonathan Puckey
|
|
|
|
* http://lehni.org/ & 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
|
|
|
*
|
2011-06-22 18:56:05 -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
|
|
|
|
*/
|
|
|
|
var Path = this.Path = PathItem.extend(/** @lends Path# */{
|
2011-05-22 18:26:08 -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
|
|
|
*
|
2011-06-19 16:52:52 -04:00
|
|
|
* @param {Segment[]} [segments] An array of segments (or points to be
|
2011-05-30 13:42:17 -04:00
|
|
|
* converted to segments) that will be added to the 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';
|
|
|
|
*/
|
2011-03-08 08:03:57 -05:00
|
|
|
initialize: function(segments) {
|
2011-02-17 15:08:37 -05:00
|
|
|
this.base();
|
2011-04-30 18:22:29 -04:00
|
|
|
this._closed = false;
|
2011-06-14 10:37:25 -04:00
|
|
|
this._selectedSegmentState = 0;
|
2011-02-17 15:08:37 -05:00
|
|
|
// Support both passing of segments as array or arguments
|
|
|
|
// If it is an array, it can also be a description of a point, so
|
|
|
|
// check its first entry for object as well
|
2011-03-08 08:03:11 -05:00
|
|
|
this.setSegments(!segments || !Array.isArray(segments)
|
2011-04-28 08:23:17 -04:00
|
|
|
|| typeof segments[0] !== 'object' ? arguments : segments);
|
2011-02-17 15:08:37 -05:00
|
|
|
},
|
|
|
|
|
2011-05-19 16:34:19 -04:00
|
|
|
clone: function() {
|
2011-05-20 03:54:44 -04:00
|
|
|
var copy = this._clone(new Path(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;
|
2011-05-19 16:34:19 -04:00
|
|
|
return copy;
|
|
|
|
},
|
|
|
|
|
2011-05-07 08:39:17 -04:00
|
|
|
_changed: function(flags) {
|
2011-06-19 17:40:49 -04:00
|
|
|
// Don't use base() for reasons of performance.
|
|
|
|
Item.prototype._changed.call(this, flags);
|
2011-06-19 17:21:14 -04:00
|
|
|
if (flags & ChangeFlag.GEOMETRY) {
|
2011-05-07 08:39:17 -04:00
|
|
|
delete this._strokeBounds;
|
2011-06-19 13:07:53 -04:00
|
|
|
delete this._length;
|
2011-05-15 12:59:06 -04:00
|
|
|
// Clockwise state becomes undefined as soon as geometry changes.
|
|
|
|
delete this._clockwise;
|
2011-06-19 17:21:14 -04:00
|
|
|
} else if (flags & ChangeFlag.STROKE) {
|
2011-05-07 08:39:17 -04:00
|
|
|
delete this._strokeBounds;
|
|
|
|
}
|
2011-05-01 19:17:21 -04:00
|
|
|
},
|
|
|
|
|
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) {
|
|
|
|
if (!this._segments) {
|
|
|
|
this._segments = [];
|
|
|
|
} else {
|
2011-06-20 14:08:34 -04:00
|
|
|
this._selectedSegmentState = 0;
|
2011-04-30 18:24:39 -04:00
|
|
|
this._segments.length = 0;
|
2011-04-30 18:44:37 -04:00
|
|
|
// Make sure new curves are calculated next time we call getCurves()
|
|
|
|
if (this._curves)
|
2011-06-05 14:27:18 -04:00
|
|
|
delete this._curves;
|
2011-04-30 18:24:39 -04:00
|
|
|
}
|
2011-05-05 19:18:56 -04:00
|
|
|
this._add(Segment.readAll(segments));
|
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
|
|
|
*/
|
|
|
|
getCurves: function() {
|
2011-04-30 18:44:37 -04:00
|
|
|
if (!this._curves) {
|
2011-05-02 19:25:23 -04:00
|
|
|
var segments = this._segments,
|
|
|
|
length = segments.length;
|
2011-04-30 18:44:37 -04:00
|
|
|
// Reduce length by one if it's an open path:
|
|
|
|
if (!this._closed && length > 0)
|
|
|
|
length--;
|
|
|
|
this._curves = new Array(length);
|
|
|
|
for (var i = 0; i < length; i++)
|
2011-05-02 19:25:23 -04:00
|
|
|
this._curves[i] = Curve.create(this, segments[i],
|
|
|
|
// Use first segment for segment2 of closing curve
|
|
|
|
segments[i + 1] || segments[0]);
|
2011-03-06 07:24:15 -05:00
|
|
|
}
|
2011-04-30 18:44:37 -04:00
|
|
|
return this._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
|
|
|
*/
|
2011-04-30 18:22:29 -04:00
|
|
|
getClosed: function() {
|
|
|
|
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) {
|
|
|
|
var length = this._segments.length,
|
|
|
|
i;
|
|
|
|
// Reduce length by one if it's an open path:
|
|
|
|
if (!closed && length > 0)
|
|
|
|
length--;
|
|
|
|
this._curves.length = length;
|
|
|
|
// If we were closing this path, we need to add a new curve now
|
|
|
|
if (closed)
|
2011-05-02 19:25:23 -04:00
|
|
|
this._curves[i = length - 1] = Curve.create(this,
|
|
|
|
this._segments[i], this._segments[0]);
|
2011-04-30 18:44:37 -04:00
|
|
|
}
|
2011-06-19 17:20:28 -04:00
|
|
|
this._changed(Change.GEOMETRY);
|
2011-04-30 18:44:37 -04:00
|
|
|
}
|
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.
|
|
|
|
|
2011-03-03 07:47:55 -05:00
|
|
|
_transform: function(matrix, flags) {
|
2011-03-06 16:26:38 -05:00
|
|
|
if (!matrix.isIdentity()) {
|
|
|
|
var coords = new Array(6);
|
|
|
|
for (var i = 0, l = this._segments.length; i < l; i++) {
|
2011-05-06 08:40:43 -04:00
|
|
|
this._segments[i]._transformCoordinates(matrix, coords, true);
|
2011-02-20 05:20:23 -05:00
|
|
|
}
|
2011-05-16 09:11:13 -04:00
|
|
|
var fillColor = this.getFillColor(),
|
|
|
|
strokeColor = this.getStrokeColor();
|
|
|
|
if (fillColor && fillColor.transform)
|
|
|
|
fillColor.transform(matrix);
|
|
|
|
if (strokeColor && strokeColor.transform)
|
|
|
|
strokeColor.transform(matrix);
|
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,
|
2011-06-14 10:40:03 -04:00
|
|
|
index = append ? segments.length : index,
|
|
|
|
fullySelected = this.isFullySelected();
|
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:
|
2011-05-07 10:38:36 -04:00
|
|
|
if (segment._path) {
|
2011-05-04 13:42:40 -04:00
|
|
|
segment = segs[i] = new Segment(segment);
|
2011-05-07 10:38:36 -04:00
|
|
|
}
|
2011-05-04 13:42:40 -04:00
|
|
|
segment._path = this;
|
|
|
|
segment._index = index + i;
|
2011-06-14 10:40:03 -04:00
|
|
|
// Select newly added segments if path was fully selected before
|
|
|
|
if (fullySelected)
|
|
|
|
segment._selectionState = SelectionState.POINT;
|
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.
|
2011-05-07 10:38:36 -04: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-07 10:38:36 -04:00
|
|
|
}
|
2011-05-01 08:27:53 -04:00
|
|
|
}
|
|
|
|
// Keep the curves list in sync all the time in case it as requested
|
|
|
|
// already. We need to step one index down from the inserted segment to
|
|
|
|
// get its curve:
|
2011-05-02 19:25:23 -04:00
|
|
|
if (curves && --index >= 0) {
|
2011-05-01 08:27:53 -04:00
|
|
|
// Insert a new curve as well and update the curves above
|
2011-05-02 19:25:23 -04:00
|
|
|
curves.splice(index, 0, Curve.create(this, segments[index],
|
2011-05-04 13:42:40 -04:00
|
|
|
segments[index + 1]));
|
2011-05-02 19:25:23 -04:00
|
|
|
// Adjust segment1 now for the curves above the inserted one
|
2011-05-04 13:42:40 -04:00
|
|
|
var curve = curves[index + amount];
|
2011-05-07 10:38:36 -04:00
|
|
|
if (curve) {
|
2011-05-04 13:42:40 -04:00
|
|
|
curve._segment1 = segments[index + amount];
|
2011-05-07 10:38:36 -04:00
|
|
|
}
|
2011-03-03 08:10:17 -05:00
|
|
|
}
|
2011-06-19 17:20:28 -04:00
|
|
|
this._changed(Change.GEOMETRY);
|
2011-05-04 13:42:40 -04:00
|
|
|
return segs;
|
2011-02-17 15:08:37 -05:00
|
|
|
},
|
|
|
|
|
2011-05-29 14:20:10 -04:00
|
|
|
// PORT: Add support for adding multiple segments at once to Scriptographer
|
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-05-26 14:09:25 -04:00
|
|
|
* @operator none
|
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:
|
|
|
|
* var path = new Path();
|
|
|
|
* path.strokeColor = 'black';
|
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:
|
|
|
|
* var path = new Path();
|
|
|
|
* path.strokeColor = 'black';
|
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:
|
|
|
|
* var path = new Path();
|
|
|
|
* path.strokeColor = 'black';
|
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:
|
|
|
|
* var path = new Path();
|
|
|
|
* path.strokeColor = 'black';
|
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-29 14:20:10 -04:00
|
|
|
// PORT: Add support for adding multiple segments at once to Scriptographer
|
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
|
|
|
},
|
|
|
|
|
2011-05-29 14:20:10 -04:00
|
|
|
// PORT: Add to Scriptographer
|
2011-05-05 07:35:14 -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
|
|
|
},
|
|
|
|
|
2011-05-29 14:20:10 -04:00
|
|
|
// PORT: Add to Scriptographer
|
2011-05-05 07:35:14 -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 14:20:10 -04:00
|
|
|
// PORT: Add to Scriptographer
|
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:
|
2011-05-29 10:06:23 -04:00
|
|
|
* var path = new Path();
|
|
|
|
* path.strokeColor = 'black';
|
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:
|
|
|
|
* var path = new Path();
|
|
|
|
* path.strokeColor = 'black';
|
|
|
|
* 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
|
|
|
*
|
2011-06-05 15:28:18 -04:00
|
|
|
* var path = new Path();
|
|
|
|
* path.strokeColor = 'black';
|
|
|
|
* 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 14:20:10 -04:00
|
|
|
// PORT: Add to Scriptographer
|
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-29 14:20:10 -04:00
|
|
|
// PORT: Add to Scriptographer
|
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:
|
|
|
|
* var path = new Path.Circle(new Point(80, 50), 35);
|
|
|
|
* path.strokeColor = 'black';
|
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) {
|
2011-05-01 08:27:53 -04:00
|
|
|
var segments = this.removeSegments(index, index + 1);
|
2011-05-04 13:42:40 -04:00
|
|
|
return segments[0] || null;
|
2011-04-28 10:42:16 -04:00
|
|
|
},
|
2011-06-13 14:05:17 -04:00
|
|
|
|
2011-05-29 14:20:10 -04:00
|
|
|
// PORT: Add to Scriptographer
|
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
|
|
|
|
* @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:
|
|
|
|
* var path = new Path.Circle(new Point(80, 50), 35);
|
|
|
|
* path.strokeColor = 'black';
|
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
|
|
|
*/
|
2011-04-28 10:42:16 -04:00
|
|
|
removeSegments: function(from, to) {
|
2011-05-01 08:27:53 -04:00
|
|
|
from = from || 0;
|
2011-06-13 14:00:39 -04:00
|
|
|
to = Base.pick(to, this._segments.length);
|
2011-05-02 19:25:23 -04:00
|
|
|
var segments = this._segments,
|
|
|
|
curves = this._curves,
|
|
|
|
last = to >= segments.length,
|
|
|
|
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
|
|
|
|
removed._index = removed._path = undefined;
|
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) {
|
|
|
|
curves.splice(from, amount);
|
|
|
|
// Adjust segments for the curves before and after the removed ones
|
|
|
|
var curve;
|
|
|
|
if (curve = curves[from - 1])
|
|
|
|
curve._segment2 = segments[from];
|
|
|
|
if (curve = curves[from])
|
|
|
|
curve._segment1 = segments[from];
|
|
|
|
// If the last segment of a closing path was removed, we need to
|
|
|
|
// readjust the last curve of the list now.
|
|
|
|
if (last && this._closed && (curve = curves[curves.length - 1]))
|
|
|
|
curve._segment2 = segments[0];
|
|
|
|
}
|
2011-06-19 17:20:28 -04:00
|
|
|
this._changed(Change.GEOMETRY);
|
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
|
|
|
|
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:
|
|
|
|
* var path = new Path.Circle(new Size(80, 50), 35);
|
|
|
|
* 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:
|
|
|
|
* var path = new Path.Circle(new Size(80, 50), 35);
|
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) {
|
|
|
|
* path.fillColor = 'red';
|
|
|
|
* }
|
2011-06-30 06:01:51 -04:00
|
|
|
*
|
2011-06-05 15:28:18 -04:00
|
|
|
*/
|
2011-05-22 18:26:08 -04:00
|
|
|
/**
|
2011-06-14 10:37:25 -04:00
|
|
|
* Specifies whether the path and all its segments are selected.
|
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:
|
|
|
|
* var path = new Path.Circle(new Size(80, 50), 35);
|
2011-06-14 10:37:25 -04:00
|
|
|
* path.fullySelected = true;
|
2011-06-30 06:01:51 -04:00
|
|
|
*
|
2011-06-05 15:28:18 -04:00
|
|
|
* var path2 = new Path.Circle(new Size(180, 50), 35);
|
2011-06-14 10:37:25 -04:00
|
|
|
* path2.fullySelected = true;
|
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) {
|
|
|
|
* path.fillColor = 'red';
|
|
|
|
* }
|
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) {
|
|
|
|
* path2.fillColor = 'red';
|
|
|
|
* }
|
2011-05-22 18:26:08 -04:00
|
|
|
*/
|
2011-04-21 12:06:06 -04:00
|
|
|
isFullySelected: function() {
|
2011-06-14 10:37:25 -04:00
|
|
|
return this._selected && this._selectedSegmentState
|
|
|
|
== this._segments.length * SelectionState.POINT;
|
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) {
|
2011-06-14 10:37:25 -04:00
|
|
|
var length = this._segments.length;
|
|
|
|
this._selectedSegmentState = selected
|
|
|
|
? length * SelectionState.POINT : 0;
|
|
|
|
for (var i = 0; i < length; i++)
|
|
|
|
this._segments[i]._selectionState = selected
|
|
|
|
? SelectionState.POINT : 0;
|
2011-04-21 12:06:06 -04:00
|
|
|
this.setSelected(selected);
|
|
|
|
},
|
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;
|
|
|
|
// 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:
|
|
|
|
* var path = new Path.Circle(new Size(80, 50), 35);
|
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-30 06:01:51 -04:00
|
|
|
*
|
2011-06-05 15:28:18 -04:00
|
|
|
* // Select the copy, so we can inspect its segments:
|
|
|
|
* copy.selected = true;
|
|
|
|
*/
|
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),
|
|
|
|
// Add half of step to end, so imprecisions are ok too.
|
|
|
|
end = flattener.length + 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;
|
|
|
|
}
|
|
|
|
this.setSegments(segments);
|
|
|
|
},
|
|
|
|
|
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) {
|
|
|
|
* // If we already made a path before, deselect it:
|
|
|
|
* if (path) {
|
|
|
|
* path.selected = false;
|
|
|
|
* }
|
2011-06-30 06:01:51 -04:00
|
|
|
*
|
2011-06-09 18:07:25 -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();
|
|
|
|
* path.strokeColor = 'black';
|
|
|
|
* path.add(event.point);
|
|
|
|
* path.selected = true;
|
|
|
|
* }
|
2011-06-30 06:01:51 -04:00
|
|
|
*
|
2011-06-09 18:07:25 -04:00
|
|
|
* function onMouseDrag(event) {
|
|
|
|
* // On every drag event, add a segment to the path
|
|
|
|
* // at the position of the mouse:
|
|
|
|
* path.add(event.point);
|
|
|
|
* }
|
2011-06-30 06:01:51 -04:00
|
|
|
*
|
2011-06-09 18:07:25 -04:00
|
|
|
* function onMouseUp(event) {
|
2011-06-20 13:17:07 -04:00
|
|
|
* // When the mouse is released, simplify the path:
|
|
|
|
* path.simplify();
|
2011-06-09 18:07:25 -04:00
|
|
|
* path.selected = true;
|
|
|
|
* }
|
|
|
|
*/
|
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])
|
2011-04-27 16:23:57 -04:00
|
|
|
// TODO: split(offset) / split(location) / split(index[, parameter])
|
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;
|
|
|
|
var sum = 0,
|
|
|
|
xPre, yPre;
|
|
|
|
function edge(x, y) {
|
|
|
|
if (xPre !== undefined)
|
|
|
|
sum += (xPre - x) * (y + yPre);
|
|
|
|
xPre = x;
|
|
|
|
yPre = y;
|
|
|
|
}
|
|
|
|
// 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
|
|
|
|
for (var i = 0, l = this._segments.length; i < l; i++) {
|
|
|
|
var seg1 = this._segments[i],
|
|
|
|
seg2 = this._segments[i + 1 < l ? i + 1 : 0],
|
|
|
|
point1 = seg1._point,
|
|
|
|
handle1 = seg1._handleOut,
|
|
|
|
handle2 = seg2._handleIn,
|
|
|
|
point2 = seg2._point;
|
|
|
|
edge(point1._x, point1._y);
|
|
|
|
edge(point1._x + handle1._x, point1._y + handle1._y);
|
|
|
|
edge(point2._x + handle2._x, point2._y + handle2._y);
|
|
|
|
edge(point2._x, point2._y);
|
|
|
|
}
|
2011-05-15 14:02:50 -04:00
|
|
|
return sum > 0;
|
2011-05-15 12:59:06 -04:00
|
|
|
},
|
|
|
|
|
|
|
|
setClockwise: function(clockwise) {
|
|
|
|
// On-the-fly conversion to boolean:
|
|
|
|
if (this.isClockwise() != (clockwise = !!clockwise)) {
|
|
|
|
// Only revers the path if its clockwise orientation is not the same
|
|
|
|
// as what it is now demanded to be.
|
|
|
|
this.reverse();
|
2011-05-15 14:02:50 -04:00
|
|
|
this._clockwise = clockwise;
|
2011-05-15 12:59:06 -04:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2011-04-11 17:30:08 -04:00
|
|
|
/**
|
|
|
|
* Reverses the segments of the path.
|
|
|
|
*/
|
|
|
|
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;
|
|
|
|
}
|
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
|
|
|
|
2011-05-22 18:26:08 -04:00
|
|
|
// DOCS: document Path#join in more detail.
|
|
|
|
/**
|
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:
|
|
|
|
* var path = new Path([30, 25], [30, 75]);
|
|
|
|
* path.strokeColor = 'black';
|
2011-06-30 06:01:51 -04:00
|
|
|
*
|
2011-06-05 10:11:13 -04:00
|
|
|
* var path2 = new Path([200, 25], [200, 75]);
|
|
|
|
* path2.strokeColor = 'black';
|
2011-06-30 06:01:51 -04: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:
|
|
|
|
* var path = new Path([30, 25], [30, 75]);
|
|
|
|
* path.strokeColor = 'black';
|
2011-06-30 06:01:51 -04:00
|
|
|
*
|
2011-06-05 10:11:13 -04:00
|
|
|
* var path2 = new Path([30, 25], [80, 25]);
|
|
|
|
* path2.strokeColor = 'black';
|
2011-06-30 06:01:51 -04: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
|
|
|
* // After joining, path with have 3 segments, since it
|
|
|
|
* // shared its first segment point with the first
|
|
|
|
* // segment point of path2.
|
2011-06-30 06:01:51 -04: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:
|
|
|
|
* var path = new Path([30, 25], [80, 25], [80, 75]);
|
|
|
|
* path.strokeColor = 'black';
|
2011-06-30 06:01:51 -04:00
|
|
|
*
|
2011-06-05 10:11:13 -04:00
|
|
|
* var path2 = new Path([30, 25], [30, 75], [80, 75]);
|
|
|
|
* path2.strokeColor = 'black';
|
2011-06-30 06:01:51 -04: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
|
|
|
* // Because the paths were joined at two points, the path is closed
|
|
|
|
* // and has 4 segments.
|
2011-06-30 06:01:51 -04: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();
|
|
|
|
var 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 {
|
|
|
|
var 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 {
|
2011-05-04 13:42:40 -04:00
|
|
|
this._add(segments.slice(0));
|
2011-04-11 17:30:08 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
path.remove();
|
|
|
|
// Close if they touch in both places
|
|
|
|
var first1 = this.getFirstSegment();
|
|
|
|
last1 = this.getLastSegment();
|
2011-04-21 12:43:22 -04:00
|
|
|
if (last1._point.equals(first1._point)) {
|
|
|
|
first1.setHandleIn(last1._handleIn);
|
2011-04-11 17:30:08 -04:00
|
|
|
last1.remove();
|
2011-04-30 18:22:29 -04:00
|
|
|
this.setClosed(true);
|
2011-04-11 17:30:08 -04:00
|
|
|
}
|
2011-06-19 17:20:28 -04:00
|
|
|
this._changed(Change.GEOMETRY);
|
2011-04-11 17:30:08 -04:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
},
|
2011-04-27 16:23:57 -04:00
|
|
|
|
2011-05-22 18:26:08 -04:00
|
|
|
/**
|
|
|
|
* The length of the perimeter of the path.
|
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
|
|
|
},
|
|
|
|
|
|
|
|
_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();
|
|
|
|
var curve = curves[index];
|
|
|
|
return offset + curve.getLength(0, location.getParameter());
|
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
|
|
|
|
2011-04-27 16:40:52 -04:00
|
|
|
// TODO: getLocationAt(point, precision)
|
2011-05-29 14:20:10 -04:00
|
|
|
// PORT: Rename functions and add new isParameter argument in Scriptographer
|
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()
|
|
|
|
return new CurveLocation(curves[index], offset - index);
|
|
|
|
}
|
|
|
|
for (var i = 0, l = curves.length; i < l; i++) {
|
|
|
|
var start = length,
|
|
|
|
curve = curves[i];
|
|
|
|
length += curve.getLength();
|
|
|
|
if (length >= offset) {
|
|
|
|
// Found the segment within which the length lies
|
|
|
|
return new CurveLocation(curve,
|
|
|
|
curve.getParameter(offset - start));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// It may be that through impreciseness of getLength, that the end
|
|
|
|
// of the curves was missed:
|
|
|
|
if (offset <= this.getLength())
|
|
|
|
return new CurveLocation(curves[curves.length - 1], 1);
|
|
|
|
return null;
|
|
|
|
},
|
|
|
|
|
2011-05-22 18:26:08 -04:00
|
|
|
// DOCS: improve Path#getPointAt documenation.
|
2011-04-27 14:28:39 -04:00
|
|
|
/**
|
2011-06-05 09:00:43 -04:00
|
|
|
* Get 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:
|
|
|
|
* var path = new Path();
|
|
|
|
* path.strokeColor = 'black';
|
|
|
|
* 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
|
|
|
* // Create a small circle shaped path at the point:
|
|
|
|
* var circle = new Path.Circle(point, 3);
|
|
|
|
* circle.fillColor = 'red';
|
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:
|
|
|
|
* var path = new Path();
|
|
|
|
* path.strokeColor = 'black';
|
|
|
|
* 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++) {
|
|
|
|
* var offset = i / amount * length;
|
2011-06-30 06:01:51 -04:00
|
|
|
*
|
2011-06-05 09:00:43 -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
|
|
|
*
|
2011-06-05 09:00:43 -04:00
|
|
|
* // Create a small circle shaped path at the point:
|
|
|
|
* var circle = new Path.Circle(point, 3);
|
|
|
|
* circle.fillColor = 'red';
|
|
|
|
* }
|
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
|
|
|
/**
|
2011-05-22 18:26:08 -04:00
|
|
|
* Get the tangent to the path at the given offset as a vector
|
2011-04-11 17:30:08 -04:00
|
|
|
* 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:
|
|
|
|
* var path = new Path();
|
|
|
|
* path.strokeColor = 'black';
|
|
|
|
* 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
|
|
|
*
|
2011-06-05 09:00:43 -04:00
|
|
|
* var path = new Path();
|
|
|
|
* path.strokeColor = 'red';
|
|
|
|
* path.add(point);
|
|
|
|
* path.add(point + tangent);
|
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:
|
|
|
|
* var path = new Path();
|
|
|
|
* path.strokeColor = 'black';
|
|
|
|
* 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++) {
|
|
|
|
* var offset = i / amount * length;
|
2011-06-30 06:01:51 -04:00
|
|
|
*
|
2011-06-05 09:00:43 -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
|
|
|
*
|
2011-06-05 09:00:43 -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
|
|
|
*
|
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
|
|
|
*
|
2011-06-05 09:00:43 -04:00
|
|
|
* var line = new Path();
|
|
|
|
* line.strokeColor = 'red';
|
|
|
|
* line.add(point);
|
|
|
|
* line.add(point + tangent);
|
|
|
|
* }
|
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
|
|
|
/**
|
2011-05-22 18:26:08 -04:00
|
|
|
* Get 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:
|
|
|
|
* var path = new Path();
|
|
|
|
* path.strokeColor = 'black';
|
|
|
|
* 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
|
|
|
*
|
2011-06-05 09:00:43 -04:00
|
|
|
* var path = new Path();
|
|
|
|
* path.strokeColor = 'red';
|
|
|
|
* path.add(point);
|
|
|
|
* path.add(point + normal);
|
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:
|
|
|
|
* var path = new Path();
|
|
|
|
* path.strokeColor = 'black';
|
|
|
|
* 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++) {
|
|
|
|
* var offset = i / amount * length;
|
2011-06-30 06:01:51 -04:00
|
|
|
*
|
2011-06-05 09:00:43 -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
|
|
|
*
|
2011-06-05 09:00:43 -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
|
|
|
*
|
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
|
|
|
*
|
2011-06-05 09:00:43 -04:00
|
|
|
* var line = new Path();
|
|
|
|
* line.strokeColor = 'red';
|
|
|
|
* line.add(point);
|
|
|
|
* line.add(point + normal);
|
|
|
|
* }
|
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-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
|
|
|
|
// of getter functions all the time we directly access these private
|
|
|
|
// properties here. The distinction between normal Point objects and
|
|
|
|
// SegmentPoint objects maybe seem a bit tedious but is worth the
|
|
|
|
// performance benefit.
|
|
|
|
|
2011-04-17 12:46:35 -04:00
|
|
|
function drawHandles(ctx, segments) {
|
|
|
|
for (var i = 0, l = segments.length; i < l; i++) {
|
2011-03-06 05:14:12 -05:00
|
|
|
var segment = segments[i],
|
2011-04-21 12:06:06 -04:00
|
|
|
point = segment._point,
|
2011-06-14 10:37:25 -04:00
|
|
|
state = segment._selectionState,
|
|
|
|
selected = state & SelectionState.POINT;
|
|
|
|
if (selected || (state & SelectionState.HANDLE_IN))
|
|
|
|
drawHandle(ctx, point, segment._handleIn);
|
|
|
|
if (selected || (state & SelectionState.HANDLE_OUT))
|
|
|
|
drawHandle(ctx, point, segment._handleOut);
|
2011-04-17 12:46:35 -04:00
|
|
|
// Draw a rectangle at segment.point:
|
|
|
|
ctx.save();
|
|
|
|
ctx.beginPath();
|
2011-04-21 09:25:25 -04:00
|
|
|
ctx.rect(point._x - 2, point._y - 2, 4, 4);
|
2011-04-17 12:46:35 -04:00
|
|
|
ctx.fill();
|
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:
|
2011-06-14 10:37:25 -04:00
|
|
|
if (!selected) {
|
2011-04-21 12:06:06 -04:00
|
|
|
ctx.beginPath();
|
|
|
|
ctx.rect(point._x - 1, point._y - 1, 2, 2);
|
|
|
|
ctx.fillStyle = '#ffffff';
|
|
|
|
ctx.fill();
|
|
|
|
ctx.restore();
|
|
|
|
}
|
2011-04-17 12:46:35 -04:00
|
|
|
}
|
|
|
|
}
|
2011-06-13 14:09:10 -04:00
|
|
|
|
2011-04-17 12:46:35 -04:00
|
|
|
function drawHandle(ctx, point, handle) {
|
|
|
|
if (!handle.isZero()) {
|
2011-04-23 09:56:27 -04:00
|
|
|
var handleX = point._x + handle._x,
|
|
|
|
handleY = point._y + handle._y;
|
2011-04-17 12:46:35 -04:00
|
|
|
ctx.beginPath();
|
2011-04-21 09:25:25 -04:00
|
|
|
ctx.moveTo(point._x, point._y);
|
2011-04-23 09:56:27 -04:00
|
|
|
ctx.lineTo(handleX, handleY);
|
2011-04-17 12:46:35 -04:00
|
|
|
ctx.stroke();
|
|
|
|
ctx.beginPath();
|
2011-05-05 06:01:20 -04:00
|
|
|
ctx.arc(handleX, handleY, 1.75, 0, Math.PI * 2, true);
|
|
|
|
ctx.fill();
|
2011-04-17 12:46:35 -04:00
|
|
|
}
|
|
|
|
}
|
2011-06-04 10:28:06 -04:00
|
|
|
|
2011-06-05 06:20:28 -04:00
|
|
|
function drawSegments(ctx, path) {
|
|
|
|
var segments = path._segments,
|
|
|
|
length = segments.length,
|
|
|
|
handleOut, outX, outY;
|
2011-06-04 10:28:06 -04:00
|
|
|
|
|
|
|
function drawSegment(i) {
|
|
|
|
var segment = segments[i],
|
|
|
|
point = segment._point,
|
|
|
|
x = point._x,
|
|
|
|
y = point._y,
|
|
|
|
handleIn = segment._handleIn;
|
|
|
|
if (!handleOut) {
|
|
|
|
ctx.moveTo(x, y);
|
|
|
|
} else {
|
|
|
|
if (handleIn.isZero() && handleOut.isZero()) {
|
|
|
|
ctx.lineTo(x, y);
|
|
|
|
} else {
|
|
|
|
ctx.bezierCurveTo(outX, outY,
|
|
|
|
handleIn._x + x, handleIn._y + y, x, y);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
handleOut = segment._handleOut;
|
|
|
|
outX = handleOut._x + x;
|
|
|
|
outY = handleOut._y + y;
|
|
|
|
}
|
|
|
|
|
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
|
2011-06-05 06:20:28 -04:00
|
|
|
if (path._closed && length > 1)
|
2011-06-04 10:28:06 -04:00
|
|
|
drawSegment(0);
|
|
|
|
}
|
|
|
|
|
2011-06-04 13:25:41 -04:00
|
|
|
function drawDashes(ctx, path, dashArray, dashOffset) {
|
|
|
|
var flattener = new PathFlattener(path),
|
2011-06-04 11:08:40 -04:00
|
|
|
from = dashOffset, to,
|
2011-06-04 13:25:41 -04:00
|
|
|
i = 0;
|
|
|
|
while (from < flattener.length) {
|
|
|
|
to = from + dashArray[(i++) % dashArray.length];
|
2011-06-04 14:25:50 -04:00
|
|
|
flattener.drawPart(ctx, from, to);
|
2011-06-04 13:25:41 -04:00
|
|
|
from = to + dashArray[(i++) % dashArray.length];
|
2011-06-04 11:08:40 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-04-17 12:46:35 -04:00
|
|
|
return {
|
|
|
|
draw: function(ctx, param) {
|
|
|
|
if (!param.compound)
|
|
|
|
ctx.beginPath();
|
2011-06-04 10:16:21 -04:00
|
|
|
|
2011-06-04 10:28:06 -04:00
|
|
|
var fillColor = this.getFillColor(),
|
2011-06-04 11:08:40 -04:00
|
|
|
strokeColor = this.getStrokeColor(),
|
|
|
|
dashArray = this.getDashArray() || [], // TODO: Always defined?
|
|
|
|
hasDash = !!dashArray.length;
|
2011-06-04 10:28:06 -04:00
|
|
|
|
2011-06-17 08:56:02 -04:00
|
|
|
if (param.compound || param.selection || this._clipMask || fillColor
|
2011-06-04 11:08:40 -04:00
|
|
|
|| strokeColor && !hasDash) {
|
2011-06-05 06:20:28 -04:00
|
|
|
drawSegments(ctx, this);
|
2011-06-04 10:28:06 -04:00
|
|
|
}
|
2011-06-04 10:16:21 -04:00
|
|
|
|
2011-04-21 09:48:21 -04:00
|
|
|
// If we are drawing the selection of a path, stroke it and draw
|
|
|
|
// its handles:
|
2011-04-17 12:46:35 -04:00
|
|
|
if (param.selection) {
|
2011-03-03 07:19:43 -05:00
|
|
|
ctx.stroke();
|
2011-04-21 09:48:21 -04:00
|
|
|
drawHandles(ctx, this._segments);
|
2011-06-17 08:56:02 -04:00
|
|
|
} else if (this._clipMask) {
|
2011-06-04 10:28:06 -04:00
|
|
|
ctx.clip();
|
|
|
|
} else if (!param.compound && (fillColor || strokeColor)) {
|
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
|
|
|
ctx.save();
|
|
|
|
this._setStyles(ctx);
|
|
|
|
// If the path only defines a strokeColor or a fillColor,
|
|
|
|
// draw it directly with the globalAlpha set, otherwise
|
|
|
|
// we will do it later when we composite the temporary
|
|
|
|
// canvas.
|
|
|
|
if (!fillColor || !strokeColor)
|
2011-06-17 13:53:34 -04:00
|
|
|
ctx.globalAlpha = this._opacity;
|
2011-06-04 10:28:06 -04:00
|
|
|
if (fillColor) {
|
|
|
|
ctx.fillStyle = fillColor.getCanvasStyle(ctx);
|
|
|
|
ctx.fill();
|
|
|
|
}
|
|
|
|
if (strokeColor) {
|
|
|
|
ctx.strokeStyle = strokeColor.getCanvasStyle(ctx);
|
2011-06-04 11:08:40 -04:00
|
|
|
if (hasDash) {
|
|
|
|
// We cannot use the path created by drawSegments above
|
|
|
|
// Use CurveFlatteners to draw dashed paths:
|
|
|
|
ctx.beginPath();
|
2011-06-04 13:25:41 -04:00
|
|
|
drawDashes(ctx, this, dashArray, this.getDashOffset());
|
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-06-04 10:28:06 -04:00
|
|
|
ctx.restore();
|
2011-03-03 07:19:43 -05:00
|
|
|
}
|
|
|
|
}
|
2011-04-17 12:46:35 -04:00
|
|
|
};
|
2011-03-02 12:23:45 -05:00
|
|
|
}, new function() { // Inject methods that require scoped privates
|
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;
|
|
|
|
};
|
2011-02-13 21:05:54 -05:00
|
|
|
|
2011-03-04 20:36:27 -05:00
|
|
|
var styles = {
|
|
|
|
getStrokeWidth: 'lineWidth',
|
|
|
|
getStrokeJoin: 'lineJoin',
|
|
|
|
getStrokeCap: 'lineCap',
|
|
|
|
getMiterLimit: 'miterLimit'
|
2011-02-17 15:08:37 -05:00
|
|
|
};
|
2011-02-17 07:36:40 -05:00
|
|
|
|
2011-02-17 15:08:37 -05:00
|
|
|
return {
|
2011-05-16 14:19:18 -04:00
|
|
|
_setStyles: function(ctx) {
|
|
|
|
for (var i in styles) {
|
|
|
|
var style = this._style[i]();
|
|
|
|
if (style)
|
|
|
|
ctx[styles[i]] = style;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
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,
|
|
|
|
n = size,
|
|
|
|
// Add overlapping ends for averaging handles in closed paths
|
|
|
|
overlap;
|
|
|
|
|
2011-02-17 07:36:40 -05:00
|
|
|
if (size <= 2)
|
|
|
|
return;
|
|
|
|
|
2011-04-30 18:22:29 -04:00
|
|
|
if (this._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;
|
|
|
|
} else {
|
|
|
|
overlap = 0;
|
|
|
|
}
|
|
|
|
var knots = [];
|
|
|
|
for (var i = 0; i < size; i++)
|
2011-03-06 05:57:14 -05:00
|
|
|
knots[i + overlap] = segments[i]._point;
|
2011-04-30 18:22:29 -04:00
|
|
|
if (this._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);
|
|
|
|
|
2011-04-30 18:22:29 -04:00
|
|
|
if (this._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++) {
|
|
|
|
var f1 = (i / overlap);
|
|
|
|
var f2 = 1 - f1;
|
|
|
|
// Beginning
|
|
|
|
x[j] = x[i] * f1 + x[j] * f2;
|
|
|
|
y[j] = y[i] * f1 + y[j] * f2;
|
|
|
|
// End
|
|
|
|
var ie = i + overlap, je = j + overlap;
|
|
|
|
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(
|
|
|
|
new Point(x[i], y[i]).subtract(segment._point));
|
2011-02-17 07:36:40 -05:00
|
|
|
if (i < n - 1)
|
|
|
|
handleIn = new Point(
|
2011-04-21 09:25:25 -04:00
|
|
|
2 * knots[i + 1]._x - x[i + 1],
|
|
|
|
2 * knots[i + 1]._y - y[i + 1]);
|
2011-02-17 07:36:40 -05:00
|
|
|
else
|
|
|
|
handleIn = 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
|
|
|
}
|
|
|
|
}
|
2011-04-30 18:22:29 -04:00
|
|
|
if (this._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;
|
|
|
|
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.
|
2011-05-15 14:57:48 -04:00
|
|
|
moveTo: function(point) {
|
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:
|
|
|
|
if (!this._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
|
|
|
|
2011-06-16 17:07:00 -04:00
|
|
|
moveBy: function(point) {
|
|
|
|
throw new Error('moveBy() is unsupported on Path items.');
|
|
|
|
},
|
|
|
|
|
2011-05-15 14:57:48 -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
|
|
|
|
2011-03-06 10:21:12 -05:00
|
|
|
cubicCurveTo: function(handle1, handle2, to) {
|
2011-04-12 08:35:42 -04:00
|
|
|
handle1 = Point.read(arguments, 0, 1);
|
|
|
|
handle2 = Point.read(arguments, 1, 1);
|
|
|
|
to = Point.read(arguments, 2, 1);
|
2011-03-06 10:21:12 -05:00
|
|
|
// First modify the current segment:
|
|
|
|
var current = getCurrentSegment(this);
|
|
|
|
// Convert to relative values:
|
2011-04-30 18:17:19 -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
|
2011-05-05 19:18:56 -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
|
|
|
|
2011-03-06 10:21:12 -05:00
|
|
|
quadraticCurveTo: function(handle, to) {
|
2011-03-19 19:09:17 -04:00
|
|
|
handle = Point.read(arguments, 0, 1);
|
|
|
|
to = Point.read(arguments, 1, 1);
|
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)
|
2011-04-28 08:12:21 -04:00
|
|
|
var current = getCurrentSegment(this)._point;
|
2011-03-06 10:21:12 -05:00
|
|
|
this.cubicCurveTo(
|
2011-04-28 08:12:21 -04:00
|
|
|
handle.add(current.subtract(handle).multiply(1/3)),
|
2011-03-06 10:21:12 -05:00
|
|
|
handle.add(to.subtract(handle).multiply(1/3)),
|
|
|
|
to
|
|
|
|
);
|
|
|
|
},
|
2011-03-06 10:15:13 -05:00
|
|
|
|
2011-03-06 10:21:12 -05:00
|
|
|
curveTo: function(through, to, parameter) {
|
2011-03-19 19:09:17 -04:00
|
|
|
through = Point.read(arguments, 0, 1);
|
|
|
|
to = Point.read(arguments, 1, 1);
|
2011-04-28 08:12:21 -04:00
|
|
|
var t = Base.pick(parameter, 0.5),
|
|
|
|
t1 = 1 - t,
|
|
|
|
current = getCurrentSegment(this)._point,
|
|
|
|
// handle = (through - (1 - t)^2 * current - t^2 * to) /
|
|
|
|
// (2 * (1 - t) * t)
|
|
|
|
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);
|
2011-03-06 10:21:12 -05:00
|
|
|
this.quadraticCurveTo(handle, to);
|
|
|
|
},
|
2011-03-06 10:15:13 -05:00
|
|
|
|
2011-06-14 16:32:14 -04:00
|
|
|
// PORT: New implementation back to Scriptographer
|
2011-06-16 17:38:43 -04:00
|
|
|
arcTo: function(to, clockwise /* | through, to */) {
|
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,
|
2011-04-28 08:12:21 -04:00
|
|
|
through;
|
2011-06-16 18:48:46 -04:00
|
|
|
if (clockwise === undefined)
|
|
|
|
clockwise = true;
|
|
|
|
if (typeof clockwise === 'boolean') {
|
2011-03-06 10:21:12 -05:00
|
|
|
to = Point.read(arguments, 0, 1);
|
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));
|
|
|
|
} else {
|
|
|
|
through = Point.read(arguments, 0, 1);
|
|
|
|
to = Point.read(arguments, 1, 1);
|
2011-03-06 10:15:13 -05:00
|
|
|
}
|
2011-06-14 07:19:14 -04:00
|
|
|
// 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)),
|
|
|
|
l2 = new Line(through.add(to).divide(2),
|
|
|
|
to.subtract(through).rotate(90)),
|
|
|
|
center = l1.intersect(l2),
|
2011-06-14 07:45:37 -04:00
|
|
|
line = new Line(from, to, true),
|
2011-06-16 18:50:14 -04:00
|
|
|
throughSide = line.getSide(through);
|
2011-06-14 07:45:37 -04:00
|
|
|
if (!center) {
|
|
|
|
// 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:
|
2011-06-16 18:50:14 -04:00
|
|
|
if (!throughSide)
|
2011-06-16 17:38:43 -04:00
|
|
|
return this.lineTo(to);
|
2011-06-16 17:50:59 -04:00
|
|
|
throw new Error("Cannot put an arc through the given points: "
|
|
|
|
+ [from, through, to]);
|
2011-06-14 07:45:37 -04:00
|
|
|
}
|
|
|
|
var vector = from.subtract(center),
|
2011-06-14 07:19:14 -04:00
|
|
|
radius = vector.getLength(),
|
2011-06-16 18:50:14 -04:00
|
|
|
extent = vector.getDirectedAngle(to.subtract(center)),
|
|
|
|
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.
|
2011-06-14 07:19:14 -04:00
|
|
|
extent -= 360 * (extent < 0 ? -1 : 1);
|
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++) {
|
2011-06-14 04:12:18 -04:00
|
|
|
// Explicitely use to point for last segment, since depending
|
|
|
|
// on values the calculation adds imprecision:
|
2011-06-14 07:19:14 -04:00
|
|
|
var pt = i < count ? center.add(vector) : to;
|
|
|
|
var out = i < count ? vector.rotate(90).multiply(z) : null;
|
2011-03-06 10:21:12 -05:00
|
|
|
if (i == 0) {
|
|
|
|
// Modify startSegment
|
|
|
|
current.setHandleOut(out);
|
|
|
|
} else {
|
|
|
|
// Add new Segment
|
2011-06-14 07:19:14 -04:00
|
|
|
segments.push(
|
|
|
|
new Segment(pt, vector.rotate(-90).multiply(z), 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
|
|
|
|
2011-03-13 13:31:00 -04:00
|
|
|
lineBy: function(vector) {
|
|
|
|
vector = Point.read(arguments);
|
|
|
|
var current = getCurrentSegment(this);
|
|
|
|
this.lineTo(current._point.add(vector));
|
2011-03-06 10:21:12 -05:00
|
|
|
},
|
2011-03-06 10:15:13 -05:00
|
|
|
|
2011-03-06 10:21:12 -05:00
|
|
|
curveBy: function(throughVector, toVector, parameter) {
|
|
|
|
throughVector = Point.read(throughVector);
|
|
|
|
toVector = Point.read(toVector);
|
|
|
|
var current = getCurrentSegment(this)._point;
|
|
|
|
this.curveTo(current.add(throughVector), current.add(toVector),
|
|
|
|
parameter);
|
|
|
|
},
|
2011-03-06 10:15:13 -05:00
|
|
|
|
2011-03-06 10:21:12 -05:00
|
|
|
arcBy: function(throughVector, toVector) {
|
|
|
|
throughVector = Point.read(throughVector);
|
|
|
|
toVector = Point.read(toVector);
|
|
|
|
var current = getCurrentSegment(this)._point;
|
|
|
|
this.arcBy(current.add(throughVector), current.add(toVector));
|
|
|
|
},
|
|
|
|
|
|
|
|
closePath: function() {
|
2011-04-30 18:22:29 -04:00
|
|
|
this.setClosed(true);
|
2011-03-06 10:21:12 -05:00
|
|
|
}
|
|
|
|
};
|
2011-03-06 19:36:44 -05:00
|
|
|
}, new function() { // A dedicated scope for the tricky bounds calculations
|
|
|
|
|
|
|
|
function getBounds(that, matrix, strokePadding) {
|
|
|
|
// Code ported and further optimised from:
|
|
|
|
// http://blog.hackers-cafe.net/2009/06/how-to-calculate-bezier-curves-bounding.html
|
|
|
|
var segments = that._segments,
|
|
|
|
first = segments[0];
|
|
|
|
if (!first)
|
|
|
|
return null;
|
|
|
|
var coords = new Array(6),
|
|
|
|
prevCoords = new Array(6);
|
|
|
|
// Make coordinates for first segment available in prevCoords.
|
|
|
|
if (matrix && matrix.isIdentity())
|
|
|
|
matrix = null;
|
2011-05-06 08:40:43 -04:00
|
|
|
first._transformCoordinates(matrix, prevCoords, false);
|
2011-03-06 19:36:44 -05:00
|
|
|
var min = prevCoords.slice(0, 2),
|
2011-05-07 06:23:46 -04:00
|
|
|
max = min.slice(0), // clone
|
|
|
|
// Add some tolerance for good roots, as t = 0 / 1 are added
|
|
|
|
// seperately anyhow, and we don't want joins to be added with
|
|
|
|
// radiuses in getStrokeBounds()
|
|
|
|
tMin = Numerical.TOLERANCE,
|
|
|
|
tMax = 1 - tMin;
|
2011-03-06 19:36:44 -05:00
|
|
|
function processSegment(segment) {
|
2011-05-06 08:40:43 -04:00
|
|
|
segment._transformCoordinates(matrix, coords, false);
|
2011-03-06 19:36:44 -05:00
|
|
|
|
|
|
|
for (var i = 0; i < 2; i++) {
|
|
|
|
var v0 = prevCoords[i], // prev.point
|
|
|
|
v1 = prevCoords[i + 4], // prev.handleOut
|
|
|
|
v2 = coords[i + 2], // segment.handleIn
|
|
|
|
v3 = coords[i]; // segment.point
|
|
|
|
|
|
|
|
function add(value, t) {
|
|
|
|
var padding = 0;
|
|
|
|
if (value == null) {
|
|
|
|
// Calculate bezier polynomial at t
|
|
|
|
var u = 1 - t;
|
|
|
|
value = u * u * u * v0
|
|
|
|
+ 3 * u * u * t * v1
|
|
|
|
+ 3 * u * t * t * v2
|
|
|
|
+ t * t * t * v3;
|
|
|
|
// Only add strokeWidth to bounds for points which lie
|
|
|
|
// within 0 < t < 1. The corner cases for cap and join
|
|
|
|
// are handled in getStrokeBounds()
|
|
|
|
padding = strokePadding ? strokePadding[i] : 0;
|
|
|
|
}
|
|
|
|
var left = value - padding,
|
|
|
|
right = value + padding;
|
|
|
|
if (left < min[i])
|
|
|
|
min[i] = left;
|
|
|
|
if (right > max[i])
|
|
|
|
max[i] = right;
|
|
|
|
|
|
|
|
}
|
|
|
|
add(v3, null);
|
|
|
|
|
|
|
|
// Calculate derivative of our bezier polynomial, divided by 3.
|
|
|
|
// Dividing by 3 allows for simpler calculations of a, b, c and
|
|
|
|
// leads to the same quadratic roots below.
|
|
|
|
var a = 3 * (v1 - v2) - v0 + v3,
|
|
|
|
b = 2 * (v0 + v2) - 4 * v1,
|
|
|
|
c = v1 - v0;
|
|
|
|
|
|
|
|
// Solve for derivative for quadratic roots. Each good root
|
|
|
|
// (meaning a solution 0 < t < 1) is an extrema in the cubic
|
|
|
|
// polynomial and thus a potential point defining the bounds
|
|
|
|
if (a == 0) {
|
|
|
|
if (b == 0)
|
|
|
|
continue;
|
|
|
|
var t = -c / b;
|
|
|
|
// Test for good root and add to bounds if good (same below)
|
|
|
|
if (tMin < t && t < tMax)
|
|
|
|
add(null, t);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
var b2ac = b * b - 4 * a * c;
|
|
|
|
if (b2ac < 0)
|
|
|
|
continue;
|
|
|
|
var sqrt = Math.sqrt(b2ac),
|
|
|
|
f = 1 / (a * -2),
|
|
|
|
t1 = (b - sqrt) * f,
|
|
|
|
t2 = (b + sqrt) * f;
|
|
|
|
if (tMin < t1 && t1 < tMax)
|
|
|
|
add(null, t1);
|
|
|
|
if (tMin < t2 && t2 < tMax)
|
|
|
|
add(null, t2);
|
|
|
|
}
|
|
|
|
// Swap coordinate buffers
|
|
|
|
var tmp = prevCoords;
|
|
|
|
prevCoords = coords;
|
|
|
|
coords = tmp;
|
|
|
|
}
|
|
|
|
for (var i = 1, l = segments.length; i < l; i++)
|
|
|
|
processSegment(segments[i]);
|
2011-04-30 18:22:29 -04:00
|
|
|
if (that._closed)
|
2011-03-06 19:36:44 -05:00
|
|
|
processSegment(first);
|
2011-03-16 18:32:46 -04:00
|
|
|
return Rectangle.create(min[0], min[1],
|
|
|
|
max[0] - min[0], max[1] - min[1]);
|
2011-03-06 19:36:44 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
function getPenPadding(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.createShiftless(),
|
|
|
|
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)) // x
|
|
|
|
// t = pi * n + arctan(b cot(phi)) // y
|
|
|
|
var tx = - Math.atan(b * Math.tan(phi)),
|
|
|
|
ty = + Math.atan(b / Math.tan(phi)),
|
|
|
|
// Due to symetry, we don't need to cycle through pi * n
|
|
|
|
// solutions:
|
|
|
|
x = a * Math.cos(tx) * Math.cos(phi)
|
|
|
|
- b * Math.sin(tx) * Math.sin(phi),
|
|
|
|
y = b * Math.sin(ty) * Math.cos(phi)
|
|
|
|
+ a * Math.cos(ty) * Math.sin(phi);
|
|
|
|
// Now update the join / round padding, as required by
|
|
|
|
// getBounds() and code below.
|
2011-03-06 20:30:45 -05:00
|
|
|
return [Math.abs(x), Math.abs(y)];
|
2011-03-06 19:36:44 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
/**
|
|
|
|
* The bounding rectangle of the item excluding stroke width.
|
2011-03-08 12:19:02 -05:00
|
|
|
* @param matrix optional
|
2011-03-06 19:36:44 -05:00
|
|
|
*/
|
2011-03-08 12:19:02 -05:00
|
|
|
getBounds: function(/* matrix */) {
|
2011-05-05 06:21:09 -04:00
|
|
|
var useCache = arguments.length == 0;
|
2011-06-30 06:01:51 -04:00
|
|
|
// Pass the matrix hidden from Bootstrap, so it still inject
|
2011-03-08 12:19:02 -05:00
|
|
|
// getBounds as bean too.
|
2011-05-05 06:21:09 -04:00
|
|
|
if (!useCache || !this._bounds) {
|
2011-06-20 12:34:43 -04:00
|
|
|
var bounds = this._createBounds(getBounds(this, arguments[0]));
|
|
|
|
if (useCache)
|
|
|
|
this._bounds = bounds;
|
|
|
|
return bounds;
|
2011-05-01 19:17:21 -04:00
|
|
|
}
|
|
|
|
return this._bounds;
|
2011-03-06 19:36:44 -05:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The bounding rectangle of the item including stroke width.
|
|
|
|
*/
|
2011-03-08 12:19:02 -05:00
|
|
|
getStrokeBounds: function(/* matrix */) {
|
2011-05-18 14:22:57 -04:00
|
|
|
if (!this._style._strokeColor || !this._style._strokeWidth)
|
2011-05-18 16:32:00 -04:00
|
|
|
return this.getBounds.apply(this, arguments);
|
2011-05-05 06:21:09 -04:00
|
|
|
var useCache = arguments.length == 0;
|
|
|
|
if (this._strokeBounds && useCache)
|
|
|
|
return this._strokeBounds;
|
2011-03-08 12:19:02 -05:00
|
|
|
var matrix = arguments[0], // set #getBounds()
|
|
|
|
width = this.getStrokeWidth(),
|
2011-03-06 19:36:44 -05:00
|
|
|
radius = width / 2,
|
|
|
|
padding = getPenPadding(radius, matrix),
|
|
|
|
join = this.getStrokeJoin(),
|
|
|
|
cap = this.getStrokeCap(),
|
|
|
|
// miter is relative to width. Divide it by 2 since we're
|
|
|
|
// measuring half the distance below
|
|
|
|
miter = this.getMiterLimit() * width / 2,
|
|
|
|
segments = this._segments,
|
|
|
|
length = segments.length,
|
2011-03-06 20:30:45 -05:00
|
|
|
// It seems to be compatible with Ai we need to pass pen padding
|
|
|
|
// untransformed to getBounds()
|
|
|
|
bounds = getBounds(this, matrix, getPenPadding(radius));
|
2011-03-06 19:36:44 -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
|
|
|
|
? matrix.transform(point) : point);
|
|
|
|
}
|
|
|
|
|
|
|
|
function addBevelJoin(curve, t) {
|
|
|
|
var point = curve.getPoint(t),
|
|
|
|
normal = curve.getNormal(t).normalize(radius);
|
|
|
|
add(point.add(normal));
|
|
|
|
add(point.subtract(normal));
|
|
|
|
}
|
|
|
|
|
|
|
|
function addJoin(segment, join) {
|
|
|
|
// When both handles are set in a segment, the join setting is
|
|
|
|
// ignored and round is always used.
|
2011-07-01 06:30:10 -04:00
|
|
|
if (join === 'round' || !segment._handleIn.isZero()
|
|
|
|
&& !segment._handleOut.isZero()) {
|
2011-03-06 19:36:44 -05:00
|
|
|
bounds = bounds.unite(joinBounds.setCenter(matrix
|
|
|
|
? matrix.transform(segment._point) : segment._point));
|
2011-05-05 19:39:44 -04:00
|
|
|
} else if (join == 'bevel') {
|
|
|
|
var curve = segment.getCurve();
|
|
|
|
addBevelJoin(curve, 0);
|
|
|
|
addBevelJoin(curve.getPrevious(), 1);
|
|
|
|
} else if (join == 'miter') {
|
|
|
|
var curve2 = segment.getCurve(),
|
|
|
|
curve1 = curve2.getPrevious(),
|
|
|
|
point = curve2.getPoint(0),
|
|
|
|
normal1 = curve1.getNormal(1).normalize(radius),
|
|
|
|
normal2 = curve2.getNormal(0).normalize(radius),
|
|
|
|
// Intersect the two lines
|
2011-06-20 10:56:08 -04:00
|
|
|
line1 = new Line(point.subtract(normal1),
|
2011-05-05 19:39:44 -04:00
|
|
|
new Point(-normal1.y, normal1.x)),
|
|
|
|
line2 = new Line(point.subtract(normal2),
|
|
|
|
new Point(-normal2.y, normal2.x)),
|
|
|
|
corner = line1.intersect(line2);
|
|
|
|
// Now measure the distance from the segment to the
|
|
|
|
// intersection, which his half of the miter distance
|
|
|
|
if (!corner || point.getDistance(corner) > miter) {
|
|
|
|
addJoin(segment, 'bevel');
|
|
|
|
} else {
|
|
|
|
add(corner);
|
2011-03-06 19:36:44 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function addCap(segment, cap, t) {
|
|
|
|
switch (cap) {
|
|
|
|
case 'round':
|
|
|
|
return addJoin(segment, cap);
|
|
|
|
case 'butt':
|
|
|
|
case 'square':
|
|
|
|
// Calculate the corner points of butt and square caps
|
|
|
|
var curve = segment.getCurve(),
|
|
|
|
point = curve.getPoint(t),
|
|
|
|
normal = curve.getNormal(t).normalize(radius);
|
|
|
|
// For square caps, we need to step away from point in the
|
|
|
|
// direction of the tangent, which is the rotated normal
|
2011-04-28 08:23:17 -04:00
|
|
|
if (cap === 'square')
|
2011-03-06 19:36:44 -05:00
|
|
|
point = point.add(normal.y, -normal.x);
|
|
|
|
add(point.add(normal));
|
|
|
|
add(point.subtract(normal));
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-04-30 18:22:29 -04:00
|
|
|
for (var i = 1, l = length - (this._closed ? 0 : 1); i < l; i++) {
|
2011-03-06 19:36:44 -05:00
|
|
|
addJoin(segments[i], join);
|
|
|
|
}
|
2011-04-30 18:22:29 -04:00
|
|
|
if (this._closed) {
|
2011-03-06 19:36:44 -05:00
|
|
|
addJoin(segments[0], join);
|
|
|
|
} else {
|
|
|
|
addCap(segments[0], cap, 0);
|
|
|
|
addCap(segments[length - 1], cap, 1);
|
|
|
|
}
|
2011-05-05 06:21:09 -04:00
|
|
|
if (useCache)
|
|
|
|
this._strokeBounds = bounds;
|
2011-03-06 19:36:44 -05:00
|
|
|
return bounds;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The bounding rectangle of the item including handles.
|
2011-07-01 06:51:18 -04:00
|
|
|
*
|
|
|
|
* @type Rectangle
|
|
|
|
* @bean
|
2011-03-06 19:36:44 -05:00
|
|
|
*/
|
2011-07-01 11:23:10 -04:00
|
|
|
getHandleBounds: function() {
|
2011-07-01 06:51:18 -04:00
|
|
|
var x1 = Infinity,
|
|
|
|
x2 = -Infinity,
|
|
|
|
y1 = x1,
|
|
|
|
y2 = x2;
|
|
|
|
|
|
|
|
function add(point, relative) {
|
|
|
|
var x = point._x,
|
|
|
|
y = point._y;
|
|
|
|
if (relative) {
|
|
|
|
x += relative._x;
|
|
|
|
y += relative._y;
|
|
|
|
}
|
|
|
|
if (x < x1) x1 = x;
|
|
|
|
if (x > x2) x2 = x;
|
|
|
|
if (y < y1) y1 = y;
|
|
|
|
if (y > y2) y2 = y;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (var i = 0, l = this._segments.length; i < l; i++) {
|
|
|
|
var segment = this._segments[i],
|
|
|
|
point = segment._point;
|
|
|
|
add(point);
|
|
|
|
add(segment._handleIn, point);
|
|
|
|
add(segment.handleOut, point);
|
|
|
|
}
|
|
|
|
return Rectangle.create(x1, y1, x2 - x1, y2 - y1);
|
2011-03-06 19:36:44 -05:00
|
|
|
}
|
2011-06-13 14:09:10 -04:00
|
|
|
|
2011-04-12 08:36:02 -04:00
|
|
|
// TODO: intersects(item)
|
|
|
|
// TODO: contains(item)
|
|
|
|
// TODO: contains(point)
|
|
|
|
// TODO: intersect(item)
|
|
|
|
// TODO: unite(item)
|
|
|
|
// TODO: exclude(item)
|
|
|
|
// TODO: getIntersections(path)
|
2011-03-06 19:36:44 -05:00
|
|
|
};
|
2011-02-13 11:26:24 -05:00
|
|
|
});
|