2013-04-07 12:49:34 -04:00
|
|
|
/*
|
2013-01-28 21:03:27 -05:00
|
|
|
* Paper.js - The Swiss Army Knife of Vector Graphics Scripting.
|
2011-03-07 20:41:50 -05:00
|
|
|
* http://paperjs.org/
|
2011-06-30 06:01:51 -04:00
|
|
|
*
|
2014-01-03 19:47:16 -05:00
|
|
|
* Copyright (c) 2011 - 2014, Juerg Lehni & Jonathan Puckey
|
|
|
|
* http://scratchdisk.com/ & http://jonathanpuckey.com/
|
2011-06-30 06:01:51 -04:00
|
|
|
*
|
2011-07-01 06:17:45 -04:00
|
|
|
* Distributed under the MIT license. See LICENSE file for details.
|
|
|
|
*
|
2011-03-07 20:41:50 -05:00
|
|
|
* All rights reserved.
|
2011-03-06 19:50:44 -05:00
|
|
|
*/
|
|
|
|
|
2011-06-22 18:56:05 -04:00
|
|
|
/**
|
|
|
|
* @name PathItem
|
2011-07-01 05:22:33 -04:00
|
|
|
*
|
|
|
|
* @class The PathItem class is the base for any items that describe paths
|
|
|
|
* and offer standardised methods for drawing and path manipulation, such as
|
|
|
|
* {@link Path} and {@link CompoundPath}.
|
|
|
|
*
|
2011-06-22 18:56:05 -04:00
|
|
|
* @extends Item
|
|
|
|
*/
|
2013-05-27 15:48:58 -04:00
|
|
|
var PathItem = Item.extend(/** @lends PathItem# */{
|
2013-06-23 23:18:32 -04:00
|
|
|
_class: 'PathItem',
|
|
|
|
|
2013-05-27 18:55:44 -04:00
|
|
|
initialize: function PathItem() {
|
2013-07-21 18:45:22 -04:00
|
|
|
// Do nothing.
|
2013-05-27 18:55:44 -04:00
|
|
|
},
|
|
|
|
|
2012-12-27 13:26:40 -05:00
|
|
|
/**
|
2013-03-03 13:47:32 -05:00
|
|
|
* Returns all intersections between two {@link PathItem} items as an array
|
2012-12-27 13:26:40 -05:00
|
|
|
* of {@link CurveLocation} objects. {@link CompoundPath} items are also
|
|
|
|
* supported.
|
|
|
|
*
|
2013-12-28 15:06:35 -05:00
|
|
|
* @name PathItem#getIntersections(path, sorted)
|
|
|
|
* @function
|
|
|
|
*
|
|
|
|
* @param {PathItem} path the other item to find the intersections with
|
|
|
|
* @param {Boolean} [sorted=true] controls wether to sort the results by
|
|
|
|
* offset
|
2012-12-27 13:26:40 -05:00
|
|
|
* @return {CurveLocation[]} the locations of all intersection between the
|
|
|
|
* paths
|
2013-03-03 13:47:32 -05:00
|
|
|
* @example {@paperscript}
|
|
|
|
* // Create a rectangular path with its top-left point at
|
|
|
|
* // {x: 30, y: 25} and a size of {width: 50, height: 50}:
|
|
|
|
* var path = new Path.Rectangle(new Point(30, 25), new Size(50, 50));
|
|
|
|
* path.strokeColor = 'black';
|
2013-04-29 15:36:12 -04:00
|
|
|
*
|
2013-03-03 13:47:32 -05:00
|
|
|
* var secondPath = path.clone();
|
|
|
|
* var intersectionGroup = new Group();
|
2013-04-29 15:36:12 -04:00
|
|
|
*
|
2013-03-03 13:47:32 -05:00
|
|
|
* function onFrame(event) {
|
|
|
|
* secondPath.rotate(3);
|
2013-04-29 15:36:12 -04:00
|
|
|
*
|
2013-03-03 13:47:32 -05:00
|
|
|
* var intersections = path.getIntersections(secondPath);
|
|
|
|
* intersectionGroup.removeChildren();
|
2013-04-29 15:36:12 -04:00
|
|
|
*
|
2013-03-03 13:47:32 -05:00
|
|
|
* for (var i = 0; i < intersections.length; i++) {
|
|
|
|
* var intersectionPath = new Path.Circle({
|
|
|
|
* center: intersections[i].point,
|
|
|
|
* radius: 4,
|
|
|
|
* fillColor: 'red'
|
|
|
|
* });
|
|
|
|
* intersectionGroup.addChild(intersectionPath);
|
|
|
|
* }
|
|
|
|
* }
|
2012-12-27 13:26:40 -05:00
|
|
|
*/
|
2014-01-25 23:38:09 -05:00
|
|
|
getIntersections: function(path, expand) {
|
2012-12-21 10:13:38 -05:00
|
|
|
// First check the bounds of the two paths. If they don't intersect,
|
2012-12-27 14:19:23 -05:00
|
|
|
// we don't need to iterate through their curves.
|
2013-04-09 23:27:55 -04:00
|
|
|
if (!this.getBounds().touches(path.getBounds()))
|
2012-12-21 10:13:38 -05:00
|
|
|
return [];
|
|
|
|
var locations = [],
|
|
|
|
curves1 = this.getCurves(),
|
2012-12-27 13:23:03 -05:00
|
|
|
curves2 = path.getCurves(),
|
2013-12-08 16:12:36 -05:00
|
|
|
matrix1 = this._matrix.orNullIfIdentity(),
|
|
|
|
matrix2 = path._matrix.orNullIfIdentity(),
|
2013-12-17 09:23:07 -05:00
|
|
|
length1 = curves1.length,
|
2012-12-27 13:23:03 -05:00
|
|
|
length2 = curves2.length,
|
2014-02-01 11:07:24 -05:00
|
|
|
values2 = [];
|
2012-12-27 13:23:03 -05:00
|
|
|
for (var i = 0; i < length2; i++)
|
2013-12-08 16:00:40 -05:00
|
|
|
values2[i] = curves2[i].getValues(matrix2);
|
2013-12-17 09:23:07 -05:00
|
|
|
for (var i = 0; i < length1; i++) {
|
2013-04-30 21:41:26 -04:00
|
|
|
var curve1 = curves1[i],
|
2013-12-08 16:00:40 -05:00
|
|
|
values1 = curve1.getValues(matrix1);
|
2012-12-27 13:23:03 -05:00
|
|
|
for (var j = 0; j < length2; j++)
|
2013-04-30 21:41:26 -04:00
|
|
|
Curve.getIntersections(values1, values2[j], curve1, curves2[j],
|
|
|
|
locations);
|
2012-12-27 13:23:03 -05:00
|
|
|
}
|
2014-02-01 11:07:24 -05:00
|
|
|
|
|
|
|
return PathItem._conditionIntersections(locations, expand);
|
2013-02-28 22:13:46 -05:00
|
|
|
},
|
|
|
|
|
2014-02-17 14:59:38 -05:00
|
|
|
getSelfIntersections: function(expand) {
|
2013-12-29 07:06:25 -05:00
|
|
|
var locations = [],
|
|
|
|
locs = [],
|
|
|
|
curves = this.getCurves(),
|
|
|
|
length = curves.length - 1,
|
|
|
|
matrix = this._matrix.orNullIfIdentity(),
|
|
|
|
values = [],
|
|
|
|
curve1, values1, parts, i, j, k, ix, from, to, param, v1, v2,
|
2014-02-17 14:59:38 -05:00
|
|
|
EPSILON = /*#=*/ Numerical.EPSILON,
|
|
|
|
EPSILON1s = 1 - EPSILON;
|
2013-12-29 07:06:25 -05:00
|
|
|
for (i = 0; i <= length; i++)
|
|
|
|
values[i] = curves[i].getValues(matrix);
|
|
|
|
for (i = 0; i <= length; i++) {
|
|
|
|
curve1 = curves[i];
|
|
|
|
values1 = values[i];
|
|
|
|
// First check for self-intersections within the same curve
|
|
|
|
from = curve1.getSegment1();
|
|
|
|
to = curve1.getSegment2();
|
|
|
|
v1 = from._handleOut;
|
|
|
|
v2 = to._handleIn;
|
|
|
|
// Check if extended handles of endpoints of this curve intersects
|
|
|
|
// each other. We cannot have a self intersection within this curve
|
|
|
|
// if they don't intersect due to convex-hull property.
|
2014-02-17 14:36:18 -05:00
|
|
|
ix = new Line(from._point.subtract(v1), v1.multiply(2), true)
|
|
|
|
.intersect(new Line(to._point.subtract(v2),
|
2013-12-29 07:06:25 -05:00
|
|
|
v2.multiply(2), true), false);
|
2014-01-25 23:39:00 -05:00
|
|
|
if (ix) {
|
2013-12-29 07:06:25 -05:00
|
|
|
parts = paper.Curve.subdivide(values1);
|
|
|
|
locs.length = 0;
|
|
|
|
Curve.getIntersections(parts[0], parts[1], curve1, curve1, locs);
|
|
|
|
for (j = locs.length - 1; j >= 0; j--) {
|
2014-02-01 11:07:24 -05:00
|
|
|
ix = locs[j];
|
2013-12-29 07:06:25 -05:00
|
|
|
if (ix._parameter <= EPSILON1s) {
|
|
|
|
ix._parameter = ix._parameter * 0.5;
|
|
|
|
ix._parameter2 = 0.5 + ix._parameter2 * 0.5;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (j >= 0)
|
|
|
|
locations.push(ix);
|
|
|
|
}
|
|
|
|
// Check for intersections with other curves
|
2014-02-17 14:59:38 -05:00
|
|
|
for (j = i + 1; j <= length; j++) {
|
2013-12-29 07:06:25 -05:00
|
|
|
// Avoid end point intersections on consecutive curves
|
|
|
|
if (j === i + 1 || (j === length && i === 0)) {
|
|
|
|
locs.length = 0;
|
|
|
|
Curve.getIntersections(values1, values[j], curve1,
|
|
|
|
curves[j], locs);
|
|
|
|
for (k = locs.length - 1; k >= 0; k--) {
|
|
|
|
param = locs[k].getParameter();
|
|
|
|
if (param < EPSILON1s && param > EPSILON)
|
|
|
|
locations.push(locs[k]);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
paper.Curve.getIntersections(values1, values[j], curve1,
|
|
|
|
curves[j], locations);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2014-02-01 11:07:24 -05:00
|
|
|
return PathItem._conditionIntersections(locations, expand);
|
2013-12-29 07:06:25 -05:00
|
|
|
},
|
|
|
|
|
2013-02-28 22:13:46 -05:00
|
|
|
setPathData: function(data) {
|
|
|
|
// This is a very compact SVG Path Data parser that works both for Path
|
|
|
|
// and CompoundPath.
|
|
|
|
|
2013-11-02 04:30:40 -04:00
|
|
|
// First split the path data into parts of command-coordinates pairs
|
|
|
|
// Commands are any of these characters: mzlhvcsqta
|
2013-11-02 04:45:11 -04:00
|
|
|
var parts = data.match(/[mlhvcsqtaz][^mlhvcsqtaz]*/ig),
|
2013-02-28 22:13:46 -05:00
|
|
|
coords,
|
|
|
|
relative = false,
|
|
|
|
control,
|
|
|
|
current = new Point(); // the current position
|
|
|
|
|
2013-11-03 06:52:00 -05:00
|
|
|
function getCoord(index, coord, isCurrent) {
|
2013-11-02 04:30:40 -04:00
|
|
|
var val = parseFloat(coords[index]);
|
2013-02-28 22:13:46 -05:00
|
|
|
if (relative)
|
|
|
|
val += current[coord];
|
2013-11-03 06:52:00 -05:00
|
|
|
if (isCurrent)
|
2013-02-28 22:13:46 -05:00
|
|
|
current[coord] = val;
|
|
|
|
return val;
|
|
|
|
}
|
|
|
|
|
2013-11-03 06:52:00 -05:00
|
|
|
function getPoint(index, isCurrent) {
|
2013-02-28 22:13:46 -05:00
|
|
|
return new Point(
|
2013-11-03 06:52:00 -05:00
|
|
|
getCoord(index, 'x', isCurrent),
|
|
|
|
getCoord(index + 1, 'y', isCurrent)
|
2013-02-28 22:13:46 -05:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2013-03-03 19:56:48 -05:00
|
|
|
// First clear the previous content
|
2013-11-01 12:52:27 -04:00
|
|
|
this.clear();
|
2013-03-03 19:56:48 -05:00
|
|
|
|
2013-02-28 22:13:46 -05:00
|
|
|
for (var i = 0, l = parts.length; i < l; i++) {
|
2013-06-12 20:27:20 -04:00
|
|
|
var part = parts[i],
|
2013-02-28 22:13:46 -05:00
|
|
|
cmd = part[0],
|
|
|
|
lower = cmd.toLowerCase();
|
2013-11-02 04:30:40 -04:00
|
|
|
// Match all coordinate values
|
2013-11-02 08:25:03 -04:00
|
|
|
coords = part.match(/[+-]?(?:\d*\.\d+|\d+\.?)(?:[eE][+-]?\d+)?/g);
|
2013-11-02 04:30:40 -04:00
|
|
|
var length = coords && coords.length;
|
2013-02-28 22:13:46 -05:00
|
|
|
relative = cmd === lower;
|
|
|
|
switch (lower) {
|
|
|
|
case 'm':
|
|
|
|
case 'l':
|
2013-11-02 04:30:40 -04:00
|
|
|
for (var j = 0; j < length; j += 2)
|
2013-02-28 22:13:46 -05:00
|
|
|
this[j === 0 && lower === 'm' ? 'moveTo' : 'lineTo'](
|
|
|
|
getPoint(j, true));
|
2013-11-03 06:52:00 -05:00
|
|
|
control = current;
|
2013-02-28 22:13:46 -05:00
|
|
|
break;
|
|
|
|
case 'h':
|
|
|
|
case 'v':
|
|
|
|
var coord = lower == 'h' ? 'x' : 'y';
|
2013-11-02 04:30:40 -04:00
|
|
|
for (var j = 0; j < length; j++) {
|
2013-02-28 22:13:46 -05:00
|
|
|
getCoord(j, coord, true);
|
2013-03-01 04:18:27 -05:00
|
|
|
this.lineTo(current);
|
|
|
|
}
|
2013-11-03 06:52:00 -05:00
|
|
|
control = current;
|
2013-02-28 22:13:46 -05:00
|
|
|
break;
|
|
|
|
case 'c':
|
2013-11-02 04:30:40 -04:00
|
|
|
for (var j = 0; j < length; j += 6) {
|
2013-04-20 17:54:21 -04:00
|
|
|
this.cubicCurveTo(
|
|
|
|
getPoint(j),
|
|
|
|
control = getPoint(j + 2),
|
|
|
|
getPoint(j + 4, true));
|
|
|
|
}
|
2013-02-28 22:13:46 -05:00
|
|
|
break;
|
2013-02-28 22:21:46 -05:00
|
|
|
case 's':
|
2013-11-03 06:52:00 -05:00
|
|
|
// Smooth cubicCurveTo
|
2013-11-02 04:30:40 -04:00
|
|
|
for (var j = 0; j < length; j += 4) {
|
2013-04-20 17:54:21 -04:00
|
|
|
this.cubicCurveTo(
|
|
|
|
// Calculate reflection of previous control points
|
|
|
|
current.multiply(2).subtract(control),
|
|
|
|
control = getPoint(j),
|
|
|
|
getPoint(j + 2, true));
|
|
|
|
}
|
2013-02-28 22:13:46 -05:00
|
|
|
break;
|
|
|
|
case 'q':
|
2013-11-02 04:30:40 -04:00
|
|
|
for (var j = 0; j < length; j += 4) {
|
2013-04-20 17:54:21 -04:00
|
|
|
this.quadraticCurveTo(
|
|
|
|
control = getPoint(j),
|
|
|
|
getPoint(j + 2, true));
|
|
|
|
}
|
2013-02-28 22:13:46 -05:00
|
|
|
break;
|
|
|
|
case 't':
|
2013-11-03 06:52:00 -05:00
|
|
|
// Smooth quadraticCurveTo
|
2013-11-02 04:30:40 -04:00
|
|
|
for (var j = 0; j < length; j += 2) {
|
2013-03-01 04:18:42 -05:00
|
|
|
this.quadraticCurveTo(
|
|
|
|
// Calculate reflection of previous control points
|
|
|
|
control = current.multiply(2).subtract(control),
|
|
|
|
getPoint(j, true));
|
2013-02-28 22:13:46 -05:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 'a':
|
|
|
|
// TODO: Implement Arcs!
|
|
|
|
break;
|
|
|
|
case 'z':
|
|
|
|
this.closePath();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2013-06-19 11:22:08 -04:00
|
|
|
},
|
|
|
|
|
|
|
|
_canComposite: function() {
|
|
|
|
// A path with only a fill or a stroke can be directly blended, but if
|
|
|
|
// it has both, it needs to be drawn into a separate canvas first.
|
|
|
|
return !(this.hasFill() && this.hasStroke());
|
2013-10-29 12:57:25 -04:00
|
|
|
},
|
|
|
|
|
2013-12-25 14:46:13 -05:00
|
|
|
/**
|
|
|
|
* Returns the winding contribution of the given point with respect to this
|
|
|
|
* PathItem.
|
2013-12-29 07:21:08 -05:00
|
|
|
*
|
2014-02-19 09:46:57 -05:00
|
|
|
* @param {Point} point the location for which to determine the winding
|
|
|
|
* direction
|
|
|
|
* @param {Boolean} horizontal wether we need to consider this point as
|
|
|
|
* part of a horizontal curve
|
|
|
|
* @return {Number} the winding number
|
2013-12-25 14:46:13 -05:00
|
|
|
*/
|
2013-12-29 07:21:08 -05:00
|
|
|
_getWinding: function(point, horizontal) {
|
2014-02-19 18:24:09 -05:00
|
|
|
return PathItem._getWinding(point, this._getMonoCurves(), horizontal);
|
2013-12-25 14:46:13 -05:00
|
|
|
},
|
|
|
|
|
2013-10-29 12:57:25 -04:00
|
|
|
_contains: function(point) {
|
|
|
|
// NOTE: point is reverse transformed by _matrix, so we don't need to
|
|
|
|
// apply here.
|
2013-11-29 14:26:38 -05:00
|
|
|
/*#*/ if (__options.nativeContains) {
|
2013-10-29 12:57:25 -04:00
|
|
|
// To compare with native canvas approach:
|
|
|
|
var ctx = CanvasProvider.getContext(1, 1);
|
|
|
|
// Abuse clip = true to get a shape for ctx.isPointInPath().
|
2013-12-10 07:18:21 -05:00
|
|
|
this._draw(ctx, new Base({ clip: true }));
|
2013-10-29 12:57:25 -04:00
|
|
|
var res = ctx.isPointInPath(point.x, point.y, this.getWindingRule());
|
|
|
|
CanvasProvider.release(ctx);
|
|
|
|
return res;
|
2013-11-29 14:26:38 -05:00
|
|
|
/*#*/ } else { // !__options.nativeContains
|
2013-10-29 12:57:25 -04:00
|
|
|
var winding = this._getWinding(point);
|
|
|
|
return !!(this.getWindingRule() === 'evenodd' ? winding & 1 : winding);
|
2013-11-29 14:26:38 -05:00
|
|
|
/*#*/ } // !__options.nativeContains
|
2013-12-29 07:25:48 -05:00
|
|
|
},
|
|
|
|
|
2014-02-19 19:56:49 -05:00
|
|
|
// Mess with indentation in order to get more line-space below...
|
|
|
|
statics: {
|
2013-12-29 07:25:48 -05:00
|
|
|
/**
|
|
|
|
* Private method for splitting a PathItem at the given intersections.
|
|
|
|
* The routine works for both self intersections and intersections
|
|
|
|
* between PathItems.
|
2014-02-19 09:46:57 -05:00
|
|
|
* @param {CurveLocation[]} intersections Array of CurveLocation objects
|
2013-12-29 07:25:48 -05:00
|
|
|
*/
|
|
|
|
_splitPath: function(intersections) {
|
2014-02-19 19:56:49 -05:00
|
|
|
var linearSegments;
|
|
|
|
|
|
|
|
function resetLinear() {
|
|
|
|
// Reset linear segments if they were part of a linear curve
|
|
|
|
// and if we are done with the entire curve.
|
2014-02-19 20:13:55 -05:00
|
|
|
for (var i = 0, l = linearSegments.length; i < l; i++) {
|
2014-02-19 19:56:49 -05:00
|
|
|
var segment = linearSegments[i];
|
2014-02-19 20:13:55 -05:00
|
|
|
// FIXME: Don't reset the appropriate handle if the intersection
|
|
|
|
// was on t == 0 && t == 1.
|
|
|
|
segment._handleOut.set(0, 0);
|
|
|
|
segment._handleIn.set(0, 0);
|
2014-02-19 19:56:49 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (var i = intersections.length - 1, curve, prevLoc; i >= 0; i--) {
|
|
|
|
var loc = intersections[i],
|
|
|
|
t = loc._parameter;
|
2013-12-29 07:25:48 -05:00
|
|
|
// Check if we are splitting same curve multiple times
|
2014-02-19 19:56:49 -05:00
|
|
|
if (prevLoc && prevLoc._curve === loc._curve) {
|
|
|
|
// Scale parameter after previous split.
|
|
|
|
t /= prevLoc._parameter;
|
2013-12-29 07:25:48 -05:00
|
|
|
} else {
|
2014-02-19 19:56:49 -05:00
|
|
|
if (linearSegments)
|
|
|
|
resetLinear();
|
|
|
|
curve = loc._curve;
|
|
|
|
linearSegments = curve.isLinear() && [];
|
2013-12-29 07:25:48 -05:00
|
|
|
}
|
2014-02-19 19:56:49 -05:00
|
|
|
var newCurve,
|
|
|
|
segment;
|
2013-12-29 07:25:48 -05:00
|
|
|
// Split the curve at t, while ignoring linearity of curves
|
2014-02-19 19:56:49 -05:00
|
|
|
if (newCurve = curve.divide(t, true, true)) {
|
|
|
|
segment = newCurve._segment1;
|
|
|
|
curve = newCurve.getPrevious();
|
2013-12-29 07:25:48 -05:00
|
|
|
} else {
|
2014-02-19 19:56:49 -05:00
|
|
|
segment = t < 0.5 ? curve._segment1 : curve._segment2;
|
2013-12-29 07:25:48 -05:00
|
|
|
}
|
|
|
|
// Link the new segment with the intersection on the other curve
|
2014-02-19 19:56:49 -05:00
|
|
|
segment._intersection = loc.getIntersection();
|
|
|
|
loc._segment = segment;
|
|
|
|
if (linearSegments)
|
|
|
|
linearSegments.push(segment);
|
|
|
|
prevLoc = loc;
|
2013-12-29 07:25:48 -05:00
|
|
|
}
|
2014-02-19 19:56:49 -05:00
|
|
|
if (linearSegments)
|
|
|
|
resetLinear();
|
2013-12-29 07:25:48 -05:00
|
|
|
},
|
|
|
|
|
2013-12-29 07:21:08 -05:00
|
|
|
/**
|
|
|
|
* Private static method that returns the winding contribution of the
|
2014-02-17 14:59:38 -05:00
|
|
|
* given point with respect to a given set of monotone curves.
|
2013-12-29 07:21:08 -05:00
|
|
|
*/
|
2014-02-19 09:46:28 -05:00
|
|
|
_getWinding: function _getWinding(point, curves, horizontal) {
|
2014-02-19 18:19:44 -05:00
|
|
|
var tolerance = /*#=*/ Numerical.TOLERANCE,
|
2013-12-29 07:21:08 -05:00
|
|
|
x = point.x,
|
|
|
|
y = point.y,
|
|
|
|
xAfter = x + tolerance,
|
|
|
|
xBefore = x - tolerance,
|
|
|
|
windLeft = 0,
|
|
|
|
windRight = 0,
|
|
|
|
roots = [],
|
|
|
|
abs = Math.abs;
|
|
|
|
// Absolutely horizontal curves may return wrong results, since
|
|
|
|
// the curves are monotonic in y direction and this is an
|
|
|
|
// indeterminate state.
|
|
|
|
if (horizontal) {
|
|
|
|
var yTop = -Infinity,
|
2014-02-19 18:19:44 -05:00
|
|
|
yBottom = Infinity;
|
|
|
|
// Find the closest top and bottom intercepts for the same vertical
|
|
|
|
// line.
|
|
|
|
for (var i = 0, l = curves.length; i < l; i++) {
|
2013-12-29 07:21:08 -05:00
|
|
|
v = curves[i];
|
|
|
|
if (Curve.solveCubic(v, 0, x, roots, 0, 1) > 0) {
|
2014-02-19 18:19:44 -05:00
|
|
|
for (var j = roots.length - 1; j >= 0; j--) {
|
|
|
|
var y0 = Curve.evaluate(v, roots[j], 0).y;
|
|
|
|
if (y0 > y + tolerance && y0 < yBottom) {
|
|
|
|
yBottom = y0;
|
|
|
|
} else if (y0 < y - tolerance && y0 > yTop) {
|
2013-12-29 07:21:08 -05:00
|
|
|
yTop = y0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Shift the point lying on the horizontal curves by
|
|
|
|
// half of closest top and bottom intercepts.
|
|
|
|
yTop = (yTop + y) / 2;
|
2014-02-19 18:19:44 -05:00
|
|
|
yBottom = (yBottom + y) / 2;
|
|
|
|
if (yTop > -Infinity)
|
|
|
|
windLeft = _getWinding(new Point(x, yTop), curves);
|
|
|
|
if (yBottom < Infinity)
|
|
|
|
windRight = _getWinding(new Point(x, yBottom), curves);
|
2013-12-29 07:21:08 -05:00
|
|
|
return Math.max(windLeft, windRight);
|
|
|
|
}
|
|
|
|
// Find the winding number for right hand side of the curve,
|
|
|
|
// inclusive of the curve itself, while tracing along its ±x direction.
|
2014-02-19 18:19:44 -05:00
|
|
|
for (var i = 0, l = curves.length; i < l; i++) {
|
|
|
|
var v = curves[i];
|
|
|
|
if (Curve.solveCubic(v, 1, y, roots, 0, 1 - tolerance) === 1) {
|
|
|
|
var t = roots[0],
|
|
|
|
x0 = Curve.evaluate(v, t, 0).x,
|
|
|
|
slope = Curve.evaluate(v, t, 1).y,
|
|
|
|
stationary = abs(slope) < tolerance && !Curve.isLinear(v)
|
|
|
|
// Take care of cases where the curve and the
|
|
|
|
// preceeding curve merely touches the ray towards
|
|
|
|
// +-x direction, but proceeds to the same side of
|
|
|
|
// the ray. This essentially is not a crossing.
|
|
|
|
// NOTE: The previous curve is stored at v[9],
|
2014-02-19 18:24:09 -05:00
|
|
|
// see Path#_getMonoCurves() for details.
|
2014-02-19 18:19:44 -05:00
|
|
|
|| t < tolerance
|
|
|
|
&& slope * Curve.evaluate(v[9], t, 1).y < 0;
|
|
|
|
if (!stationary) {
|
|
|
|
var winding = v[8];
|
|
|
|
if (x0 <= xBefore)
|
|
|
|
windLeft += winding;
|
|
|
|
if (x0 >= xAfter)
|
|
|
|
windRight += winding;
|
2013-12-29 07:21:08 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return Math.max(abs(windLeft), abs(windRight));
|
|
|
|
},
|
2013-12-29 07:29:54 -05:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Private method to trace closed contours from a set of segments according
|
2014-02-19 09:46:57 -05:00
|
|
|
* to a set of constraints—winding contribution and a custom operator.
|
2013-12-29 07:29:54 -05:00
|
|
|
*
|
2014-02-19 09:46:57 -05:00
|
|
|
* @param {Segment[]} segments Array of 'seed' segments for tracing closed
|
|
|
|
* contours
|
|
|
|
* @param {Function} the operator function that receives as argument the
|
|
|
|
* winding number contribution of a curve and returns a boolean value
|
|
|
|
* indicating whether the curve should be included in the final contour or
|
|
|
|
* not
|
|
|
|
* @return {Path[]} the contours traced
|
2013-12-29 07:29:54 -05:00
|
|
|
*/
|
2014-02-16 12:47:11 -05:00
|
|
|
_tracePaths: function(segments, operator, selfIx) {
|
2014-02-01 21:53:02 -05:00
|
|
|
// Utility function. Correctly returns entry and exit tangents of an
|
|
|
|
// intersection, even when the curve[s] are linear.
|
|
|
|
function getEntryExitTangents(seg) {
|
|
|
|
var c2 = seg.getCurve(),
|
2014-02-11 13:18:54 -05:00
|
|
|
c1 = c2.getPrevious(), t = 1e-3;
|
2014-02-02 16:36:00 -05:00
|
|
|
// Avoid zero length curves
|
|
|
|
c1 = c1.getLength() === 0 ? c1.getPrevious() : c1;
|
|
|
|
c2 = c2.getLength() === 0 ? c2.getNext() : c2;
|
|
|
|
var v1 = c1.getValues(),
|
2014-02-01 21:53:02 -05:00
|
|
|
v2 = c2.getValues(),
|
|
|
|
pnt = seg.getPoint(),
|
2014-02-11 13:18:54 -05:00
|
|
|
ret = [seg.getHandleIn(), seg.getHandleOut()];
|
|
|
|
if (ret[0].getLength() === 0) {
|
|
|
|
ret[0] = new Point(pnt.x - v1[2], pnt.y - v1[3]).normalize();
|
|
|
|
} else {
|
|
|
|
ret[0] = Curve.evaluate(v1, 1-t, 1).normalize(-1);
|
|
|
|
}
|
|
|
|
if (ret[1].getLength() === 0) {
|
|
|
|
ret[1] = new Point(pnt.x - v2[4], pnt.y - v2[5]).normalize();
|
|
|
|
} else {
|
|
|
|
ret[1] = Curve.evaluate(v2, t, 1).normalize();
|
|
|
|
}
|
2014-02-01 21:53:02 -05:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
// Choose a default operator which will return all contours
|
2014-02-17 14:59:38 -05:00
|
|
|
operator = operator || function() {
|
|
|
|
return true;
|
|
|
|
};
|
2014-02-01 21:53:02 -05:00
|
|
|
var seg, startSeg, startSegIx, i, j, len, path, ixOther, firstHandleIn,
|
|
|
|
c1, c3, c4, t1, tan, crv, ixOtherSeg, nextSeg, nextHandleIn,
|
|
|
|
nextHandleOut, direction, entryExitTangents,
|
|
|
|
// Tangents of all curves at an intersection, except the entry curve
|
|
|
|
crvTan = [{}, {}],
|
|
|
|
paths = [];
|
|
|
|
for (i = 0, len = segments.length; i < len; i++) {
|
|
|
|
startSeg = seg = segments[i];
|
|
|
|
if (seg._visited || !operator(seg._winding))
|
|
|
|
continue;
|
|
|
|
// Initialise a new path chain with the seed segment.
|
2014-02-17 14:36:18 -05:00
|
|
|
path = new Path();
|
2014-02-01 21:53:02 -05:00
|
|
|
ixOther = seg._intersection;
|
|
|
|
startSegIx = ixOther ? ixOther._segment : null;
|
|
|
|
firstHandleIn = null;
|
|
|
|
direction = 1;
|
|
|
|
do {
|
|
|
|
nextHandleIn = direction > 0 ? seg._handleIn : seg._handleOut;
|
|
|
|
nextHandleOut = direction > 0 ? seg._handleOut : seg._handleIn;
|
|
|
|
ixOther = seg._intersection;
|
|
|
|
// If the intersection segment is valid, try switching to
|
|
|
|
// it, with an appropriate direction to continue traversal.
|
2014-02-17 14:59:38 -05:00
|
|
|
// Else, stay on the same contour.
|
|
|
|
if ((!operator(seg._winding) || selfIx) && ixOther
|
|
|
|
&& (ixOtherSeg = ixOther._segment)
|
|
|
|
&& ixOtherSeg !== startSeg && firstHandleIn) {
|
2014-02-01 21:53:02 -05:00
|
|
|
entryExitTangents = getEntryExitTangents(seg);
|
|
|
|
c1 = seg.getCurve();
|
|
|
|
if (direction < 1) {
|
|
|
|
entryExitTangents.reverse();
|
|
|
|
} else {
|
|
|
|
c1 = c1.getPrevious();
|
|
|
|
}
|
|
|
|
t1 = entryExitTangents[0];
|
|
|
|
entryExitTangents = getEntryExitTangents(ixOtherSeg);
|
|
|
|
c4 = crvTan[1].c = ixOtherSeg.getCurve();
|
|
|
|
c3 = crvTan[0].c = c4.getPrevious();
|
2014-02-02 16:36:00 -05:00
|
|
|
// Avoid zero length curves
|
|
|
|
c3 = crvTan[0].c = c3.getLength() === 0 ? c3.getPrevious() : c3;
|
|
|
|
c4 = crvTan[1].c = c4.getLength() === 0 ? c4.getNext() : c4;
|
2014-02-01 21:53:02 -05:00
|
|
|
crvTan[0].t = entryExitTangents[0];
|
|
|
|
crvTan[1].t = entryExitTangents[1];
|
|
|
|
// cross product of the entry and exit tangent vectors at
|
|
|
|
// the intersection, will let us select the correct countour
|
|
|
|
// to traverse next.
|
|
|
|
for (j = 0; j < 2; j++) {
|
|
|
|
tan = crvTan[j].t;
|
|
|
|
crvTan[j].w = t1.x * tan.y - tan.x * t1.y;
|
|
|
|
}
|
2014-02-04 14:57:24 -05:00
|
|
|
// Do not attempt to switch contours if we aren't absolutely
|
|
|
|
// sure that there is a possible candidate.
|
2014-02-11 13:18:54 -05:00
|
|
|
if (crvTan[0].w * crvTan[1].w !== 0) {
|
2014-02-17 14:59:38 -05:00
|
|
|
crvTan.sort(function(a, b) {
|
|
|
|
// Compare tangents to sort curves counter clockwise
|
|
|
|
return a.w - b.w;
|
|
|
|
});
|
2014-02-02 16:36:00 -05:00
|
|
|
j = 0;
|
|
|
|
do {
|
|
|
|
crv = crvTan[j++].c;
|
2014-02-04 14:57:24 -05:00
|
|
|
nextSeg = crv.getSegment1();
|
|
|
|
direction = crv === c3 ? -1 : 1;
|
|
|
|
} while (j < 2 && !operator(nextSeg._winding));
|
2014-02-02 16:36:00 -05:00
|
|
|
} else {
|
|
|
|
nextSeg = null;
|
|
|
|
}
|
2014-02-04 14:57:24 -05:00
|
|
|
// If we didn't manage to find a suitable direction for next
|
|
|
|
// contour to traverse, stay on the same contour.
|
|
|
|
if (!nextSeg || nextSeg && ((nextSeg._visited &&
|
|
|
|
seg.getPath() !== nextSeg.getPath()) ||
|
2014-02-03 15:55:01 -05:00
|
|
|
!operator(nextSeg._winding))) {
|
2014-02-02 16:36:00 -05:00
|
|
|
direction = 1;
|
|
|
|
} else {
|
|
|
|
// Switch to the intersection segment.
|
|
|
|
seg._visited = ixOtherSeg._visited;
|
|
|
|
seg = ixOtherSeg;
|
|
|
|
if (nextSeg._visited)
|
2014-02-01 21:53:02 -05:00
|
|
|
direction = 1;
|
2014-02-02 16:36:00 -05:00
|
|
|
}
|
2014-02-01 21:53:02 -05:00
|
|
|
nextHandleOut = direction > 0 ? seg._handleOut : seg._handleIn;
|
|
|
|
}
|
2014-02-17 14:59:38 -05:00
|
|
|
// Add the current segment to the path, and mark the added
|
|
|
|
// segment as visited.
|
2014-02-01 21:53:02 -05:00
|
|
|
if (!firstHandleIn) {
|
|
|
|
firstHandleIn = nextHandleIn;
|
|
|
|
nextHandleIn = null;
|
|
|
|
}
|
2014-02-17 14:36:18 -05:00
|
|
|
path.add(new Segment(seg._point, nextHandleIn, nextHandleOut));
|
2014-02-01 21:53:02 -05:00
|
|
|
seg._visited = true;
|
|
|
|
// Move to the next segment according to the traversal direction
|
|
|
|
seg = direction > 0 ? seg.getNext() : seg. getPrevious();
|
2014-02-17 14:59:38 -05:00
|
|
|
} while (seg && seg !== startSeg && seg !== startSegIx
|
|
|
|
&& !seg._visited
|
|
|
|
&& (seg._intersection || operator(seg._winding)));
|
|
|
|
// Finish with closing the paths if necessary, correctly linking up
|
|
|
|
// curves etc.
|
|
|
|
if (seg && (seg === startSeg || seg === startSegIx)) {
|
|
|
|
path.firstSegment.setHandleIn(seg === startSegIx
|
|
|
|
? startSegIx._handleIn
|
|
|
|
: seg._handleIn);
|
2014-02-01 21:53:02 -05:00
|
|
|
path.setClosed(true);
|
|
|
|
} else {
|
|
|
|
path.lastSegment._handleOut.set(0, 0);
|
|
|
|
}
|
2014-02-17 14:59:38 -05:00
|
|
|
// Add the path to the result.
|
2014-02-01 21:53:02 -05:00
|
|
|
// Try to avoid stray segments and incomplete paths.
|
2014-02-17 14:59:38 -05:00
|
|
|
if (path.closed && path.segments.length
|
|
|
|
|| path.segments.length > 2
|
|
|
|
|| (path.closed && path.segments.length === 2
|
|
|
|
&& (!path.getCurves()[0].isLinear()
|
|
|
|
|| !path.getCurves()[1].isLinear()))) {
|
2014-02-01 21:53:02 -05:00
|
|
|
paths.push(path);
|
|
|
|
} else {
|
|
|
|
path.remove();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return paths;
|
|
|
|
},
|
|
|
|
|
2014-02-01 11:07:24 -05:00
|
|
|
_conditionIntersections: function(locations, expand) {
|
|
|
|
function compare(loc1, loc2) {
|
|
|
|
var path1 = loc1.getPath(),
|
|
|
|
path2 = loc2.getPath();
|
|
|
|
return path1 === path2
|
|
|
|
// We can add parameter (0 <= t <= 1) to index
|
|
|
|
// (a integer) to compare both at the same time
|
|
|
|
? (loc1.getIndex() + loc1.getParameter())
|
|
|
|
- (loc2.getIndex() + loc2.getParameter())
|
|
|
|
// Sort by path id to group all locations on the same path.
|
|
|
|
: path1._id - path2._id;
|
|
|
|
}
|
|
|
|
// Remove duplicate intersections near curve endings
|
|
|
|
var loc, locNext,
|
2014-02-17 14:59:38 -05:00
|
|
|
tolerance = /*#=*/ Numerical.EPSILON,
|
2014-02-01 11:07:24 -05:00
|
|
|
tolerance1 = 1 - tolerance,
|
|
|
|
abs = Math.abs;
|
|
|
|
// Merge intersections very close to the end of a curve to the
|
|
|
|
// begining of the next curve
|
|
|
|
for (var i = locations.length-1; i >= 0; i--) {
|
|
|
|
loc = locations[i];
|
|
|
|
locNext = loc._curve.getNext();
|
|
|
|
if (loc._parameter >= tolerance1 && locNext) {
|
|
|
|
loc._parameter = 0;
|
|
|
|
loc._curve = locNext;
|
|
|
|
}
|
|
|
|
locNext = loc._curve2.getNext();
|
|
|
|
if (loc._parameter2 >= tolerance1 && locNext) {
|
|
|
|
loc._parameter2 = 0;
|
|
|
|
loc._curve2 = locNext;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (locations.length > 1) {
|
|
|
|
locations.sort(compare);
|
|
|
|
for (var length1 = locations.length - 1, i = length1; i >= 0; i--) {
|
|
|
|
loc = locations[i];
|
|
|
|
locNext = (i === 0)? locations[length1] : locations[i-1];
|
|
|
|
if (abs(loc._parameter - locNext._parameter) < tolerance &&
|
|
|
|
loc._curve === locNext._curve &&
|
|
|
|
abs(loc._parameter2 - locNext._parameter2) < tolerance &&
|
|
|
|
loc._curve2 === locNext._curve2) {
|
|
|
|
locations.splice(i, 1);
|
|
|
|
--length1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (expand) {
|
|
|
|
for (var i = locations.length-1; i >= 0; i--) {
|
|
|
|
loc = locations[i];
|
|
|
|
locations.push(loc.getIntersection());
|
|
|
|
}
|
|
|
|
locations.sort(compare);
|
|
|
|
}
|
|
|
|
return locations;
|
|
|
|
},
|
2014-02-19 19:56:49 -05:00
|
|
|
}
|
2013-04-29 15:36:12 -04:00
|
|
|
|
2011-06-16 17:07:00 -04:00
|
|
|
/**
|
|
|
|
* Smooth bezier curves without changing the amount of segments or their
|
|
|
|
* points, by only smoothing and adjusting their handle points, for both
|
|
|
|
* open ended and closed paths.
|
2011-06-30 06:01:51 -04:00
|
|
|
*
|
2011-06-16 18:37:45 -04:00
|
|
|
* @name PathItem#smooth
|
|
|
|
* @function
|
2011-06-30 06:01:51 -04:00
|
|
|
*
|
2011-06-16 17:07:00 -04:00
|
|
|
* @example {@paperscript}
|
|
|
|
* // Smoothing a closed shape:
|
2011-06-30 06:01:51 -04:00
|
|
|
*
|
2011-06-16 17:07:00 -04:00
|
|
|
* // Create a rectangular path with its top-left point at
|
|
|
|
* // {x: 30, y: 25} and a size of {width: 50, height: 50}:
|
|
|
|
* var path = new Path.Rectangle(new Point(30, 25), new Size(50, 50));
|
|
|
|
* path.strokeColor = 'black';
|
2011-06-30 06:01:51 -04:00
|
|
|
*
|
2011-06-16 17:07:00 -04:00
|
|
|
* // Select the path, so we can see its handles:
|
2011-11-12 10:45:33 -05:00
|
|
|
* path.fullySelected = true;
|
2011-06-30 06:01:51 -04:00
|
|
|
*
|
2011-06-16 17:07:00 -04:00
|
|
|
* // Create a copy of the path and move it 100pt to the right:
|
|
|
|
* var copy = path.clone();
|
|
|
|
* copy.position.x += 100;
|
2011-06-30 06:01:51 -04:00
|
|
|
*
|
2011-06-16 17:07:00 -04:00
|
|
|
* // Smooth the segments of the copy:
|
|
|
|
* copy.smooth();
|
2011-06-30 06:01:51 -04:00
|
|
|
*
|
2011-06-16 17:07:00 -04:00
|
|
|
* @example {@paperscript height=220}
|
|
|
|
* var path = new Path();
|
|
|
|
* path.strokeColor = 'black';
|
2011-06-30 06:01:51 -04:00
|
|
|
*
|
2011-06-16 17:07:00 -04:00
|
|
|
* path.add(new Point(30, 50));
|
2011-06-30 06:01:51 -04:00
|
|
|
*
|
2011-06-16 17:07:00 -04:00
|
|
|
* var y = 5;
|
|
|
|
* var x = 3;
|
2011-06-30 06:01:51 -04:00
|
|
|
*
|
2011-06-16 17:07:00 -04:00
|
|
|
* for (var i = 0; i < 28; i++) {
|
|
|
|
* y *= -1.1;
|
|
|
|
* x *= 1.1;
|
|
|
|
* path.lineBy(x, y);
|
|
|
|
* }
|
2011-06-30 06:01:51 -04:00
|
|
|
*
|
2011-06-16 17:07:00 -04:00
|
|
|
* // Create a copy of the path and move it 100pt down:
|
|
|
|
* var copy = path.clone();
|
|
|
|
* copy.position.y += 120;
|
2011-06-30 06:01:51 -04:00
|
|
|
*
|
2011-06-16 17:07:00 -04:00
|
|
|
* // Set its stroke color to red:
|
|
|
|
* copy.strokeColor = 'red';
|
2011-06-30 06:01:51 -04:00
|
|
|
*
|
2011-06-16 17:07:00 -04:00
|
|
|
* // Smooth the segments of the copy:
|
|
|
|
* copy.smooth();
|
|
|
|
*/
|
|
|
|
|
2011-06-14 18:04:32 -04:00
|
|
|
/**
|
|
|
|
* {@grouptitle Postscript Style Drawing Commands}
|
2011-06-30 06:01:51 -04:00
|
|
|
*
|
2012-10-05 19:09:15 -04:00
|
|
|
* On a normal empty {@link Path}, the point is simply added as the path's
|
|
|
|
* first segment. If called on a {@link CompoundPath}, a new {@link Path} is
|
|
|
|
* created as a child and the point is added as its first segment.
|
2011-06-30 06:01:51 -04:00
|
|
|
*
|
2011-06-14 18:04:32 -04:00
|
|
|
* @name PathItem#moveTo
|
2011-06-16 17:07:00 -04:00
|
|
|
* @function
|
|
|
|
* @param {Point} point
|
|
|
|
*/
|
|
|
|
|
2011-12-23 16:47:10 -05:00
|
|
|
// DOCS: Document #lineTo()
|
2011-06-16 17:07:00 -04:00
|
|
|
/**
|
|
|
|
* @name PathItem#lineTo
|
|
|
|
* @function
|
|
|
|
* @param {Point} point
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Adds a cubic bezier curve to the path, defined by two handles and a to
|
|
|
|
* point.
|
2011-06-30 06:01:51 -04:00
|
|
|
*
|
2011-06-16 17:07:00 -04:00
|
|
|
* @name PathItem#cubicCurveTo
|
|
|
|
* @function
|
|
|
|
* @param {Point} handle1
|
|
|
|
* @param {Point} handle2
|
|
|
|
* @param {Point} to
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Adds a quadratic bezier curve to the path, defined by a handle and a to
|
|
|
|
* point.
|
2011-06-30 06:01:51 -04:00
|
|
|
*
|
2011-06-16 17:07:00 -04:00
|
|
|
* @name PathItem#quadraticCurveTo
|
|
|
|
* @function
|
|
|
|
* @param {Point} handle
|
|
|
|
* @param {Point} to
|
|
|
|
*/
|
|
|
|
|
2011-12-23 16:47:10 -05:00
|
|
|
// DOCS: Document PathItem#curveTo() 'paramater' param.
|
2011-06-16 17:07:00 -04:00
|
|
|
/**
|
|
|
|
* Draws a curve from the position of the last segment point in the path
|
|
|
|
* that goes through the specified {@code through} point, to the specified
|
|
|
|
* {@code to} point by adding one segment to the path.
|
2011-06-30 06:01:51 -04:00
|
|
|
*
|
2011-06-16 17:07:00 -04:00
|
|
|
* @name PathItem#curveTo
|
|
|
|
* @function
|
|
|
|
* @param {Point} through the point through which the curve should go
|
|
|
|
* @param {Point} to the point where the curve should end
|
|
|
|
* @param {Number} [parameter=0.5]
|
2011-06-30 06:01:51 -04:00
|
|
|
*
|
2011-06-16 17:07:00 -04:00
|
|
|
* @example {@paperscript height=300}
|
2013-03-03 13:47:32 -05:00
|
|
|
* // Interactive example. Move your mouse around the view below:
|
2011-06-30 06:01:51 -04:00
|
|
|
*
|
2011-06-16 17:07:00 -04:00
|
|
|
* var myPath;
|
2013-03-03 13:47:32 -05:00
|
|
|
* function onMouseMove(event) {
|
2011-06-16 17:07:00 -04:00
|
|
|
* // If we created a path before, remove it:
|
|
|
|
* if (myPath) {
|
2013-03-03 13:47:32 -05:00
|
|
|
* myPath.remove();
|
2011-06-16 17:07:00 -04:00
|
|
|
* }
|
2013-04-29 15:36:12 -04:00
|
|
|
*
|
2011-06-16 17:07:00 -04:00
|
|
|
* // Create a new path and add a segment point to it
|
|
|
|
* // at {x: 150, y: 150):
|
|
|
|
* myPath = new Path();
|
|
|
|
* myPath.add(150, 150);
|
2013-04-29 15:36:12 -04:00
|
|
|
*
|
2011-06-16 17:07:00 -04:00
|
|
|
* // Draw a curve through the position of the mouse to 'toPoint'
|
|
|
|
* var toPoint = new Point(350, 150);
|
|
|
|
* myPath.curveTo(event.point, toPoint);
|
2013-04-29 15:36:12 -04:00
|
|
|
*
|
2011-06-16 17:07:00 -04:00
|
|
|
* // Select the path, so we can see its segments:
|
|
|
|
* myPath.selected = true;
|
|
|
|
* }
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Draws an arc from the position of the last segment point in the path that
|
|
|
|
* goes through the specified {@code through} point, to the specified
|
|
|
|
* {@code to} point by adding one or more segments to the path.
|
2011-06-30 06:01:51 -04:00
|
|
|
*
|
2011-06-16 17:07:00 -04:00
|
|
|
* @name PathItem#arcTo
|
|
|
|
* @function
|
|
|
|
* @param {Point} through the point where the arc should pass through
|
|
|
|
* @param {Point} to the point where the arc should end
|
2011-06-30 06:01:51 -04:00
|
|
|
*
|
2011-06-16 17:07:00 -04:00
|
|
|
* @example {@paperscript}
|
|
|
|
* var path = new Path();
|
|
|
|
* path.strokeColor = 'black';
|
2011-06-30 06:01:51 -04:00
|
|
|
*
|
2011-06-16 17:07:00 -04:00
|
|
|
* var firstPoint = new Point(30, 75);
|
|
|
|
* path.add(firstPoint);
|
2011-06-30 06:01:51 -04:00
|
|
|
*
|
2011-06-16 17:07:00 -04:00
|
|
|
* // The point through which we will create the arc:
|
|
|
|
* var throughPoint = new Point(40, 40);
|
2011-06-30 06:01:51 -04:00
|
|
|
*
|
2011-06-16 17:07:00 -04:00
|
|
|
* // The point at which the arc will end:
|
|
|
|
* var toPoint = new Point(130, 75);
|
2011-06-30 06:01:51 -04:00
|
|
|
*
|
2011-06-16 17:07:00 -04:00
|
|
|
* // Draw an arc through 'throughPoint' to 'toPoint'
|
|
|
|
* path.arcTo(throughPoint, toPoint);
|
2011-06-30 06:01:51 -04:00
|
|
|
*
|
2011-06-16 17:07:00 -04:00
|
|
|
* // Add a red circle shaped path at the position of 'throughPoint':
|
|
|
|
* var circle = new Path.Circle(throughPoint, 3);
|
|
|
|
* circle.fillColor = 'red';
|
2011-06-30 06:01:51 -04:00
|
|
|
*
|
2011-06-16 17:07:00 -04:00
|
|
|
* @example {@paperscript height=300}
|
|
|
|
* // Interactive example. Click and drag in the view below:
|
2011-06-30 06:01:51 -04:00
|
|
|
*
|
2011-06-16 17:07:00 -04:00
|
|
|
* var myPath;
|
|
|
|
* function onMouseDrag(event) {
|
|
|
|
* // If we created a path before, remove it:
|
|
|
|
* if (myPath) {
|
|
|
|
* myPath.remove();
|
|
|
|
* }
|
2011-06-30 06:01:51 -04:00
|
|
|
*
|
2011-06-16 17:07:00 -04:00
|
|
|
* // Create a new path and add a segment point to it
|
|
|
|
* // at {x: 150, y: 150):
|
|
|
|
* myPath = new Path();
|
|
|
|
* myPath.add(150, 150);
|
2011-06-30 06:01:51 -04:00
|
|
|
*
|
2011-06-16 17:07:00 -04:00
|
|
|
* // Draw an arc through the position of the mouse to 'toPoint'
|
|
|
|
* var toPoint = new Point(350, 150);
|
|
|
|
* myPath.arcTo(event.point, toPoint);
|
2011-06-30 06:01:51 -04:00
|
|
|
*
|
2011-06-16 17:07:00 -04:00
|
|
|
* // Select the path, so we can see its segments:
|
|
|
|
* myPath.selected = true;
|
|
|
|
* }
|
2011-06-30 06:01:51 -04:00
|
|
|
*
|
2011-06-16 17:07:00 -04:00
|
|
|
* // When the mouse is released, deselect the path
|
|
|
|
* // and fill it with black.
|
|
|
|
* function onMouseUp(event) {
|
|
|
|
* myPath.selected = false;
|
|
|
|
* myPath.fillColor = 'black';
|
|
|
|
* }
|
|
|
|
*/
|
|
|
|
/**
|
|
|
|
* Draws an arc from the position of the last segment point in the path to
|
|
|
|
* the specified point by adding one or more segments to the path.
|
2011-06-30 06:01:51 -04:00
|
|
|
*
|
2011-06-16 17:07:00 -04:00
|
|
|
* @name PathItem#arcTo
|
|
|
|
* @function
|
2011-06-16 17:38:58 -04:00
|
|
|
* @param {Point} to the point where the arc should end
|
2011-06-16 17:07:00 -04:00
|
|
|
* @param {Boolean} [clockwise=true] specifies whether the arc should be
|
|
|
|
* drawn in clockwise direction.
|
2011-06-30 06:01:51 -04:00
|
|
|
*
|
2011-06-16 17:07:00 -04:00
|
|
|
* @example {@paperscript}
|
|
|
|
* var path = new Path();
|
|
|
|
* path.strokeColor = 'black';
|
2011-06-30 06:01:51 -04:00
|
|
|
*
|
2011-06-16 17:07:00 -04:00
|
|
|
* path.add(new Point(30, 75));
|
|
|
|
* path.arcTo(new Point(130, 75));
|
2011-06-30 06:01:51 -04:00
|
|
|
*
|
2011-06-16 17:07:00 -04:00
|
|
|
* var path2 = new Path();
|
|
|
|
* path2.strokeColor = 'red';
|
|
|
|
* path2.add(new Point(180, 25));
|
2011-06-30 06:01:51 -04:00
|
|
|
*
|
2011-06-16 17:07:00 -04:00
|
|
|
* // To draw an arc in anticlockwise direction,
|
|
|
|
* // we pass 'false' as the second argument to arcTo:
|
|
|
|
* path2.arcTo(new Point(280, 25), false);
|
2011-06-30 06:01:51 -04:00
|
|
|
*
|
2011-06-16 17:07:00 -04:00
|
|
|
* @example {@paperscript height=300}
|
|
|
|
* // Interactive example. Click and drag in the view below:
|
|
|
|
* var myPath;
|
2011-06-30 06:01:51 -04:00
|
|
|
*
|
2011-06-16 17:07:00 -04:00
|
|
|
* // The mouse has to move at least 20 points before
|
|
|
|
* // the next mouse drag event is fired:
|
|
|
|
* tool.minDistance = 20;
|
2011-06-30 06:01:51 -04:00
|
|
|
*
|
2011-06-16 17:07:00 -04:00
|
|
|
* // When the user clicks, create a new path and add
|
|
|
|
* // the current mouse position to it as its first segment:
|
|
|
|
* function onMouseDown(event) {
|
|
|
|
* myPath = new Path();
|
|
|
|
* myPath.strokeColor = 'black';
|
|
|
|
* myPath.add(event.point);
|
|
|
|
* }
|
2011-06-30 06:01:51 -04:00
|
|
|
*
|
2011-06-16 17:07:00 -04:00
|
|
|
* // On each mouse drag event, draw an arc to the current
|
|
|
|
* // position of the mouse:
|
|
|
|
* function onMouseDrag(event) {
|
|
|
|
* myPath.arcTo(event.point);
|
|
|
|
* }
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Closes the path. When closed, Paper.js connects the first and last
|
|
|
|
* segments.
|
2011-06-30 06:01:51 -04:00
|
|
|
*
|
2011-06-16 17:07:00 -04:00
|
|
|
* @name PathItem#closePath
|
|
|
|
* @function
|
|
|
|
* @see Path#closed
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* {@grouptitle Relative Drawing Commands}
|
2011-06-30 06:01:51 -04:00
|
|
|
*
|
2011-06-16 17:07:00 -04:00
|
|
|
* If called on a {@link CompoundPath}, a new {@link Path} is created as a
|
2012-10-05 19:09:15 -04:00
|
|
|
* child and a point is added as its first segment relative to the
|
2011-06-16 17:07:00 -04:00
|
|
|
* position of the last segment of the current path.
|
2011-06-30 06:01:51 -04:00
|
|
|
*
|
2011-06-16 17:07:00 -04:00
|
|
|
* @name PathItem#moveBy
|
|
|
|
* @function
|
2013-10-29 20:43:55 -04:00
|
|
|
* @param {Point} to
|
2011-06-16 17:07:00 -04:00
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Adds a segment relative to the last segment point of the path.
|
2011-06-30 06:01:51 -04:00
|
|
|
*
|
2011-06-16 17:07:00 -04:00
|
|
|
* @name PathItem#lineBy
|
|
|
|
* @function
|
2013-10-29 20:43:55 -04:00
|
|
|
* @param {Point} to the vector which is added to the position of the last
|
|
|
|
* segment of the path, to get to the position of the new segment.
|
2011-06-30 06:01:51 -04:00
|
|
|
*
|
2011-06-16 17:07:00 -04:00
|
|
|
* @example {@paperscript}
|
|
|
|
* var path = new Path();
|
|
|
|
* path.strokeColor = 'black';
|
2011-06-30 06:01:51 -04:00
|
|
|
*
|
2011-06-16 17:07:00 -04:00
|
|
|
* // Add a segment at {x: 50, y: 50}
|
|
|
|
* path.add(25, 25);
|
2011-06-30 06:01:51 -04:00
|
|
|
*
|
2011-06-16 17:07:00 -04:00
|
|
|
* // Add a segment relative to the last segment of the path.
|
|
|
|
* // 50 in x direction and 0 in y direction, becomes {x: 75, y: 25}
|
|
|
|
* path.lineBy(50, 0);
|
2011-06-30 06:01:51 -04:00
|
|
|
*
|
2011-06-16 17:07:00 -04:00
|
|
|
* // 0 in x direction and 50 in y direction, becomes {x: 75, y: 75}
|
|
|
|
* path.lineBy(0, 50);
|
2011-06-30 06:01:51 -04:00
|
|
|
*
|
2011-06-16 17:07:00 -04:00
|
|
|
* @example {@paperscript height=300}
|
|
|
|
* // Drawing a spiral using lineBy:
|
|
|
|
* var path = new Path();
|
|
|
|
* path.strokeColor = 'black';
|
2011-06-30 06:01:51 -04:00
|
|
|
*
|
2011-06-16 17:07:00 -04:00
|
|
|
* // Add the first segment at {x: 50, y: 50}
|
|
|
|
* path.add(view.center);
|
2011-06-30 06:01:51 -04:00
|
|
|
*
|
2011-06-16 17:07:00 -04:00
|
|
|
* // Loop 500 times:
|
|
|
|
* for (var i = 0; i < 500; i++) {
|
|
|
|
* // Create a vector with an ever increasing length
|
|
|
|
* // and an angle in increments of 45 degrees
|
|
|
|
* var vector = new Point({
|
|
|
|
* angle: i * 45,
|
|
|
|
* length: i / 2
|
|
|
|
* });
|
|
|
|
* // Add the vector relatively to the last segment point:
|
|
|
|
* path.lineBy(vector);
|
|
|
|
* }
|
2011-06-30 06:01:51 -04:00
|
|
|
*
|
2011-06-16 17:07:00 -04:00
|
|
|
* // Smooth the handles of the path:
|
|
|
|
* path.smooth();
|
2011-06-30 06:01:51 -04:00
|
|
|
*
|
2011-06-16 17:07:00 -04:00
|
|
|
* // Uncomment the following line and click on 'run' to see
|
|
|
|
* // the construction of the path:
|
|
|
|
* // path.selected = true;
|
|
|
|
*/
|
|
|
|
|
2011-12-23 16:47:10 -05:00
|
|
|
// DOCS: Document Path#curveBy()
|
2011-06-16 17:07:00 -04:00
|
|
|
/**
|
|
|
|
* @name PathItem#curveBy
|
|
|
|
* @function
|
2013-10-29 20:43:55 -04:00
|
|
|
* @param {Point} through
|
|
|
|
* @param {Point} to
|
2011-06-16 17:07:00 -04:00
|
|
|
* @param {Number} [parameter=0.5]
|
|
|
|
*/
|
|
|
|
|
2013-11-24 19:04:51 -05:00
|
|
|
// DOCS: Document Path#cubicCurveBy()
|
|
|
|
/**
|
|
|
|
* @name PathItem#cubicCurveBy
|
|
|
|
* @function
|
|
|
|
* @param {Point} handle1
|
|
|
|
* @param {Point} handle2
|
|
|
|
* @param {Point} to
|
|
|
|
*/
|
2013-10-29 20:43:55 -04:00
|
|
|
|
2013-11-24 19:04:51 -05:00
|
|
|
// DOCS: Document Path#quadraticCurveBy()
|
|
|
|
/**
|
|
|
|
* @name PathItem#quadraticCurveBy
|
|
|
|
* @function
|
|
|
|
* @param {Point} handle
|
|
|
|
* @param {Point} to
|
|
|
|
*/
|
2013-10-29 20:43:55 -04:00
|
|
|
|
2013-11-24 19:04:51 -05:00
|
|
|
// DOCS: Document Path#arcBy(through, to)
|
2011-06-16 17:07:00 -04:00
|
|
|
/**
|
|
|
|
* @name PathItem#arcBy
|
|
|
|
* @function
|
2013-10-29 20:43:55 -04:00
|
|
|
* @param {Point} through
|
|
|
|
* @param {Point} to
|
2011-06-14 18:04:32 -04:00
|
|
|
*/
|
2013-11-24 19:04:51 -05:00
|
|
|
|
|
|
|
// DOCS: Document Path#arcBy(to, clockwise)
|
|
|
|
/**
|
|
|
|
* @name PathItem#arcBy
|
|
|
|
* @function
|
|
|
|
* @param {Point} to
|
|
|
|
* @param {Boolean} [clockwise=true]
|
|
|
|
*/
|
2011-02-13 11:26:24 -05:00
|
|
|
});
|