mirror of
https://github.com/scratchfoundation/paper.js.git
synced 2025-01-10 06:41:59 -05:00
cdaab794f2
* 'master' of https://github.com/paperjs/paper.js: (56 commits) Fix regressions in Raster caused by faulty merge. Store data in _data rather than on the image itself. Clean up @trankek's fix for node a bit. Explain compact = true in Project#_serialize() Properly fix issue with Project#exportJSON() creating separate projects on import. Update straps.js to version 1.1.0 Rename options.server to options.node Remove all create() constructors for basic types since new constructors are now faster. Make sure project we import into is active. Unbox project data in Project#importJSON(), as we don't want to create a new project object. Remove need for _needsRedraw() calls by replacing it with a boolean flag. Apply "pending" matrix in group when it receives content. Move main Item insertion code from #insertChild() to #insertChildren(). Fix documentation warning. Handle exporting of Numerical and PaperScript in export.js Update straps.js to latest version. Accessors cannot define the writable property. Properly export Numerical and PaperScript again. Only reset matrices in Groups when it could actually be applied to the content. Set options.stats = false for build.sh and Node.js ...
504 lines
14 KiB
JavaScript
504 lines
14 KiB
JavaScript
/*
|
|
* Paper.js - The Swiss Army Knife of Vector Graphics Scripting.
|
|
* http://paperjs.org/
|
|
*
|
|
* Copyright (c) 2011 - 2013, Juerg Lehni & Jonathan Puckey
|
|
* http://lehni.org/ & http://jonathanpuckey.com/
|
|
*
|
|
* Distributed under the MIT license. See LICENSE file for details.
|
|
*
|
|
* All rights reserved.
|
|
*/
|
|
|
|
/**
|
|
* @name PathItem
|
|
*
|
|
* @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}.
|
|
*
|
|
* @extends Item
|
|
*/
|
|
var PathItem = Item.extend(/** @lends PathItem# */{
|
|
initialize: function PathItem() {
|
|
Item.apply(this, arguments);
|
|
},
|
|
|
|
/**
|
|
* Returns all intersections between two {@link PathItem} items as an array
|
|
* of {@link CurveLocation} objects. {@link CompoundPath} items are also
|
|
* supported.
|
|
*
|
|
* @param {PathItem} path the other item to find the intersections to.
|
|
* @return {CurveLocation[]} the locations of all intersection between the
|
|
* paths
|
|
* @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';
|
|
*
|
|
* var secondPath = path.clone();
|
|
* var intersectionGroup = new Group();
|
|
*
|
|
* function onFrame(event) {
|
|
* secondPath.rotate(3);
|
|
*
|
|
* var intersections = path.getIntersections(secondPath);
|
|
* intersectionGroup.removeChildren();
|
|
*
|
|
* for (var i = 0; i < intersections.length; i++) {
|
|
* var intersectionPath = new Path.Circle({
|
|
* center: intersections[i].point,
|
|
* radius: 4,
|
|
* fillColor: 'red'
|
|
* });
|
|
* intersectionGroup.addChild(intersectionPath);
|
|
* }
|
|
* }
|
|
*/
|
|
getIntersections: function(path) {
|
|
// First check the bounds of the two paths. If they don't intersect,
|
|
// we don't need to iterate through their curves.
|
|
if (!this.getBounds().touches(path.getBounds()))
|
|
return [];
|
|
var locations = [],
|
|
curves1 = this.getCurves(),
|
|
curves2 = path.getCurves(),
|
|
length2 = curves2.length,
|
|
values2 = [];
|
|
for (var i = 0; i < length2; i++)
|
|
values2[i] = curves2[i].getValues();
|
|
for (var i = 0, l = curves1.length; i < l; i++) {
|
|
var curve1 = curves1[i],
|
|
values1 = curve1.getValues();
|
|
for (var j = 0; j < length2; j++)
|
|
Curve.getIntersections(values1, values2[j], curve1, curves2[j],
|
|
locations);
|
|
}
|
|
return locations;
|
|
},
|
|
|
|
setPathData: function(data) {
|
|
// This is a very compact SVG Path Data parser that works both for Path
|
|
// and CompoundPath.
|
|
|
|
var parts = data.match(/[a-z][^a-z]*/ig),
|
|
coords,
|
|
relative = false,
|
|
control,
|
|
current = new Point(); // the current position
|
|
|
|
function getCoord(index, coord, update) {
|
|
var val = parseFloat(coords[index]);
|
|
if (relative)
|
|
val += current[coord];
|
|
if (update)
|
|
current[coord] = val;
|
|
return val;
|
|
}
|
|
|
|
function getPoint(index, update) {
|
|
return new Point(
|
|
getCoord(index, 'x', update),
|
|
getCoord(index + 1, 'y', update)
|
|
);
|
|
}
|
|
|
|
// First clear the previous content
|
|
if (this._type === 'path')
|
|
this.removeSegments();
|
|
else
|
|
this.removeChildren();
|
|
|
|
for (var i = 0, l = parts.length; i < l; i++) {
|
|
var part = parts[i];
|
|
cmd = part[0],
|
|
lower = cmd.toLowerCase();
|
|
// Split at white-space, commas but also before signs.
|
|
// Use positive lookahead to include signs.
|
|
coords = part.slice(1).trim().split(/[\s,]+|(?=[+-])/);
|
|
relative = cmd === lower;
|
|
var length = coords.length;
|
|
switch (lower) {
|
|
case 'm':
|
|
case 'l':
|
|
for (var j = 0; j < length; j += 2)
|
|
this[j === 0 && lower === 'm' ? 'moveTo' : 'lineTo'](
|
|
getPoint(j, true));
|
|
break;
|
|
case 'h':
|
|
case 'v':
|
|
var coord = lower == 'h' ? 'x' : 'y';
|
|
for (var j = 0; j < length; j++) {
|
|
getCoord(j, coord, true);
|
|
this.lineTo(current);
|
|
}
|
|
break;
|
|
case 'c':
|
|
for (var j = 0; j < length; j += 6) {
|
|
this.cubicCurveTo(
|
|
getPoint(j),
|
|
control = getPoint(j + 2),
|
|
getPoint(j + 4, true));
|
|
}
|
|
break;
|
|
case 's':
|
|
// Shorthand cubic bezierCurveTo, absolute
|
|
for (var j = 0; j < length; j += 4) {
|
|
this.cubicCurveTo(
|
|
// Calculate reflection of previous control points
|
|
current.multiply(2).subtract(control),
|
|
control = getPoint(j),
|
|
getPoint(j + 2, true));
|
|
}
|
|
break;
|
|
case 'q':
|
|
for (var j = 0; j < length; j += 4) {
|
|
this.quadraticCurveTo(
|
|
control = getPoint(j),
|
|
getPoint(j + 2, true));
|
|
}
|
|
break;
|
|
case 't':
|
|
for (var j = 0; j < length; j += 2) {
|
|
this.quadraticCurveTo(
|
|
// Calculate reflection of previous control points
|
|
control = current.multiply(2).subtract(control),
|
|
getPoint(j, true));
|
|
}
|
|
break;
|
|
case 'a':
|
|
// TODO: Implement Arcs!
|
|
break;
|
|
case 'z':
|
|
this.closePath();
|
|
break;
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* 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.
|
|
*
|
|
* @name PathItem#smooth
|
|
* @function
|
|
*
|
|
* @example {@paperscript}
|
|
* // Smoothing a closed shape:
|
|
*
|
|
* // 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';
|
|
*
|
|
* // Select the path, so we can see its handles:
|
|
* path.fullySelected = true;
|
|
*
|
|
* // Create a copy of the path and move it 100pt to the right:
|
|
* var copy = path.clone();
|
|
* copy.position.x += 100;
|
|
*
|
|
* // Smooth the segments of the copy:
|
|
* copy.smooth();
|
|
*
|
|
* @example {@paperscript height=220}
|
|
* var path = new Path();
|
|
* path.strokeColor = 'black';
|
|
*
|
|
* path.add(new Point(30, 50));
|
|
*
|
|
* var y = 5;
|
|
* var x = 3;
|
|
*
|
|
* for (var i = 0; i < 28; i++) {
|
|
* y *= -1.1;
|
|
* x *= 1.1;
|
|
* path.lineBy(x, y);
|
|
* }
|
|
*
|
|
* // Create a copy of the path and move it 100pt down:
|
|
* var copy = path.clone();
|
|
* copy.position.y += 120;
|
|
*
|
|
* // Set its stroke color to red:
|
|
* copy.strokeColor = 'red';
|
|
*
|
|
* // Smooth the segments of the copy:
|
|
* copy.smooth();
|
|
*/
|
|
|
|
/**
|
|
* {@grouptitle Postscript Style Drawing Commands}
|
|
*
|
|
* 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.
|
|
*
|
|
* @name PathItem#moveTo
|
|
* @function
|
|
* @param {Point} point
|
|
*/
|
|
|
|
// DOCS: Document #lineTo()
|
|
/**
|
|
* @name PathItem#lineTo
|
|
* @function
|
|
* @param {Point} point
|
|
*/
|
|
|
|
/**
|
|
* Adds a cubic bezier curve to the path, defined by two handles and a to
|
|
* point.
|
|
*
|
|
* @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.
|
|
*
|
|
* @name PathItem#quadraticCurveTo
|
|
* @function
|
|
* @param {Point} handle
|
|
* @param {Point} to
|
|
*/
|
|
|
|
// DOCS: Document PathItem#curveTo() 'paramater' param.
|
|
/**
|
|
* 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.
|
|
*
|
|
* @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]
|
|
*
|
|
* @example {@paperscript height=300}
|
|
* // Interactive example. Move your mouse around the view below:
|
|
*
|
|
* var myPath;
|
|
* function onMouseMove(event) {
|
|
* // If we created a path before, remove it:
|
|
* if (myPath) {
|
|
* myPath.remove();
|
|
* }
|
|
*
|
|
* // Create a new path and add a segment point to it
|
|
* // at {x: 150, y: 150):
|
|
* myPath = new Path();
|
|
* myPath.add(150, 150);
|
|
*
|
|
* // Draw a curve through the position of the mouse to 'toPoint'
|
|
* var toPoint = new Point(350, 150);
|
|
* myPath.curveTo(event.point, toPoint);
|
|
*
|
|
* // 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.
|
|
*
|
|
* @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
|
|
*
|
|
* @example {@paperscript}
|
|
* var path = new Path();
|
|
* path.strokeColor = 'black';
|
|
*
|
|
* var firstPoint = new Point(30, 75);
|
|
* path.add(firstPoint);
|
|
*
|
|
* // The point through which we will create the arc:
|
|
* var throughPoint = new Point(40, 40);
|
|
*
|
|
* // The point at which the arc will end:
|
|
* var toPoint = new Point(130, 75);
|
|
*
|
|
* // Draw an arc through 'throughPoint' to 'toPoint'
|
|
* path.arcTo(throughPoint, toPoint);
|
|
*
|
|
* // Add a red circle shaped path at the position of 'throughPoint':
|
|
* var circle = new Path.Circle(throughPoint, 3);
|
|
* circle.fillColor = 'red';
|
|
*
|
|
* @example {@paperscript height=300}
|
|
* // Interactive example. Click and drag in the view below:
|
|
*
|
|
* var myPath;
|
|
* function onMouseDrag(event) {
|
|
* // If we created a path before, remove it:
|
|
* if (myPath) {
|
|
* myPath.remove();
|
|
* }
|
|
*
|
|
* // Create a new path and add a segment point to it
|
|
* // at {x: 150, y: 150):
|
|
* myPath = new Path();
|
|
* myPath.add(150, 150);
|
|
*
|
|
* // Draw an arc through the position of the mouse to 'toPoint'
|
|
* var toPoint = new Point(350, 150);
|
|
* myPath.arcTo(event.point, toPoint);
|
|
*
|
|
* // Select the path, so we can see its segments:
|
|
* myPath.selected = true;
|
|
* }
|
|
*
|
|
* // 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.
|
|
*
|
|
* @name PathItem#arcTo
|
|
* @function
|
|
* @param {Point} to the point where the arc should end
|
|
* @param {Boolean} [clockwise=true] specifies whether the arc should be
|
|
* drawn in clockwise direction.
|
|
*
|
|
* @example {@paperscript}
|
|
* var path = new Path();
|
|
* path.strokeColor = 'black';
|
|
*
|
|
* path.add(new Point(30, 75));
|
|
* path.arcTo(new Point(130, 75));
|
|
*
|
|
* var path2 = new Path();
|
|
* path2.strokeColor = 'red';
|
|
* path2.add(new Point(180, 25));
|
|
*
|
|
* // To draw an arc in anticlockwise direction,
|
|
* // we pass 'false' as the second argument to arcTo:
|
|
* path2.arcTo(new Point(280, 25), false);
|
|
*
|
|
* @example {@paperscript height=300}
|
|
* // Interactive example. Click and drag in the view below:
|
|
* var myPath;
|
|
*
|
|
* // The mouse has to move at least 20 points before
|
|
* // the next mouse drag event is fired:
|
|
* tool.minDistance = 20;
|
|
*
|
|
* // 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);
|
|
* }
|
|
*
|
|
* // 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.
|
|
*
|
|
* @name PathItem#closePath
|
|
* @function
|
|
* @see Path#closed
|
|
*/
|
|
|
|
/**
|
|
* {@grouptitle Relative Drawing Commands}
|
|
*
|
|
* If called on a {@link CompoundPath}, a new {@link Path} is created as a
|
|
* child and a point is added as its first segment relative to the
|
|
* position of the last segment of the current path.
|
|
*
|
|
* @name PathItem#moveBy
|
|
* @function
|
|
* @param {Point} vector
|
|
*/
|
|
|
|
/**
|
|
* Adds a segment relative to the last segment point of the path.
|
|
*
|
|
* @name PathItem#lineBy
|
|
* @function
|
|
* @param {Point} vector The vector which is added to the position of the
|
|
* last segment of the path, to become the new segment.
|
|
*
|
|
* @example {@paperscript}
|
|
* var path = new Path();
|
|
* path.strokeColor = 'black';
|
|
*
|
|
* // Add a segment at {x: 50, y: 50}
|
|
* path.add(25, 25);
|
|
*
|
|
* // 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);
|
|
*
|
|
* // 0 in x direction and 50 in y direction, becomes {x: 75, y: 75}
|
|
* path.lineBy(0, 50);
|
|
*
|
|
* @example {@paperscript height=300}
|
|
* // Drawing a spiral using lineBy:
|
|
* var path = new Path();
|
|
* path.strokeColor = 'black';
|
|
*
|
|
* // Add the first segment at {x: 50, y: 50}
|
|
* path.add(view.center);
|
|
*
|
|
* // 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);
|
|
* }
|
|
*
|
|
* // Smooth the handles of the path:
|
|
* path.smooth();
|
|
*
|
|
* // Uncomment the following line and click on 'run' to see
|
|
* // the construction of the path:
|
|
* // path.selected = true;
|
|
*/
|
|
|
|
// DOCS: Document Path#curveBy()
|
|
/**
|
|
* @name PathItem#curveBy
|
|
* @function
|
|
* @param {Point} throughVector
|
|
* @param {Point} toVector
|
|
* @param {Number} [parameter=0.5]
|
|
*/
|
|
|
|
// DOCS: Document Path#arcBy()
|
|
/**
|
|
* @name PathItem#arcBy
|
|
* @function
|
|
* @param {Point} throughVector
|
|
* @param {Point} toVector
|
|
*/
|
|
});
|