2011-03-06 19:50:44 -05:00
|
|
|
/*
|
|
|
|
* Paper.js
|
|
|
|
*
|
|
|
|
* This file is part of Paper.js, a JavaScript Vector Graphics Library,
|
|
|
|
* based on Scriptographer.org and designed to be largely API compatible.
|
|
|
|
* http://scriptographer.org/
|
|
|
|
*
|
|
|
|
* Copyright (c) 2011, Juerg Lehni & Jonathan Puckey
|
|
|
|
* http://lehni.org/ & http://jonathanpuckey.com/
|
|
|
|
*
|
|
|
|
* All rights reserved. See LICENSE file for details.
|
|
|
|
*/
|
|
|
|
|
2011-03-04 08:34:31 -05:00
|
|
|
var Path = this.Path = PathItem.extend({
|
2011-02-17 15:08:37 -05:00
|
|
|
beans: true,
|
|
|
|
|
|
|
|
initialize: function(/* segments */) {
|
|
|
|
this.base();
|
|
|
|
this.closed = false;
|
|
|
|
this._segments = [];
|
|
|
|
// 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
|
|
|
|
var segments = arguments[0];
|
2011-03-05 08:53:18 -05:00
|
|
|
if (!segments || !Array.isArray(segments)
|
2011-02-17 15:08:37 -05:00
|
|
|
|| typeof segments[0] != 'object')
|
|
|
|
segments = arguments;
|
2011-03-04 08:34:31 -05:00
|
|
|
for (var i = 0, l = segments.length; i < l; i++) {
|
|
|
|
var seg = Segment.read(segments, i, 1);
|
|
|
|
this._add(seg);
|
|
|
|
}
|
2011-02-17 15:08:37 -05:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The segments contained within the path.
|
|
|
|
*/
|
|
|
|
getSegments: function() {
|
|
|
|
return this._segments;
|
|
|
|
},
|
|
|
|
|
|
|
|
setSegments: function(segments) {
|
2011-03-04 07:02:11 -05:00
|
|
|
var l = segments.length;
|
|
|
|
this._segments = new Array(l);
|
|
|
|
for(var i = 0; i < l; i++) {
|
|
|
|
this._segments[i] = Segment.read(segments, i, 1);
|
|
|
|
}
|
2011-02-17 15:08:37 -05:00
|
|
|
},
|
|
|
|
|
2011-03-06 07:24:15 -05:00
|
|
|
/**
|
|
|
|
* The curves contained within the path.
|
|
|
|
*/
|
|
|
|
getCurves: function() {
|
|
|
|
var length = this._segments.length;
|
|
|
|
// Reduce length by one if it's an open path:
|
|
|
|
if (!this.closed && length > 0)
|
|
|
|
length--;
|
|
|
|
var curves = this._curves = this._curves || new Array(length);
|
|
|
|
curves.length = length;
|
|
|
|
for (var i = 0; i < length; i++) {
|
|
|
|
var curve = curves[i];
|
|
|
|
if (!curve) {
|
|
|
|
curve = curves[i] = new Curve(this, i);
|
|
|
|
} else {
|
|
|
|
// Make sure index is kept up to date.
|
|
|
|
curve._setIndex(i);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return curves;
|
|
|
|
},
|
|
|
|
|
2011-03-04 18:33:37 -05:00
|
|
|
// TODO: Add back to Scriptographer:
|
|
|
|
|
|
|
|
getFirstSegment: function() {
|
|
|
|
return this._segments[0];
|
|
|
|
},
|
|
|
|
|
|
|
|
getLastSegment: function() {
|
|
|
|
return this._segments[this._segments.length - 1];
|
|
|
|
},
|
|
|
|
|
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++) {
|
|
|
|
this._segments[i]._transformCoordinates(matrix, coords, true);
|
2011-02-20 05:20:23 -05:00
|
|
|
}
|
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-03-03 08:10:17 -05:00
|
|
|
_add: function(segment, index) {
|
|
|
|
// If this segment belongs to another path already, clone it before
|
|
|
|
// adding.
|
2011-03-06 07:24:15 -05:00
|
|
|
if (segment._path)
|
2011-03-03 08:10:17 -05:00
|
|
|
segment = new Segment(segment);
|
2011-03-06 07:24:15 -05:00
|
|
|
segment._path = this;
|
2011-03-03 08:10:17 -05:00
|
|
|
if (index == undefined) {
|
|
|
|
this._segments.push(segment);
|
|
|
|
} else {
|
|
|
|
this._segments.splice(index, 0, segment);
|
|
|
|
}
|
|
|
|
return segment;
|
2011-02-17 15:08:37 -05:00
|
|
|
},
|
|
|
|
|
|
|
|
add: function() {
|
|
|
|
var segment = Segment.read(arguments);
|
2011-03-03 08:10:17 -05:00
|
|
|
return segment ? this._add(segment) : null;
|
2011-02-17 15:08:37 -05:00
|
|
|
},
|
|
|
|
|
|
|
|
insert: function(index, segment) {
|
2011-03-03 08:10:17 -05:00
|
|
|
var segment = Segment.read(arguments, 1);
|
|
|
|
return segment ? this._add(segment, index) : null;
|
2011-02-17 15:08:37 -05:00
|
|
|
},
|
|
|
|
|
2011-03-06 09:58:10 -05:00
|
|
|
getLength: function() {
|
|
|
|
var curves = this.getCurves();
|
|
|
|
var length = 0;
|
|
|
|
for (var i = 0, l = curves.length; i < l; i++)
|
|
|
|
length += curves[i].getLength();
|
|
|
|
return length;
|
|
|
|
},
|
|
|
|
|
2011-03-03 07:19:43 -05:00
|
|
|
draw: function(ctx, param) {
|
|
|
|
if (!param.compound)
|
|
|
|
ctx.beginPath();
|
2011-03-06 06:00:25 -05:00
|
|
|
var segments = this._segments,
|
|
|
|
length = segments.length,
|
2011-03-06 06:18:35 -05:00
|
|
|
handleOut, outX, outY;
|
2011-03-03 07:19:43 -05:00
|
|
|
for (var i = 0; i < length; i++) {
|
2011-03-06 05:14:12 -05:00
|
|
|
var segment = segments[i],
|
2011-03-06 05:57:14 -05:00
|
|
|
point = segment._point,
|
|
|
|
x = point.x,
|
|
|
|
y = point.y,
|
2011-03-06 06:18:35 -05:00
|
|
|
handleIn = segment._handleIn;
|
2011-03-03 07:19:43 -05:00
|
|
|
if (i == 0) {
|
|
|
|
ctx.moveTo(x, y);
|
|
|
|
} else {
|
2011-03-06 06:18:35 -05:00
|
|
|
if (handleIn.isZero() && handleOut.isZero()) {
|
2011-03-03 07:19:43 -05:00
|
|
|
ctx.lineTo(x, y);
|
|
|
|
} else {
|
|
|
|
ctx.bezierCurveTo(
|
|
|
|
outX, outY,
|
|
|
|
handleIn.x + x, handleIn.y + y,
|
|
|
|
x, y
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
2011-03-06 06:18:35 -05:00
|
|
|
handleOut = segment._handleOut;
|
2011-03-06 06:00:25 -05:00
|
|
|
outX = handleOut.x + x;
|
|
|
|
outY = handleOut.y + y;
|
2011-03-03 07:19:43 -05:00
|
|
|
}
|
|
|
|
if (this.closed && length > 1) {
|
2011-03-06 05:14:12 -05:00
|
|
|
var segment = segments[0],
|
2011-03-06 05:57:14 -05:00
|
|
|
point = segment._point,
|
|
|
|
x = point.x,
|
|
|
|
y = point.y,
|
|
|
|
handleIn = segment._handleIn;
|
2011-03-03 07:19:43 -05:00
|
|
|
ctx.bezierCurveTo(outX, outY, handleIn.x + x, handleIn.y + y, x, y);
|
|
|
|
ctx.closePath();
|
|
|
|
}
|
|
|
|
// If the path is part of a compound path or doesn't have a fill or
|
|
|
|
// stroke, there is no need to continue.
|
2011-03-04 20:36:27 -05:00
|
|
|
var fillColor = this.getFillColor(),
|
|
|
|
strokeColor = this.getStrokeColor();
|
|
|
|
if (!param.compound && (fillColor || strokeColor)) {
|
2011-03-04 20:37:02 -05:00
|
|
|
this.setContextStyles(ctx);
|
2011-03-03 07:19:43 -05:00
|
|
|
ctx.save();
|
|
|
|
// 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.
|
2011-03-04 20:36:27 -05:00
|
|
|
if (!fillColor || !strokeColor)
|
2011-03-03 07:19:43 -05:00
|
|
|
ctx.globalAlpha = this.opacity;
|
2011-03-04 20:36:27 -05:00
|
|
|
if (fillColor) {
|
|
|
|
ctx.fillStyle = fillColor.getCanvasStyle(ctx);
|
2011-03-03 07:19:43 -05:00
|
|
|
ctx.fill();
|
|
|
|
}
|
2011-03-04 20:36:27 -05:00
|
|
|
if (strokeColor) {
|
|
|
|
ctx.strokeStyle = strokeColor.getCanvasStyle(ctx);
|
2011-03-03 07:19:43 -05:00
|
|
|
ctx.stroke();
|
|
|
|
}
|
|
|
|
ctx.restore();
|
|
|
|
}
|
2011-02-17 15:08:37 -05: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.
|
|
|
|
*
|
|
|
|
* @param rhs right hand side vector.
|
|
|
|
* @return Solution vector.
|
|
|
|
*/
|
2011-03-02 12:23:45 -05:00
|
|
|
function getFirstControlPoints(rhs) {
|
2011-02-17 07:36:40 -05:00
|
|
|
var n = rhs.length;
|
|
|
|
var x = []; // Solution vector.
|
|
|
|
var tmp = []; // Temporary workspace.
|
|
|
|
var b = 2;
|
|
|
|
x[0] = rhs[0] / b;
|
|
|
|
// Decomposition and forward substitution.
|
|
|
|
for (var i = 1; i < n; i++) {
|
|
|
|
tmp[i] = 1 / b;
|
|
|
|
b = (i < n - 1 ? 4.0 : 2.0) - tmp[i];
|
|
|
|
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-02-17 07:36:40 -05:00
|
|
|
smooth: function() {
|
|
|
|
var segments = this._segments;
|
2011-02-17 15:08:37 -05:00
|
|
|
|
2011-02-17 07:36:40 -05:00
|
|
|
// 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.
|
|
|
|
var size = segments.length;
|
|
|
|
if (size <= 2)
|
|
|
|
return;
|
|
|
|
|
|
|
|
var n = size;
|
|
|
|
// Add overlapping ends for averaging handles in closed paths
|
|
|
|
var overlap;
|
|
|
|
if (this.closed) {
|
|
|
|
// 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-02-17 07:36:40 -05: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++)
|
|
|
|
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;
|
|
|
|
// Get first control points X-values
|
|
|
|
var x = getFirstControlPoints(rhs);
|
|
|
|
|
|
|
|
// Set right hand side Y values
|
|
|
|
for (var i = 1; i < n - 1; i++)
|
|
|
|
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;
|
|
|
|
// Get first control points Y-values
|
|
|
|
var y = getFirstControlPoints(rhs);
|
|
|
|
|
|
|
|
if (this.closed) {
|
|
|
|
// 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(
|
|
|
|
2 * knots[i + 1].x - x[i + 1],
|
|
|
|
2 * knots[i + 1].y - y[i + 1]);
|
|
|
|
else
|
|
|
|
handleIn = new Point(
|
|
|
|
(knots[n].x + x[n - 1]) / 2,
|
|
|
|
(knots[n].y + y[n - 1]) / 2);
|
|
|
|
}
|
|
|
|
}
|
2011-03-06 05:57:14 -05:00
|
|
|
if (closed && handleIn) {
|
2011-02-17 07:36:40 -05:00
|
|
|
var segment = this._segments[0];
|
2011-03-06 05:57:14 -05:00
|
|
|
segment.setHandleIn(handleIn.subtract(segment._point));
|
2011-02-17 07:36:40 -05:00
|
|
|
}
|
|
|
|
},
|
2011-02-17 15:08:37 -05:00
|
|
|
|
2011-03-04 20:37:02 -05:00
|
|
|
setContextStyles: function(context) {
|
2011-03-04 20:36:27 -05:00
|
|
|
for (var i in styles) {
|
2011-02-17 07:36:40 -05:00
|
|
|
var style;
|
2011-03-04 20:36:27 -05:00
|
|
|
if (style = this[i]()) {
|
|
|
|
context[styles[i]] = style;
|
|
|
|
}
|
2011-02-17 07:36:40 -05:00
|
|
|
}
|
2011-02-13 21:05:54 -05:00
|
|
|
}
|
2011-03-06 19:36:44 -05:00
|
|
|
}
|
2011-03-06 16:13:55 -05:00
|
|
|
}, new function() { // PostScript-style drawing commands
|
|
|
|
|
2011-03-06 10:21:12 -05:00
|
|
|
function getCurrentSegment(that) {
|
|
|
|
var segments = that._segments;
|
|
|
|
if (segments.length == 0)
|
|
|
|
throw('Use a moveTo() command first');
|
|
|
|
return segments[segments.length - 1];
|
|
|
|
}
|
2011-03-06 10:15:13 -05:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Helper method that returns the current segment and checks if we need to
|
|
|
|
* execute a moveTo() command first.
|
|
|
|
*/
|
2011-03-06 10:21:12 -05:00
|
|
|
return {
|
|
|
|
moveTo: function() {
|
|
|
|
var segment = new Segment(Point.read(arguments));
|
|
|
|
if (segment && !this._segments.length)
|
|
|
|
this._add(segment);
|
|
|
|
},
|
2011-03-06 10:15:13 -05:00
|
|
|
|
2011-03-06 10:21:12 -05:00
|
|
|
lineTo: function() {
|
|
|
|
var segment = new Segment(Point.read(arguments));
|
|
|
|
if (segment)
|
|
|
|
this._add(segment);
|
|
|
|
},
|
2011-03-06 10:15:13 -05:00
|
|
|
|
2011-03-06 10:21:12 -05:00
|
|
|
/**
|
|
|
|
* Adds a cubic bezier curve to the path, defined by two handles and a
|
|
|
|
* to point.
|
|
|
|
*/
|
|
|
|
cubicCurveTo: function(handle1, handle2, to) {
|
|
|
|
// First modify the current segment:
|
|
|
|
var current = getCurrentSegment(this);
|
|
|
|
// Convert to relative values:
|
|
|
|
current.setHandleOut(new Point(
|
|
|
|
handle1.x - current._point.x,
|
|
|
|
handle1.y - current._point.y));
|
|
|
|
// And add the new segment, with handleIn set to c2
|
|
|
|
this._add(
|
|
|
|
new Segment(to, handle2.subtract(to), new Point())
|
|
|
|
);
|
|
|
|
},
|
2011-03-06 10:15:13 -05:00
|
|
|
|
2011-03-06 10:21:12 -05:00
|
|
|
/**
|
|
|
|
* Adds a quadratic bezier curve to the path, defined by a handle and a
|
|
|
|
* to point.
|
|
|
|
*/
|
|
|
|
quadraticCurveTo: function(handle, to) {
|
|
|
|
// 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)
|
|
|
|
var current = getCurrentSegment(this),
|
|
|
|
x1 = current._point.x,
|
|
|
|
y1 = current._point.y;
|
|
|
|
this.cubicCurveTo(
|
|
|
|
handle.add(current._point.subtract(handle).multiply(1/3)),
|
|
|
|
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) {
|
|
|
|
through = new Point(through);
|
|
|
|
to = new Point(to);
|
|
|
|
if (parameter == null)
|
|
|
|
parameter = 0.5;
|
|
|
|
var current = getCurrentSegment(this)._point;
|
|
|
|
// handle = (through - (1 - t)^2 * current - t^2 * to) /
|
|
|
|
// (2 * (1 - t) * t)
|
|
|
|
var t1 = 1 - parameter;
|
|
|
|
var handle = through.subtract(
|
|
|
|
current.multiply(t1 * t1)).subtract(
|
|
|
|
to.multiply(parameter * parameter)).divide(
|
|
|
|
2.0 * parameter * t1);
|
|
|
|
if (handle.isNaN())
|
|
|
|
throw new Error(
|
|
|
|
"Cannot put a curve through points with parameter="
|
|
|
|
+ parameter);
|
|
|
|
this.quadraticCurveTo(handle, to);
|
|
|
|
},
|
2011-03-06 10:15:13 -05:00
|
|
|
|
2011-03-06 10:21:12 -05:00
|
|
|
arcTo: function(to, clockwise) {
|
|
|
|
var through, to;
|
|
|
|
// Get the start point:
|
|
|
|
var current = getCurrentSegment(this);
|
|
|
|
if (arguments[1] && typeof arguments[1] != 'boolean') {
|
|
|
|
through = Point.read(arguments, 0, 1);
|
|
|
|
to = Point.read(arguments, 1, 1);
|
2011-03-06 10:15:13 -05:00
|
|
|
} else {
|
2011-03-06 10:21:12 -05:00
|
|
|
to = Point.read(arguments, 0, 1);
|
|
|
|
if (clockwise === null)
|
|
|
|
clockwise = true;
|
|
|
|
var middle = current._point.add(to).divide(2),
|
|
|
|
step = middle.subtract(current._point);
|
|
|
|
through = clockwise
|
|
|
|
? middle.subtract(-step.y, step.x)
|
|
|
|
: middle.add(-step.y, step.x);
|
2011-03-06 10:15:13 -05:00
|
|
|
}
|
|
|
|
|
2011-03-06 10:21:12 -05:00
|
|
|
var x1 = current._point.x, x2 = through.x, x3 = to.x,
|
|
|
|
y1 = current._point.y, y2 = through.y, y3 = to.y,
|
|
|
|
|
|
|
|
f = x3 * x3 - x3 * x2 - x1 * x3 + x1 * x2 + y3 * y3 - y3 * y2
|
|
|
|
- y1 * y3 + y1 * y2,
|
|
|
|
g = x3 * y1 - x3 * y2 + x1 * y2 - x1 * y3 + x2 * y3 - x2 * y1,
|
|
|
|
m = g == 0 ? 0 : f / g,
|
|
|
|
|
|
|
|
c = (m * y2) - x2 - x1 - (m * y1),
|
|
|
|
d = (m * x1) - y1 - y2 - (x2 * m),
|
|
|
|
e = (x1 * x2) + (y1 * y2) - (m * x1 * y2) + (m * x2 * y1),
|
|
|
|
|
|
|
|
centerX = -c / 2,
|
|
|
|
centerY = -d / 2,
|
|
|
|
radius = Math.sqrt(centerX * centerX + centerY * centerY - e),
|
|
|
|
|
|
|
|
// Note: reversing the Y equations negates the angle to adjust
|
|
|
|
// for the upside down coordinate system.
|
|
|
|
angle = Math.atan2(centerY - y1, x1 - centerX),
|
|
|
|
middle = Math.atan2(centerY - y2, x2 - centerX),
|
|
|
|
extent = Math.atan2(centerY - y3, x3 - centerX),
|
|
|
|
|
|
|
|
diff = middle - angle;
|
|
|
|
|
|
|
|
if (diff < -Math.PI)
|
|
|
|
diff += Math.PI * 2;
|
|
|
|
else if (diff > Math.PI)
|
|
|
|
diff -= Math.PI * 2;
|
|
|
|
|
|
|
|
extent -= angle;
|
|
|
|
if (extent <= 0.0)
|
|
|
|
extent += Math.PI * 2;
|
|
|
|
|
|
|
|
if (diff < 0) extent = Math.PI * 2 - extent;
|
|
|
|
else extent = -extent;
|
|
|
|
angle = -angle;
|
|
|
|
|
|
|
|
var ext = Math.abs(extent),
|
|
|
|
arcSegs;
|
|
|
|
if (ext >= 2 * Math.PI) arcSegs = 4;
|
|
|
|
else arcSegs = Math.ceil(ext * 2 / Math.PI);
|
|
|
|
|
|
|
|
var inc = extent;
|
|
|
|
if (inc > 2 * Math.PI) inc = 2 * Math.PI;
|
|
|
|
else if (inc < -2 * Math.PI) inc = -2 * Math.PI;
|
|
|
|
inc /= arcSegs;
|
|
|
|
|
|
|
|
var halfInc = inc / 2,
|
|
|
|
z = 4 / 3 * Math.sin(halfInc) / (1 + Math.cos(halfInc));
|
|
|
|
|
|
|
|
for (var i = 0; i <= arcSegs; i++) {
|
|
|
|
var relx = Math.cos(angle),
|
|
|
|
rely = Math.sin(angle),
|
|
|
|
pt = new Point(centerX + relx * radius,
|
|
|
|
centerY + rely * radius);
|
|
|
|
var out;
|
2011-03-06 16:13:55 -05:00
|
|
|
if (i == arcSegs) {
|
|
|
|
out = null;
|
|
|
|
} else {
|
|
|
|
out = new Point(
|
|
|
|
centerX + (relx - z * rely) * radius - pt.x,
|
2011-03-06 10:21:12 -05:00
|
|
|
centerY + (rely + z * relx) * radius - pt.y);
|
2011-03-06 16:13:55 -05:00
|
|
|
}
|
2011-03-06 10:21:12 -05:00
|
|
|
if (i == 0) {
|
|
|
|
// Modify startSegment
|
|
|
|
current.setHandleOut(out);
|
|
|
|
} else {
|
|
|
|
// Add new Segment
|
2011-03-06 16:13:55 -05:00
|
|
|
var handleIn = new Point(
|
2011-03-06 10:21:12 -05:00
|
|
|
centerX + (relx + z * rely) * radius - pt.x,
|
|
|
|
centerY + (rely - z * relx) * radius - pt.y);
|
2011-03-06 16:13:55 -05:00
|
|
|
this._add(new Segment(pt, handleIn, out));
|
2011-03-06 10:21:12 -05:00
|
|
|
}
|
|
|
|
angle += inc;
|
|
|
|
}
|
|
|
|
},
|
2011-03-06 10:15:13 -05:00
|
|
|
|
2011-03-06 10:21:12 -05:00
|
|
|
lineBy: function() {
|
|
|
|
var vector = Point.read(arguments);
|
|
|
|
if (vector) {
|
|
|
|
var current = getCurrentSegment(this);
|
|
|
|
this.lineTo(current._point.add(vector));
|
|
|
|
}
|
|
|
|
},
|
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() {
|
|
|
|
this.closed = ture;
|
|
|
|
}
|
|
|
|
};
|
2011-03-06 19:36:44 -05:00
|
|
|
}, new function() { // A dedicated scope for the tricky bounds calculations
|
|
|
|
|
|
|
|
// 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
|
|
|
|
// getBounds()
|
|
|
|
var tMin = 10e-6, tMax = 1 - 10e-6;
|
|
|
|
|
|
|
|
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;
|
|
|
|
first._transformCoordinates(matrix, prevCoords, false);
|
|
|
|
var min = prevCoords.slice(0, 2),
|
|
|
|
max = min.slice(0); // clone
|
|
|
|
function processSegment(segment) {
|
|
|
|
segment._transformCoordinates(matrix, coords, false);
|
|
|
|
|
|
|
|
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]);
|
|
|
|
if (that.closed)
|
|
|
|
processSegment(first);
|
|
|
|
return new Rectangle(min[0], min[1], max[0] - min[0], max[1] - min[1]);
|
|
|
|
}
|
|
|
|
|
|
|
|
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.
|
|
|
|
padding = [Math.abs(x), Math.abs(y)];
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
beans: true,
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The bounding rectangle of the item excluding stroke width.
|
|
|
|
*/
|
|
|
|
getBounds: function(matrix) {
|
|
|
|
return getBounds(this, matrix);
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The bounding rectangle of the item including stroke width.
|
|
|
|
*/
|
|
|
|
getStrokeBounds: function(matrix) {
|
|
|
|
var width = this.getStrokeWidth(),
|
|
|
|
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,
|
|
|
|
closed= this.closed,
|
|
|
|
bounds = getBounds(this, matrix, padding);
|
|
|
|
|
|
|
|
// 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) {
|
|
|
|
var handleIn = segment.getHandleInIfSet(),
|
|
|
|
handleOut = segment.getHandleOutIfSet();
|
|
|
|
// When both handles are set in a segment, the join setting is
|
|
|
|
// ignored and round is always used.
|
|
|
|
if (join == 'round' || handleIn && handleOut) {
|
|
|
|
bounds = bounds.unite(joinBounds.setCenter(matrix
|
|
|
|
? matrix.transform(segment._point) : segment._point));
|
|
|
|
} else {
|
|
|
|
switch (join) {
|
|
|
|
case 'bevel':
|
|
|
|
var curve = segment.getCurve();
|
|
|
|
addBevelJoin(curve, 0);
|
|
|
|
addBevelJoin(curve.getPrevious(), 1);
|
|
|
|
break;
|
|
|
|
case '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
|
|
|
|
line1 = new Line(point.add(normal1),
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
if (cap == 'square')
|
|
|
|
point = point.add(normal.y, -normal.x);
|
|
|
|
add(point.add(normal));
|
|
|
|
add(point.subtract(normal));
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (var i = 1, l = length - (closed ? 0 : 1); i < l; i++) {
|
|
|
|
addJoin(segments[i], join);
|
|
|
|
}
|
|
|
|
if (closed) {
|
|
|
|
addJoin(segments[0], join);
|
|
|
|
} else {
|
|
|
|
addCap(segments[0], cap, 0);
|
|
|
|
addCap(segments[length - 1], cap, 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
return bounds;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The bounding rectangle of the item including handles.
|
|
|
|
*/
|
|
|
|
getControlBounds: function() {
|
|
|
|
// TODO: Implement!
|
|
|
|
}
|
|
|
|
};
|
2011-02-13 11:26:24 -05:00
|
|
|
});
|