mirror of
https://github.com/scratchfoundation/paper.js.git
synced 2024-12-29 09:22:22 -05:00
Merge remote-tracking branch 'origin/boolean-fix' into develop
This commit is contained in:
commit
2cf637d13b
33 changed files with 2293 additions and 1470 deletions
|
@ -92,7 +92,7 @@
|
|||
text.content = 'ring.' + operation + '(square)';
|
||||
}
|
||||
result.selected = true;
|
||||
result.fillColor = colors[curIndex % operations.length];
|
||||
result.fillColor = colors[curIndex % colors.length];
|
||||
result.moveBelow(text);
|
||||
|
||||
// If the result is a group, color each of its children differently:
|
||||
|
|
|
@ -269,7 +269,7 @@
|
|||
// // annotatePath(pathB)
|
||||
// // pathB.translate([ 300, 0 ]);
|
||||
// // pathB.segments.filter(function(a) { return a._ixPair; }).map(
|
||||
// // function(a) { a._ixPair.getIntersection()._segment.selected = true; });
|
||||
// // function(a) { a._ixPair.intersection._segment.selected = true; });
|
||||
|
||||
// console.time('unite');
|
||||
// var nup = unite(pathA, pathB);
|
||||
|
@ -360,14 +360,12 @@
|
|||
|
||||
function disperse(path, distance) {
|
||||
distance = distance || 10;
|
||||
if (! path instanceof CompoundPath || ! path instanceof Group) { return; }
|
||||
var center = path.bounds.center;
|
||||
var children = path.children, i ,len;
|
||||
for (var i = 0, len = children.length; i < len; i++) {
|
||||
for (var i = 0, len = children && children.length; i < len; i++) {
|
||||
var cCenter = children[i].bounds.center;
|
||||
var vec = cCenter.subtract(center);
|
||||
vec = (vec.isClose([0,0], 0.5))? vec : vec.normalize(distance);
|
||||
children[i].translate(vec);
|
||||
children[i].translate(vec.length < 0.5 ? vec : vec.normalize(distance));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -35,7 +35,7 @@
|
|||
"devDependencies": {
|
||||
"gulp": "^3.9.0",
|
||||
"gulp-qunit": "^1.2.1",
|
||||
"prepro": "~0.8.3",
|
||||
"prepro": "~0.9.0",
|
||||
"qunitjs": "~1.15.0",
|
||||
"uglify-js": "~2.4.24"
|
||||
},
|
||||
|
|
|
@ -48,30 +48,30 @@ var Line = Base.extend(/** @lends Line# */{
|
|||
},
|
||||
|
||||
/**
|
||||
* The starting point of the line
|
||||
* The starting point of the line.
|
||||
*
|
||||
* @name Line#point
|
||||
* @type Point
|
||||
* @bean
|
||||
*/
|
||||
getPoint: function() {
|
||||
return new Point(this._px, this._py);
|
||||
},
|
||||
|
||||
/**
|
||||
* The vector of the line
|
||||
* The direction of the line as a vector.
|
||||
*
|
||||
* @name Line#vector
|
||||
* @type Point
|
||||
* @bean
|
||||
*/
|
||||
getVector: function() {
|
||||
return new Point(this._vx, this._vy);
|
||||
},
|
||||
|
||||
/**
|
||||
* The length of the line
|
||||
* The length of the line.
|
||||
*
|
||||
* @name Line#length
|
||||
* @type Number
|
||||
* @bean
|
||||
*/
|
||||
getLength: function() {
|
||||
return this.getVector().getLength();
|
||||
|
@ -113,35 +113,47 @@ var Line = Base.extend(/** @lends Line# */{
|
|||
},
|
||||
|
||||
isCollinear: function(line) {
|
||||
// TODO: Tests showed that 1e-10 might work well here, but we want to
|
||||
// keep it in sync with Point#isCollinear()
|
||||
return this._vx * line._vy - this._vy * line._vx
|
||||
< /*#=*/Numerical.TOLERANCE;
|
||||
return Point.isCollinear(this._vx, this._vy, line._vx, line._vy);
|
||||
},
|
||||
|
||||
isOrthogonal: function(line) {
|
||||
return Point.isOrthogonal(this._vx, this._vy, line._vx, line._vy);
|
||||
},
|
||||
|
||||
statics: /** @lends Line */{
|
||||
intersect: function(apx, apy, avx, avy, bpx, bpy, bvx, bvy, asVector,
|
||||
intersect: function(p1x, p1y, v1x, v1y, p2x, p2y, v2x, v2y, asVector,
|
||||
isInfinite) {
|
||||
// Convert 2nd points to vectors if they are not specified as such.
|
||||
if (!asVector) {
|
||||
avx -= apx;
|
||||
avy -= apy;
|
||||
bvx -= bpx;
|
||||
bvy -= bpy;
|
||||
v1x -= p1x;
|
||||
v1y -= p1y;
|
||||
v2x -= p2x;
|
||||
v2y -= p2y;
|
||||
}
|
||||
var cross = avx * bvy - avy * bvx;
|
||||
var cross = v1x * v2y - v1y * v2x;
|
||||
// Avoid divisions by 0, and errors when getting too close to 0
|
||||
if (!Numerical.isZero(cross)) {
|
||||
var dx = apx - bpx,
|
||||
dy = apy - bpy,
|
||||
ta = (bvx * dy - bvy * dx) / cross,
|
||||
tb = (avx * dy - avy * dx) / cross;
|
||||
// Check the ranges of t parameters if the line is not allowed
|
||||
// to extend beyond the definition points.
|
||||
if (isInfinite || 0 <= ta && ta <= 1 && 0 <= tb && tb <= 1)
|
||||
var dx = p1x - p2x,
|
||||
dy = p1y - p2y,
|
||||
u1 = (v2x * dy - v2y * dx) / cross,
|
||||
u2 = (v1x * dy - v1y * dx) / cross,
|
||||
// Check the ranges of the u parameters if the line is not
|
||||
// allowed to extend beyond the definition points, but
|
||||
// compare with EPSILON tolerance over the [0, 1] bounds.
|
||||
epsilon = /*#=*/Numerical.EPSILON,
|
||||
uMin = -epsilon,
|
||||
uMax = 1 + epsilon;
|
||||
if (isInfinite
|
||||
|| uMin < u1 && u1 < uMax && uMin < u2 && u2 < uMax) {
|
||||
if (!isInfinite) {
|
||||
// Address the tolerance at the bounds by clipping to
|
||||
// the actual range.
|
||||
u1 = u1 <= 0 ? 0 : u1 >= 1 ? 1 : u1;
|
||||
}
|
||||
return new Point(
|
||||
apx + ta * avx,
|
||||
apy + ta * avy);
|
||||
p1x + u1 * v1x,
|
||||
p1y + u1 * v1y);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -157,9 +169,7 @@ var Line = Base.extend(/** @lends Line# */{
|
|||
ccw = v2x * vx + v2y * vy; // ccw = v2.dot(v1);
|
||||
if (ccw > 0) {
|
||||
// ccw = v2.subtract(v1).dot(v1);
|
||||
v2x -= vx;
|
||||
v2y -= vy;
|
||||
ccw = v2x * vx + v2y * vy;
|
||||
ccw = (v2x - vx) * vx + (v2y - vy) * vy;
|
||||
if (ccw < 0)
|
||||
ccw = 0;
|
||||
}
|
||||
|
@ -172,11 +182,13 @@ var Line = Base.extend(/** @lends Line# */{
|
|||
vx -= px;
|
||||
vy -= py;
|
||||
}
|
||||
return Numerical.isZero(vx)
|
||||
? vy >= 0 ? px - x : x - px
|
||||
: Numerical.isZero(vy)
|
||||
? vx >= 0 ? y - py : py - y
|
||||
: (vx * (y - py) - vy * (x - px)) / Math.sqrt(vx * vx + vy * vy);
|
||||
// Based on the error analysis by @iconexperience outlined in
|
||||
// https://github.com/paperjs/paper.js/issues/799
|
||||
return vx === 0
|
||||
? vy >= 0 ? px - x : x - px
|
||||
: vy === 0
|
||||
? vx >= 0 ? y - py : py - y
|
||||
: (vx * (y - py) - vy * (x - px)) / Math.sqrt(vx * vx + vy * vy);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -460,11 +460,11 @@ var Point = Base.extend(/** @lends Point# */{
|
|||
return this.clone();
|
||||
angle = angle * Math.PI / 180;
|
||||
var point = center ? this.subtract(center) : this,
|
||||
s = Math.sin(angle),
|
||||
c = Math.cos(angle);
|
||||
sin = Math.sin(angle),
|
||||
cos = Math.cos(angle);
|
||||
point = new Point(
|
||||
point.x * c - point.y * s,
|
||||
point.x * s + point.y * c
|
||||
point.x * cos - point.y * sin,
|
||||
point.x * sin + point.y * cos
|
||||
);
|
||||
return center ? point.add(center) : point;
|
||||
},
|
||||
|
@ -690,7 +690,9 @@ var Point = Base.extend(/** @lends Point# */{
|
|||
* @param {Number} tolerance the maximum distance allowed
|
||||
* @return {Boolean} {@true if it is within the given distance}
|
||||
*/
|
||||
isClose: function(point, tolerance) {
|
||||
isClose: function(/* point, tolerance */) {
|
||||
var point = Point.read(arguments),
|
||||
tolerance = Base.read(arguments);
|
||||
return this.getDistance(point) < tolerance;
|
||||
},
|
||||
|
||||
|
@ -701,11 +703,9 @@ var Point = Base.extend(/** @lends Point# */{
|
|||
* @param {Point} point the vector to check against
|
||||
* @return {Boolean} {@true it is collinear}
|
||||
*/
|
||||
isCollinear: function(point) {
|
||||
// NOTE: Numerical.EPSILON is too small, breaking shape-path-shape
|
||||
// conversion test. But tolerance is probably too large?
|
||||
// TODO: Tests showed that 1e-10 might work well here.
|
||||
return Math.abs(this.cross(point)) < /*#=*/Numerical.TOLERANCE;
|
||||
isCollinear: function(/* point */) {
|
||||
var point = Point.read(arguments);
|
||||
return Point.isCollinear(this.x, this.y, point.x, point.y);
|
||||
},
|
||||
|
||||
// TODO: Remove version with typo after a while (deprecated June 2015)
|
||||
|
@ -718,10 +718,9 @@ var Point = Base.extend(/** @lends Point# */{
|
|||
* @param {Point} point the vector to check against
|
||||
* @return {Boolean} {@true it is orthogonal}
|
||||
*/
|
||||
isOrthogonal: function(point) {
|
||||
// NOTE: Numerical.EPSILON is too small, breaking shape-path-shape
|
||||
// conversion test.
|
||||
return Math.abs(this.dot(point)) < /*#=*/Numerical.TOLERANCE;
|
||||
isOrthogonal: function(/* point */) {
|
||||
var point = Point.read(arguments);
|
||||
return Point.isOrthogonal(this.x, this.y, point.x, point.y);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -767,23 +766,19 @@ var Point = Base.extend(/** @lends Point# */{
|
|||
},
|
||||
|
||||
/**
|
||||
* Returns the projection of the point on another point.
|
||||
* Returns the projection of the point onto another point.
|
||||
* Both points are interpreted as vectors.
|
||||
*
|
||||
* @param {Point} point
|
||||
* @return {Point} the projection of the point on another point
|
||||
* @return {Point} the projection of the point onto another point
|
||||
*/
|
||||
project: function(/* point */) {
|
||||
var point = Point.read(arguments);
|
||||
if (point.isZero()) {
|
||||
return new Point(0, 0);
|
||||
} else {
|
||||
var scale = this.dot(point) / point.dot(point);
|
||||
return new Point(
|
||||
point.x * scale,
|
||||
point.y * scale
|
||||
);
|
||||
}
|
||||
var point = Point.read(arguments),
|
||||
scale = point.isZero() ? 0 : this.dot(point) / point.dot(point);
|
||||
return new Point(
|
||||
point.x * scale,
|
||||
point.y * scale
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -920,6 +915,23 @@ var Point = Base.extend(/** @lends Point# */{
|
|||
*/
|
||||
random: function() {
|
||||
return new Point(Math.random(), Math.random());
|
||||
},
|
||||
|
||||
isCollinear: function(x1, y1, x2, y2) {
|
||||
// NOTE: We use normalized vectors so that the epsilon comparison is
|
||||
// reliable. We could instead scale the epsilon based on the vector
|
||||
// length. But instead of normalizing the vectors before calculating
|
||||
// the cross product, we can scale the epsilon accordingly.
|
||||
return Math.abs(x1 * y2 - y1 * x2)
|
||||
<= Math.sqrt((x1 * x1 + y1 * y1) * (x2 * x2 + y2 * y2))
|
||||
* /*#=*/Numerical.TRIGONOMETRIC_EPSILON;
|
||||
},
|
||||
|
||||
isOrthogonal: function(x1, y1, x2, y2) {
|
||||
// See Point.isCollinear()
|
||||
return Math.abs(x1 * x2 + y1 * y2)
|
||||
<= Math.sqrt((x1 * x1 + y1 * y1) * (x2 * x2 + y2 * y2))
|
||||
* /*#=*/Numerical.TRIGONOMETRIC_EPSILON;
|
||||
}
|
||||
}
|
||||
}, Base.each(['round', 'ceil', 'floor', 'abs'], function(name) {
|
||||
|
|
|
@ -475,7 +475,7 @@ var Rectangle = Base.extend(/** @lends Rectangle# */{
|
|||
*/
|
||||
|
||||
/**
|
||||
* The area of the rectangle in square points.
|
||||
* The area of the rectangle.
|
||||
*
|
||||
* @type Number
|
||||
* @bean
|
||||
|
@ -857,7 +857,8 @@ var LinkedRectangle = Rectangle.extend({
|
|||
this._owner[this._setter](this);
|
||||
return this;
|
||||
}
|
||||
}, new function() {
|
||||
},
|
||||
new function() {
|
||||
var proto = Rectangle.prototype;
|
||||
|
||||
return Base.each(['x', 'y', 'width', 'height'], function(key) {
|
||||
|
|
|
@ -120,7 +120,7 @@ var PaperScope = Base.extend(/** @lends PaperScope# */{
|
|||
*
|
||||
* @type String
|
||||
*/
|
||||
version: '/*#=*/__options.version',
|
||||
version: /*#=*/__options.version,
|
||||
|
||||
// DOCS: PaperScope#settings
|
||||
/**
|
||||
|
|
|
@ -1144,7 +1144,7 @@ var Item = Base.extend(Emitter, /** @lends Item# */{
|
|||
|
||||
/**
|
||||
* @bean
|
||||
* @deprecated use {@link #getApplyMatrix()} instead.
|
||||
* @deprecated use {@link #applyMatrix} instead.
|
||||
*/
|
||||
getTransformContent: '#getApplyMatrix',
|
||||
setTransformContent: '#setApplyMatrix',
|
||||
|
@ -1638,10 +1638,10 @@ var Item = Base.extend(Emitter, /** @lends Item# */{
|
|||
intersects: function(item, _matrix) {
|
||||
if (!(item instanceof Item))
|
||||
return false;
|
||||
// Tell _getIntersections to return as soon as some intersections are
|
||||
// Tell getIntersections() to return as soon as some intersections are
|
||||
// found, because all we care for here is there are some or none:
|
||||
return this._asPathItem()._getIntersections(item._asPathItem(),
|
||||
_matrix || item._matrix, [], true).length > 0;
|
||||
return this._asPathItem().getIntersections(item._asPathItem(), null,
|
||||
_matrix || item._matrix, true).length > 0;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -1652,7 +1652,7 @@ var Item = Base.extend(Emitter, /** @lends Item# */{
|
|||
* and may contain a combination of the following values:
|
||||
*
|
||||
* @option [options.tolerance={@link PaperScope#settings}.hitTolerance]
|
||||
* {Number} the tolerance of the hit-test in points
|
||||
* {Number} the tolerance of the hit-test
|
||||
* @option options.class {Function} only hit-test again a certain item class
|
||||
* and its sub-classes: {@code Group, Layer, Path, CompoundPath,
|
||||
* Shape, Raster, PlacedSymbol, PointText}, etc
|
||||
|
@ -2618,6 +2618,16 @@ var Item = Base.extend(Emitter, /** @lends Item# */{
|
|||
return item ? item.isDescendant(this) : false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks if the item is an a sibling of the specified item.
|
||||
*
|
||||
* @param {Item} item the item to check against
|
||||
* @return {Boolean} {@true if the item is aa sibling of the specified item}
|
||||
*/
|
||||
isSibling: function(item) {
|
||||
return this._parent === item._parent;
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks whether the item is grouped with the specified item.
|
||||
*
|
||||
|
|
|
@ -207,7 +207,7 @@ var Raster = Item.extend(/** @lends Raster# */{
|
|||
/**
|
||||
* @private
|
||||
* @bean
|
||||
* @deprecated use {@link #getResolution()} instead.
|
||||
* @deprecated use {@link #resolution} instead.
|
||||
*/
|
||||
getPpi: '#getResolution',
|
||||
|
||||
|
|
|
@ -64,7 +64,7 @@ var Shape = Item.extend(/** @lends Shape# */{
|
|||
/**
|
||||
* @private
|
||||
* @bean
|
||||
* @deprecated use {@link #getType()} instead.
|
||||
* @deprecated use {@link #type} instead.
|
||||
*/
|
||||
getShape: '#getType',
|
||||
setShape: '#setType',
|
||||
|
@ -277,7 +277,6 @@ var Shape = Item.extend(/** @lends Shape# */{
|
|||
}
|
||||
},
|
||||
new function() { // Scope for _contains() and _hitTestSelf() code.
|
||||
|
||||
// Returns the center of the quarter corner ellipse for rounded rectangle,
|
||||
// if the point lies within its bounding box.
|
||||
function getCornerCenter(that, point, expand) {
|
||||
|
|
|
@ -102,6 +102,15 @@ var CompoundPath = PathItem.extend(/** @lends CompoundPath# */{
|
|||
},
|
||||
|
||||
insertChildren: function insertChildren(index, items, _preserve) {
|
||||
// Convert CompoundPath items in the children list by adding their
|
||||
// children to the list and removing their parent.
|
||||
for (var i = items.length - 1; i >= 0; i--) {
|
||||
var item = items[i];
|
||||
if (item instanceof CompoundPath) {
|
||||
items.splice.apply(items, [i, 1].concat(item.removeChildren()));
|
||||
item.remove();
|
||||
}
|
||||
}
|
||||
// Pass on 'path' for _type, to make sure that only paths are added as
|
||||
// children.
|
||||
items = insertChildren.base.call(this, index, items, _preserve, Path);
|
||||
|
@ -131,16 +140,23 @@ var CompoundPath = PathItem.extend(/** @lends CompoundPath# */{
|
|||
this._children[i].smooth();
|
||||
},
|
||||
|
||||
// DOCS: reduce()
|
||||
// TEST: reduce()
|
||||
reduce: function reduce() {
|
||||
if (this._children.length === 0) { // Replace with a simple empty Path
|
||||
var children = this._children;
|
||||
for (var i = children.length - 1; i >= 0; i--) {
|
||||
var path = children[i].reduce();
|
||||
if (path.isEmpty())
|
||||
children.splice(i, 1);
|
||||
}
|
||||
if (children.length === 0) { // Replace with a simple empty Path
|
||||
var path = new Path(Item.NO_INSERT);
|
||||
path.insertAbove(this);
|
||||
path.setStyle(this._style);
|
||||
this.remove();
|
||||
return path;
|
||||
} else {
|
||||
return reduce.base.call(this);
|
||||
}
|
||||
return reduce.base.call(this);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -220,8 +236,8 @@ var CompoundPath = PathItem.extend(/** @lends CompoundPath# */{
|
|||
},
|
||||
|
||||
/**
|
||||
* The area of the path in square points. Self-intersecting paths can
|
||||
* contain sub-areas that cancel each other out.
|
||||
* The area that the path's geometry is covering. Self-intersecting paths
|
||||
* can contain sub-areas that cancel each other out.
|
||||
*
|
||||
* @type Number
|
||||
* @bean
|
||||
|
@ -298,7 +314,8 @@ var CompoundPath = PathItem.extend(/** @lends CompoundPath# */{
|
|||
: matrix.chain(mx));
|
||||
}
|
||||
}
|
||||
}, new function() { // Injection scope for PostScript-like drawing functions
|
||||
},
|
||||
new function() { // Injection scope for PostScript-like drawing functions
|
||||
/**
|
||||
* Helper method that returns the current path and checks if a moveTo()
|
||||
* command is required first.
|
||||
|
|
1395
src/path/Curve.js
1395
src/path/Curve.js
File diff suppressed because it is too large
Load diff
|
@ -13,16 +13,16 @@
|
|||
/**
|
||||
* @name CurveLocation
|
||||
*
|
||||
* @class CurveLocation objects describe a location on {@link Curve}
|
||||
* objects, as defined by the curve {@link #parameter}, a value between
|
||||
* {@code 0} (beginning of the curve) and {@code 1} (end of the curve). If
|
||||
* the curve is part of a {@link Path} item, its {@link #index} inside the
|
||||
* @class CurveLocation objects describe a location on {@link Curve} objects,
|
||||
* as defined by the curve-time {@link #parameter}, a value between {@code 0}
|
||||
* (beginning of the curve) and {@code 1} (end of the curve). If the curve is
|
||||
* part of a {@link Path} item, its {@link #index} inside the
|
||||
* {@link Path#curves} array is also provided.
|
||||
*
|
||||
* The class is in use in many places, such as
|
||||
* {@link Path#getLocationAt(offset, isParameter)},
|
||||
* {@link Path#getLocationAt(offset)},
|
||||
* {@link Path#getLocationOf(point)},
|
||||
* {@link Path#getNearestLocation(point),
|
||||
* {@link Path#getNearestLocation(point)},
|
||||
* {@link PathItem#getIntersections(path)},
|
||||
* etc.
|
||||
*/
|
||||
|
@ -41,22 +41,35 @@ var CurveLocation = Base.extend(/** @lends CurveLocation# */{
|
|||
* @param {Number} parameter
|
||||
* @param {Point} [point]
|
||||
*/
|
||||
initialize: function CurveLocation(curve, parameter, point, _curve2,
|
||||
_parameter2, _point2, _distance) {
|
||||
initialize: function CurveLocation(curve, parameter, point,
|
||||
_overlap, _distance) {
|
||||
// Merge intersections very close to the end of a curve with the
|
||||
// beginning of the next curve.
|
||||
if (parameter > /*#=*/(1 - Numerical.CURVETIME_EPSILON)) {
|
||||
var next = curve.getNext();
|
||||
if (next) {
|
||||
parameter = 0;
|
||||
curve = next;
|
||||
}
|
||||
}
|
||||
// Define this CurveLocation's unique id.
|
||||
// NOTE: We do not use the same pool as the rest of the library here,
|
||||
// since this is only required to be unique at runtime among other
|
||||
// CurveLocation objects.
|
||||
this._id = UID.get(CurveLocation);
|
||||
this._setCurve(curve);
|
||||
this._parameter = parameter;
|
||||
this._point = point || curve.getPointAt(parameter, true);
|
||||
this._overlap = _overlap;
|
||||
this._distance = _distance;
|
||||
this._intersection = this._next = this._prev = null;
|
||||
},
|
||||
|
||||
_setCurve: function(curve) {
|
||||
var path = curve._path;
|
||||
this._version = path ? path._version : 0;
|
||||
this._curve = curve;
|
||||
this._parameter = parameter;
|
||||
this._point = point || curve.getPointAt(parameter, true);
|
||||
this._curve2 = _curve2;
|
||||
this._parameter2 = _parameter2;
|
||||
this._point2 = _point2;
|
||||
this._distance = _distance;
|
||||
this._segment = null; // To be determined, see #getSegment()
|
||||
// Also store references to segment1 and segment2, in case path
|
||||
// splitting / dividing is going to happen, in which case the segments
|
||||
// can be used to determine the new curves, see #getCurve(true)
|
||||
|
@ -64,31 +77,40 @@ var CurveLocation = Base.extend(/** @lends CurveLocation# */{
|
|||
this._segment2 = curve._segment2;
|
||||
},
|
||||
|
||||
_setSegment: function(segment) {
|
||||
this._setCurve(segment.getCurve());
|
||||
this._segment = segment;
|
||||
this._parameter = segment === this._segment1 ? 0 : 1;
|
||||
// To avoid issues with imprecision in getCurve() / trySegment()
|
||||
this._point = segment._point.clone();
|
||||
},
|
||||
|
||||
/**
|
||||
* The segment of the curve which is closer to the described location.
|
||||
*
|
||||
* @type Segment
|
||||
* @bean
|
||||
*/
|
||||
getSegment: function(_preferFirst) {
|
||||
if (!this._segment) {
|
||||
var curve = this.getCurve(),
|
||||
parameter = this.getParameter();
|
||||
if (parameter === 1) {
|
||||
this._segment = curve._segment2;
|
||||
} else if (parameter === 0 || _preferFirst) {
|
||||
this._segment = curve._segment1;
|
||||
} else if (parameter == null) {
|
||||
return null;
|
||||
} else {
|
||||
getSegment: function() {
|
||||
// Request curve first, so _segment gets invalidated if it's out of sync
|
||||
var curve = this.getCurve(),
|
||||
segment = this._segment;
|
||||
if (!segment) {
|
||||
var parameter = this.getParameter();
|
||||
if (parameter === 0) {
|
||||
segment = curve._segment1;
|
||||
} else if (parameter === 1) {
|
||||
segment = curve._segment2;
|
||||
} else if (parameter != null) {
|
||||
// Determine the closest segment by comparing curve lengths
|
||||
this._segment = curve.getPartLength(0, parameter)
|
||||
segment = curve.getPartLength(0, parameter)
|
||||
< curve.getPartLength(parameter, 1)
|
||||
? curve._segment1
|
||||
: curve._segment2;
|
||||
}
|
||||
this._segment = segment;
|
||||
}
|
||||
return this._segment;
|
||||
return segment;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -99,29 +121,34 @@ var CurveLocation = Base.extend(/** @lends CurveLocation# */{
|
|||
*/
|
||||
getCurve: function() {
|
||||
var curve = this._curve,
|
||||
path = curve && curve._path;
|
||||
path = curve && curve._path,
|
||||
that = this;
|
||||
if (path && path._version !== this._version) {
|
||||
// If the path's segments have changed in the meantime, clear the
|
||||
// internal _parameter value and force refetching of the correct
|
||||
// curve again here.
|
||||
curve = null;
|
||||
this._parameter = null;
|
||||
curve = this._parameter = this._curve = this._offset = null;
|
||||
}
|
||||
if (!curve) {
|
||||
// If we're asked to get the curve uncached, access current curve
|
||||
// objects through segment1 / segment2. Since path splitting or
|
||||
// dividing might have happened in the meantime, try segment1's
|
||||
// curve, and see if _point lies on it still, otherwise assume it's
|
||||
// the curve before segment2.
|
||||
curve = this._segment1.getCurve();
|
||||
if (curve.getParameterOf(this._point) == null)
|
||||
curve = this._segment2.getPrevious().getCurve();
|
||||
this._curve = curve;
|
||||
// Fetch path again as it could be on a new one through split()
|
||||
path = curve._path;
|
||||
this._version = path ? path._version : 0;
|
||||
|
||||
// If path is out of sync, access current curve objects through segment1
|
||||
// / segment2. Since path splitting or dividing might have happened in
|
||||
// the meantime, try segment1's curve, and see if _point lies on it
|
||||
// still, otherwise assume it's the curve before segment2.
|
||||
function trySegment(segment) {
|
||||
var curve = segment && segment.getCurve();
|
||||
if (curve && (that._parameter = curve.getParameterOf(that._point))
|
||||
!= null) {
|
||||
// Fetch path again as it could be on a new one through split()
|
||||
that._setCurve(curve);
|
||||
that._segment = segment;
|
||||
return curve;
|
||||
}
|
||||
}
|
||||
return curve;
|
||||
|
||||
return curve
|
||||
|| trySegment(this._segment)
|
||||
|| trySegment(this._segment1)
|
||||
|| trySegment(this._segment2.getPrevious());
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -136,8 +163,8 @@ var CurveLocation = Base.extend(/** @lends CurveLocation# */{
|
|||
},
|
||||
|
||||
/**
|
||||
* The index of the curve within the {@link Path#curves} list, if the
|
||||
* curve is part of a {@link Path} item.
|
||||
* The index of the {@link #curve} within the {@link Path#curves} list, if
|
||||
* it is part of a {@link Path} item.
|
||||
*
|
||||
* @type Index
|
||||
* @bean
|
||||
|
@ -148,9 +175,9 @@ var CurveLocation = Base.extend(/** @lends CurveLocation# */{
|
|||
},
|
||||
|
||||
/**
|
||||
* The curve parameter, as used by various bezier curve calculations. It is
|
||||
* value between {@code 0} (beginning of the curve) and {@code 1} (end of
|
||||
* the curve).
|
||||
* The curve-time parameter, as used by various bezier curve calculations.
|
||||
* It is value between {@code 0} (beginning of the curve) and {@code 1}
|
||||
* (end of the curve).
|
||||
*
|
||||
* @type Number
|
||||
* @bean
|
||||
|
@ -183,8 +210,13 @@ var CurveLocation = Base.extend(/** @lends CurveLocation# */{
|
|||
* @bean
|
||||
*/
|
||||
getOffset: function() {
|
||||
var path = this.getPath();
|
||||
return path ? path._getOffset(this) : this.getCurveOffset();
|
||||
var offset = this._offset;
|
||||
if (offset == null) {
|
||||
var path = this.getPath();
|
||||
offset = this._offset = path ? path._getOffset(this)
|
||||
: this.getCurveOffset();
|
||||
}
|
||||
return offset;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -209,37 +241,31 @@ var CurveLocation = Base.extend(/** @lends CurveLocation# */{
|
|||
* @bean
|
||||
*/
|
||||
getIntersection: function() {
|
||||
var intersection = this._intersection;
|
||||
if (!intersection && this._curve2) {
|
||||
// If we have the parameter on the other curve use that for
|
||||
// intersection rather than the point.
|
||||
this._intersection = intersection = new CurveLocation(this._curve2,
|
||||
this._parameter2, this._point2 || this._point);
|
||||
intersection._overlap = this._overlap;
|
||||
intersection._intersection = this;
|
||||
}
|
||||
return intersection;
|
||||
return this._intersection;
|
||||
},
|
||||
|
||||
/**
|
||||
* The tangential vector to the {@link #curve} at the given location.
|
||||
*
|
||||
* @name Item#tangent
|
||||
* @name CurveLocation#getTangent
|
||||
* @type Point
|
||||
* @bean
|
||||
*/
|
||||
|
||||
/**
|
||||
* The normal vector to the {@link #curve} at the given location.
|
||||
*
|
||||
* @name Item#normal
|
||||
* @name CurveLocation#getNormal
|
||||
* @type Point
|
||||
* @bean
|
||||
*/
|
||||
|
||||
/**
|
||||
* The curvature of the {@link #curve} at the given location.
|
||||
*
|
||||
* @name Item#curvature
|
||||
* @name CurveLocation#getCurvature
|
||||
* @type Number
|
||||
* @bean
|
||||
*/
|
||||
|
||||
/**
|
||||
|
@ -274,23 +300,40 @@ var CurveLocation = Base.extend(/** @lends CurveLocation# */{
|
|||
* @param {CurveLocation} location
|
||||
* @return {Boolean} {@true if the locations are equal}
|
||||
*/
|
||||
equals: function(loc) {
|
||||
var abs = Math.abs,
|
||||
// Use the same tolerance for curve time parameter comparisons as
|
||||
// in Curve.js when considering two locations the same.
|
||||
tolerance = /*#=*/Numerical.TOLERANCE;
|
||||
return this === loc
|
||||
|| loc instanceof CurveLocation
|
||||
// Call getCurve() and getParameter() to keep in sync
|
||||
&& this.getCurve() === loc.getCurve()
|
||||
&& abs(this.getParameter() - loc.getParameter()) < tolerance
|
||||
// _curve2/_parameter2 are only used for Boolean operations
|
||||
// and don't need syncing there.
|
||||
// TODO: That's not quite true though... Rework this!
|
||||
&& this._curve2 === loc._curve2
|
||||
&& abs((this._parameter2 || 0) - (loc._parameter2 || 0))
|
||||
< tolerance
|
||||
|| false;
|
||||
equals: function(loc, _ignoreOther) {
|
||||
var res = this === loc,
|
||||
epsilon = /*#=*/Numerical.GEOMETRIC_EPSILON;
|
||||
// NOTE: We need to compare both by (index + parameter) and by proximity
|
||||
// of points. See:
|
||||
// https://github.com/paperjs/paper.js/issues/784#issuecomment-143161586
|
||||
if (!res && loc instanceof CurveLocation
|
||||
&& this.getPath() === loc.getPath()
|
||||
&& this.getPoint().isClose(loc.getPoint(), epsilon)) {
|
||||
// The position is the same, but it could still be in a different
|
||||
// location on the path. Perform more thorough checks now:
|
||||
var c1 = this.getCurve(),
|
||||
c2 = loc.getCurve(),
|
||||
abs = Math.abs,
|
||||
// We need to wrap diff around the path's beginning / end:
|
||||
diff = abs(
|
||||
((c1.isLast() && c2.isFirst() ? -1 : c1.getIndex())
|
||||
+ this.getParameter()) -
|
||||
((c2.isLast() && c1.isFirst() ? -1 : c2.getIndex())
|
||||
+ loc.getParameter()));
|
||||
res = (diff < /*#=*/Numerical.CURVETIME_EPSILON
|
||||
// If diff isn't close enough, compare the actual offsets of
|
||||
// both locations to determine if they're in the same spot,
|
||||
// taking into account the wrapping around path ends too.
|
||||
// This is necessary in order to handle very short consecutive
|
||||
// curves (length ~< 1e-7), which would lead to diff > 1.
|
||||
|| ((diff = abs(this.getOffset() - loc.getOffset())) < epsilon
|
||||
|| abs(this.getPath().getLength() - diff) < epsilon))
|
||||
&& (_ignoreOther
|
||||
|| (!this._intersection && !loc._intersection
|
||||
|| this._intersection && this._intersection.equals(
|
||||
loc._intersection, true)));
|
||||
}
|
||||
return res;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -313,37 +356,207 @@ var CurveLocation = Base.extend(/** @lends CurveLocation# */{
|
|||
return '{ ' + parts.join(', ') + ' }';
|
||||
},
|
||||
|
||||
statics: {
|
||||
sort: function(locations) {
|
||||
var tolerance = /*#=*/Numerical.TOLERANCE;
|
||||
locations.sort(function compare(l1, l2) {
|
||||
var curve1 = l1._curve,
|
||||
curve2 = l2._curve,
|
||||
path1 = curve1._path,
|
||||
path2 = curve2._path;
|
||||
// Sort by path-id, curve, parameter, curve2, parameter2 so we
|
||||
// can easily remove duplicates with calls to equals() after.
|
||||
return path1 === path2
|
||||
? curve1 === curve2
|
||||
? Math.abs(l1._parameter - l2._parameter) < tolerance
|
||||
? l1._curve2 === l2._curve2
|
||||
? l1._parameter2 - l2._parameter2
|
||||
: l1._curve2.getIndex() - l2._curve2.getIndex()
|
||||
: l1._parameter - l2._parameter
|
||||
: curve1.getIndex() - curve2.getIndex()
|
||||
// Sort by path id to group all locs on the same path.
|
||||
: path1._id - path2._id;
|
||||
|
||||
/**
|
||||
* {@grouptitle Tests}
|
||||
* Checks if the location is an intersection with another curve and is
|
||||
* merely touching the other curve, as opposed to crossing it.
|
||||
*
|
||||
* @return {Boolean} {@true if the location is an intersection that is
|
||||
* merely touching another curve}
|
||||
* @see #isCrossing()
|
||||
*/
|
||||
isTouching: function() {
|
||||
var inter = this._intersection;
|
||||
if (inter && this.getTangent().isCollinear(inter.getTangent())) {
|
||||
// Only consider two straight curves as touching if their lines
|
||||
// don't intersect.
|
||||
var curve1 = this.getCurve(),
|
||||
curve2 = inter.getCurve();
|
||||
return !(curve1.isStraight() && curve2.isStraight()
|
||||
&& curve1.getLine().intersect(curve2.getLine()));
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks if the location is an intersection with another curve and is
|
||||
* crossing the other curve, as opposed to just touching it.
|
||||
*
|
||||
* @return {Boolean} {@true if the location is an intersection that is
|
||||
* crossing another curve}
|
||||
* @see #isTouching()
|
||||
*/
|
||||
isCrossing: function(_report) {
|
||||
// Implementation based on work by Andy Finnell:
|
||||
// http://losingfight.com/blog/2011/07/09/how-to-implement-boolean-operations-on-bezier-paths-part-3/
|
||||
// https://bitbucket.org/andyfinnell/vectorboolean
|
||||
var inter = this._intersection;
|
||||
if (!inter)
|
||||
return false;
|
||||
// TODO: Make getCurve() and getParameter() sync work in boolean ops
|
||||
// before and after splitting!!!
|
||||
var t1 = this._parameter,
|
||||
t2 = inter._parameter,
|
||||
tMin = /*#=*/Numerical.CURVETIME_EPSILON,
|
||||
tMax = 1 - tMin;
|
||||
// If the intersection is in the middle of the path, it is either a
|
||||
// tangent or a crossing, no need for the detailed corner check below.
|
||||
// But we do need a check for the edge case of tangents?
|
||||
if (t1 >= tMin && t1 <= tMax || t2 >= tMin && t2 <= tMax)
|
||||
return !this.isTouching();
|
||||
// Values for getTangentAt() that are almost 0 and 1.
|
||||
// NOTE: Even though getTangentAt() has code to support 0 and 1 instead
|
||||
// of tMin and tMax, we still need to use this instead, as other issues
|
||||
// emerge from switching to 0 and 1 in edge cases.
|
||||
// NOTE: VectorBoolean has code that slowly shifts these points inwards
|
||||
// until the resulting tangents are not ambiguous. Do we need this too?
|
||||
var c2 = this._curve,
|
||||
c1 = c2.getPrevious(),
|
||||
c4 = inter._curve,
|
||||
c3 = c4.getPrevious(),
|
||||
PI = Math.PI;
|
||||
if (!c1 || !c3)
|
||||
return false;
|
||||
|
||||
if (_report) {
|
||||
new Path.Circle({
|
||||
center: this.getPoint(),
|
||||
radius: 10,
|
||||
strokeColor: 'red'
|
||||
});
|
||||
new Path({
|
||||
segments: [c1.getSegment1(), c1.getSegment2(), c2.getSegment2()],
|
||||
strokeColor: 'red',
|
||||
strokeWidth: 4
|
||||
});
|
||||
new Path({
|
||||
segments: [c3.getSegment1(), c3.getSegment2(), c4.getSegment2()],
|
||||
strokeColor: 'orange',
|
||||
strokeWidth: 4
|
||||
});
|
||||
}
|
||||
|
||||
function isInRange(angle, min, max) {
|
||||
return min < max
|
||||
? angle > min && angle < max
|
||||
// The range wraps around -PI / PI:
|
||||
: angle > min && angle <= PI || angle >= -PI && angle < max;
|
||||
}
|
||||
|
||||
// Calculate angles for all four tangents at the intersection point
|
||||
var a1 = c1.getTangentAt(tMax, true).negate().getAngleInRadians(),
|
||||
a2 = c2.getTangentAt(tMin, true).getAngleInRadians(),
|
||||
a3 = c3.getTangentAt(tMax, true).negate().getAngleInRadians(),
|
||||
a4 = c4.getTangentAt(tMin, true).getAngleInRadians();
|
||||
|
||||
// Count how many times curve2 angles appear between the curve1 angles
|
||||
// If each pair of angles split the other two, then the edges cross.
|
||||
return (isInRange(a3, a1, a2) ^ isInRange(a4, a1, a2))
|
||||
&& (isInRange(a3, a2, a1) ^ isInRange(a4, a2, a1));
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks if the location is an intersection with another curve and is
|
||||
* part of an overlap between the two involved paths.
|
||||
*
|
||||
* @return {Boolean} {@true if the location is an intersection that is
|
||||
* part of an overlap between the two involved paths}
|
||||
* @see #isCrossing()
|
||||
* @see #isTouching()
|
||||
*/
|
||||
isOverlap: function() {
|
||||
return !!this._overlap;
|
||||
}
|
||||
}, Base.each(Curve.evaluateMethods, function(name) {
|
||||
// Produce getters for #getTangent() / #getNormal() / #getCurvature()
|
||||
if (name !== 'getPoint') {
|
||||
var get = name + 'At';
|
||||
this[name] = function() {
|
||||
var parameter = this.getParameter(),
|
||||
curve = this.getCurve();
|
||||
return parameter != null && curve && curve[get](parameter, true);
|
||||
};
|
||||
var get = name + 'At';
|
||||
this[name] = function() {
|
||||
var parameter = this.getParameter(),
|
||||
curve = this.getCurve();
|
||||
return parameter != null && curve && curve[get](parameter, true);
|
||||
};
|
||||
}, {
|
||||
// Do not override the existing #getPoint():
|
||||
preserve: true
|
||||
}),
|
||||
new function() { // Scope for statics
|
||||
|
||||
function insert(locations, loc, merge) {
|
||||
// Insert-sort by path-id, curve, parameter so we can easily merge
|
||||
// duplicates with calls to equals() after.
|
||||
var length = locations.length,
|
||||
l = 0,
|
||||
r = length - 1,
|
||||
abs = Math.abs;
|
||||
|
||||
function search(index, dir) {
|
||||
// If we reach the beginning/end of the list, also compare with the
|
||||
// location at the other end, as paths are circular lists.
|
||||
// NOTE: When merging, the locations array will only contain
|
||||
// locations on the same path, so it is fine that check for the end
|
||||
// to address circularity. See PathItem#getIntersections()
|
||||
for (var i = index + dir; i >= -1 && i <= length; i += dir) {
|
||||
// Wrap the index around, to match the other ends:
|
||||
var loc2 = locations[((i % length) + length) % length];
|
||||
// Once we're outside the spot, we can stop searching.
|
||||
if (!loc.getPoint().isClose(loc2.getPoint(),
|
||||
/*#=*/Numerical.GEOMETRIC_EPSILON))
|
||||
break;
|
||||
if (loc.equals(loc2))
|
||||
return loc2;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
while (l <= r) {
|
||||
var m = (l + r) >>> 1,
|
||||
loc2 = locations[m],
|
||||
found;
|
||||
// See if the two locations are actually the same, and merge if
|
||||
// they are. If they aren't check the other neighbors with search()
|
||||
if (merge && (found = loc.equals(loc2) ? loc2
|
||||
: (search(m, -1) || search(m, 1)))) {
|
||||
// We're done, don't insert, merge with the found location
|
||||
// instead, and carry over overlap:
|
||||
if (loc._overlap) {
|
||||
found._overlap = found._intersection._overlap = true;
|
||||
}
|
||||
return found;
|
||||
}
|
||||
var path1 = loc.getPath(),
|
||||
path2 = loc2.getPath(),
|
||||
// NOTE: equals() takes the intersection location into account,
|
||||
// while this calculation of diff doesn't!
|
||||
diff = path1 === path2
|
||||
//Sort by both index and parameter. The two values added
|
||||
// together provides a convenient sorting index.
|
||||
? (loc.getIndex() + loc.getParameter())
|
||||
- (loc2.getIndex() + loc2.getParameter())
|
||||
// Sort by path id to group all locs on same path.
|
||||
: path1._id - path2._id;
|
||||
if (diff < 0) {
|
||||
r = m - 1;
|
||||
} else {
|
||||
l = m + 1;
|
||||
}
|
||||
}
|
||||
// We didn't merge with a preexisting location, insert it now.
|
||||
locations.splice(l, 0, loc);
|
||||
return loc;
|
||||
}
|
||||
}, {}));
|
||||
|
||||
return { statics: {
|
||||
insert: insert,
|
||||
|
||||
expand: function(locations) {
|
||||
// Create a copy since insert() keeps modifying the array and
|
||||
// inserting at sorted indices.
|
||||
var expanded = locations.slice();
|
||||
for (var i = 0, l = locations.length; i < l; i++) {
|
||||
insert(expanded, locations[i]._intersection, false);
|
||||
}
|
||||
return expanded;
|
||||
}
|
||||
}};
|
||||
});
|
||||
|
|
252
src/path/Path.js
252
src/path/Path.js
|
@ -150,7 +150,9 @@ var Path = PathItem.extend(/** @lends Path# */{
|
|||
if (parent)
|
||||
parent._currentPath = undefined;
|
||||
// Clockwise state becomes undefined as soon as geometry changes.
|
||||
this._length = this._clockwise = undefined;
|
||||
// Also clear cached mono curves used for winding calculations.
|
||||
this._length = this._area = this._clockwise = this._monoCurves =
|
||||
undefined;
|
||||
if (flags & /*#=*/ChangeFlag.SEGMENTS) {
|
||||
this._version++; // See CurveLocation
|
||||
} else if (this._curves) {
|
||||
|
@ -159,10 +161,6 @@ var Path = PathItem.extend(/** @lends Path# */{
|
|||
for (var i = 0, l = this._curves.length; i < l; i++)
|
||||
this._curves[i]._changed();
|
||||
}
|
||||
// Clear cached curves used for winding direction and containment
|
||||
// calculation.
|
||||
// NOTE: This is only needed with __options.booleanOperations
|
||||
this._monoCurves = undefined;
|
||||
} else if (flags & /*#=*/ChangeFlag.STROKE) {
|
||||
// TODO: We could preserve the purely geometric bounds that are not
|
||||
// affected by stroke: _bounds.bounds and _bounds.handleBounds
|
||||
|
@ -368,40 +366,6 @@ var Path = PathItem.extend(/** @lends Path# */{
|
|||
return this._segments.length === 0;
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks if this path consists of only linear curves. This can mean that
|
||||
* the curves have no handles defined, or that the handles run collinear
|
||||
* with the line.
|
||||
*
|
||||
* @return {Boolean} {@true if the path is entirely linear}
|
||||
* @see Segment#isLinear()
|
||||
* @see Curve#isLinear()
|
||||
*/
|
||||
isLinear: function() {
|
||||
var segments = this._segments;
|
||||
for (var i = 0, l = segments.length; i < l; i++) {
|
||||
if (!segments[i].isLinear())
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks if none of the curves in the path define any curve handles.
|
||||
*
|
||||
* @return {Boolean} {@true if the path contains no curve handles}
|
||||
* @see Segment#hasHandles()
|
||||
* @see Curve#hasHandles()
|
||||
*/
|
||||
hasHandles: function() {
|
||||
var segments = this._segments;
|
||||
for (var i = 0, l = segments.length; i < l; i++) {
|
||||
if (segments[i].hasHandles())
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
_transformContent: function(matrix) {
|
||||
var coords = new Array(6);
|
||||
for (var i = 0, l = this._segments.length; i < l; i++)
|
||||
|
@ -410,10 +374,10 @@ var Path = PathItem.extend(/** @lends Path# */{
|
|||
},
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* If a curves list was requested, it will kept in sync with the segments
|
||||
* list automatically.
|
||||
* Private method that adds segments to the segment list. It assumes that
|
||||
* the passed object is an array of segments already and does not perform
|
||||
* any checks. If a curves list was requested, it will be kept in sync with
|
||||
* the segments list automatically.
|
||||
*/
|
||||
_add: function(segs, index) {
|
||||
// Local short-cuts:
|
||||
|
@ -421,7 +385,7 @@ var Path = PathItem.extend(/** @lends Path# */{
|
|||
curves = this._curves,
|
||||
amount = segs.length,
|
||||
append = index == null,
|
||||
index = append ? segments.length : index;
|
||||
from = append ? segments.length : index;
|
||||
// Scan through segments to add first, convert if necessary and set
|
||||
// _path and _index references on them.
|
||||
for (var i = 0; i < amount; i++) {
|
||||
|
@ -431,7 +395,7 @@ var Path = PathItem.extend(/** @lends Path# */{
|
|||
if (segment._path)
|
||||
segment = segs[i] = segment.clone();
|
||||
segment._path = this;
|
||||
segment._index = index + i;
|
||||
segment._index = from + i;
|
||||
// If parts of this segment are selected, adjust the internal
|
||||
// _selectedSegmentState now
|
||||
if (segment._selectionState)
|
||||
|
@ -442,20 +406,15 @@ var Path = PathItem.extend(/** @lends Path# */{
|
|||
segments.push.apply(segments, segs);
|
||||
} else {
|
||||
// Insert somewhere else
|
||||
segments.splice.apply(segments, [index, 0].concat(segs));
|
||||
segments.splice.apply(segments, [from, 0].concat(segs));
|
||||
// Adjust the indices of the segments above.
|
||||
for (var i = index + amount, l = segments.length; i < l; i++)
|
||||
for (var i = from + amount, l = segments.length; i < l; i++)
|
||||
segments[i]._index = i;
|
||||
}
|
||||
// Keep the curves list in sync all the time in case it was requested
|
||||
// already.
|
||||
if (curves || segs._curves) {
|
||||
if (!curves)
|
||||
curves = this._curves = [];
|
||||
// We need to step one index down from the inserted segment to
|
||||
// get its curve, except for the first segment.
|
||||
var from = index > 0 ? index - 1 : index,
|
||||
start = from,
|
||||
if (curves) {
|
||||
var start = from,
|
||||
to = Math.min(from + amount, this._countCurves());
|
||||
if (segs._curves) {
|
||||
// Reuse removed curves.
|
||||
|
@ -810,34 +769,91 @@ var Path = PathItem.extend(/** @lends Path# */{
|
|||
clear: '#removeSegments',
|
||||
|
||||
/**
|
||||
* The approximate length of the path in points.
|
||||
* Checks if any of the curves in the path have curve handles set.
|
||||
*
|
||||
* @return {Boolean} {@true if the path has curve handles set}
|
||||
* @see Segment#hasHandles()
|
||||
* @see Curve#hasHandles()
|
||||
*/
|
||||
hasHandles: function() {
|
||||
var segments = this._segments;
|
||||
for (var i = 0, l = segments.length; i < l; i++) {
|
||||
if (segments[i].hasHandles())
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Clears the path's handles by setting their coordinates to zero,
|
||||
* turning the path into a polygon (or a polyline if it isn't closed).
|
||||
*/
|
||||
clearHandles: function() {
|
||||
var segments = this._segments;
|
||||
for (var i = 0, l = segments.length; i < l; i++)
|
||||
segments[i].clearHandles();
|
||||
},
|
||||
|
||||
/**
|
||||
* The approximate length of the path.
|
||||
*
|
||||
* @type Number
|
||||
* @bean
|
||||
*/
|
||||
getLength: function() {
|
||||
if (this._length == null) {
|
||||
var curves = this.getCurves();
|
||||
this._length = 0;
|
||||
var curves = this.getCurves(),
|
||||
length = 0;
|
||||
for (var i = 0, l = curves.length; i < l; i++)
|
||||
this._length += curves[i].getLength();
|
||||
length += curves[i].getLength();
|
||||
this._length = length;
|
||||
}
|
||||
return this._length;
|
||||
},
|
||||
|
||||
/**
|
||||
* The area of the path in square points. Self-intersecting paths can
|
||||
* contain sub-areas that cancel each other out.
|
||||
* The area that the path's geometry is covering. Self-intersecting paths
|
||||
* can contain sub-areas that cancel each other out.
|
||||
*
|
||||
* @type Number
|
||||
* @bean
|
||||
*/
|
||||
getArea: function() {
|
||||
var curves = this.getCurves();
|
||||
var area = 0;
|
||||
for (var i = 0, l = curves.length; i < l; i++)
|
||||
area += curves[i].getArea();
|
||||
return area;
|
||||
if (this._area == null) {
|
||||
var segments = this._segments,
|
||||
count = segments.length,
|
||||
last = count - 1,
|
||||
area = 0;
|
||||
for (var i = 0, l = this._closed ? count : last; i < l; i++) {
|
||||
area += Curve.getArea(Curve.getValues(
|
||||
segments[i], segments[i < last ? i + 1 : 0]));
|
||||
}
|
||||
this._area = area;
|
||||
}
|
||||
return this._area;
|
||||
},
|
||||
|
||||
/**
|
||||
* Specifies whether the path is oriented clock-wise.
|
||||
*
|
||||
* @type Boolean
|
||||
* @bean
|
||||
*/
|
||||
isClockwise: function() {
|
||||
if (this._clockwise !== undefined)
|
||||
return this._clockwise;
|
||||
return this.getArea() >= 0;
|
||||
},
|
||||
|
||||
setClockwise: function(clockwise) {
|
||||
// Only revers the path if its clockwise orientation is not the same
|
||||
// as what it is now demanded to be.
|
||||
// On-the-fly conversion to boolean:
|
||||
if (this.isClockwise() != (clockwise = !!clockwise))
|
||||
this.reverse();
|
||||
// Reverse only flips _clockwise state if it was already set, so let's
|
||||
// always set this here now.
|
||||
this._clockwise = clockwise;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -1009,10 +1025,9 @@ var Path = PathItem.extend(/** @lends Path# */{
|
|||
reduce: function() {
|
||||
var curves = this.getCurves();
|
||||
for (var i = curves.length - 1; i >= 0; i--) {
|
||||
var curve = curves[i],
|
||||
next;
|
||||
if (curve.isLinear() && (curve.getLength() === 0
|
||||
|| (next = curve.getNext()) && curve.isCollinear(next)))
|
||||
var curve = curves[i];
|
||||
if (!curve.hasHandles() && (curve.getLength() === 0
|
||||
|| curve.isCollinear(curve.getNext())))
|
||||
curve.remove();
|
||||
}
|
||||
return this;
|
||||
|
@ -1174,7 +1189,8 @@ var Path = PathItem.extend(/** @lends Path# */{
|
|||
*
|
||||
* @param {Number} index the index of the curve in the {@link Path#curves}
|
||||
* array at which to split
|
||||
* @param {Number} parameter the parameter at which the curve will be split
|
||||
* @param {Number} parameter the curve-time parameter at which the curve
|
||||
* will be split
|
||||
* @return {Path} the newly created path after splitting, if any
|
||||
*/
|
||||
split: function(index, parameter) {
|
||||
|
@ -1191,7 +1207,7 @@ var Path = PathItem.extend(/** @lends Path# */{
|
|||
index = arg.index;
|
||||
parameter = arg.parameter;
|
||||
}
|
||||
var tMin = /*#=*/Numerical.TOLERANCE,
|
||||
var tMin = /*#=*/Numerical.CURVETIME_EPSILON,
|
||||
tMax = 1 - tMin;
|
||||
if (parameter >= tMax) {
|
||||
// t == 1 is the same as t == 0 and index ++
|
||||
|
@ -1235,29 +1251,6 @@ var Path = PathItem.extend(/** @lends Path# */{
|
|||
return null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Specifies whether the path is oriented clock-wise.
|
||||
*
|
||||
* @type Boolean
|
||||
* @bean
|
||||
*/
|
||||
isClockwise: function() {
|
||||
if (this._clockwise !== undefined)
|
||||
return this._clockwise;
|
||||
return Path.isClockwise(this._segments);
|
||||
},
|
||||
|
||||
setClockwise: function(clockwise) {
|
||||
// Only revers the path if its clockwise orientation is not the same
|
||||
// as what it is now demanded to be.
|
||||
// On-the-fly conversion to boolean:
|
||||
if (this.isClockwise() != (clockwise = !!clockwise))
|
||||
this.reverse();
|
||||
// Reverse only flips _clockwise state if it was already set, so let's
|
||||
// always set this here now.
|
||||
this._clockwise = clockwise;
|
||||
},
|
||||
|
||||
/**
|
||||
* Reverses the orientation of the path, by reversing all its segments.
|
||||
*/
|
||||
|
@ -1414,15 +1407,47 @@ var Path = PathItem.extend(/** @lends Path# */{
|
|||
topCenter;
|
||||
|
||||
function isCollinear(i, j) {
|
||||
return segments[i].isCollinear(segments[j]);
|
||||
var seg1 = segments[i],
|
||||
seg2 = seg1.getNext(),
|
||||
seg3 = segments[j],
|
||||
seg4 = seg3.getNext();
|
||||
return seg1._handleOut.isZero() && seg2._handleIn.isZero()
|
||||
&& seg3._handleOut.isZero() && seg4._handleIn.isZero()
|
||||
&& seg2._point.subtract(seg1._point).isCollinear(
|
||||
seg4._point.subtract(seg3._point));
|
||||
}
|
||||
|
||||
function isOrthogonal(i) {
|
||||
return segments[i].isOrthogonal();
|
||||
var seg2 = segments[i],
|
||||
seg1 = seg2.getPrevious(),
|
||||
seg3 = seg2.getNext();
|
||||
return seg1._handleOut.isZero() && seg2._handleIn.isZero()
|
||||
&& seg2._handleOut.isZero() && seg3._handleIn.isZero()
|
||||
&& seg2._point.subtract(seg1._point).isOrthogonal(
|
||||
seg3._point.subtract(seg2._point));
|
||||
}
|
||||
|
||||
function isArc(i) {
|
||||
return segments[i].isOrthogonalArc();
|
||||
var seg1 = segments[i],
|
||||
seg2 = seg1.getNext(),
|
||||
handle1 = seg1._handleOut,
|
||||
handle2 = seg2._handleIn,
|
||||
kappa = /*#=*/Numerical.KAPPA;
|
||||
// Look at handle length and the distance to the imaginary corner
|
||||
// point and see if it their relation is kappa.
|
||||
if (handle1.isOrthogonal(handle2)) {
|
||||
var pt1 = seg1._point,
|
||||
pt2 = seg2._point,
|
||||
// Find the corner point by intersecting the lines described
|
||||
// by both handles:
|
||||
corner = new Line(pt1, handle1, true).intersect(
|
||||
new Line(pt2, handle2, true), true);
|
||||
return corner && Numerical.isZero(handle1.getLength() /
|
||||
corner.subtract(pt1).getLength() - kappa)
|
||||
&& Numerical.isZero(handle2.getLength() /
|
||||
corner.subtract(pt2).getLength() - kappa);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function getDistance(i, j) {
|
||||
|
@ -1974,7 +1999,7 @@ var Path = PathItem.extend(/** @lends Path# */{
|
|||
* Returns the nearest location on the path to the specified point.
|
||||
*
|
||||
* @function
|
||||
* @param point {Point} the point for which we search the nearest location
|
||||
* @param {Point} point the point for which we search the nearest location
|
||||
* @return {CurveLocation} the location on the path that's the closest to
|
||||
* the specified point
|
||||
*/
|
||||
|
@ -1997,7 +2022,7 @@ var Path = PathItem.extend(/** @lends Path# */{
|
|||
* Returns the nearest point on the path to the specified point.
|
||||
*
|
||||
* @function
|
||||
* @param point {Point} the point for which we search the nearest point
|
||||
* @param {Point} point the point for which we search the nearest point
|
||||
* @return {Point} the point on the path that's the closest to the specified
|
||||
* point
|
||||
*
|
||||
|
@ -2028,8 +2053,8 @@ var Path = PathItem.extend(/** @lends Path# */{
|
|||
getNearestPoint: function(/* point */) {
|
||||
return this.getNearestLocation.apply(this, arguments).getPoint();
|
||||
}
|
||||
}), new function() { // Scope for drawing
|
||||
|
||||
}),
|
||||
new function() { // Scope for drawing
|
||||
// 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
|
||||
|
@ -2228,8 +2253,8 @@ var Path = PathItem.extend(/** @lends Path# */{
|
|||
drawHandles(ctx, this._segments, matrix, paper.settings.handleSize);
|
||||
}
|
||||
};
|
||||
}, new function() { // Path Smoothing
|
||||
|
||||
},
|
||||
new function() { // Path Smoothing
|
||||
/**
|
||||
* Solves a tri-diagonal system for one of coordinates (x or y) of first
|
||||
* bezier control points.
|
||||
|
@ -2354,7 +2379,8 @@ var Path = PathItem.extend(/** @lends Path# */{
|
|||
}
|
||||
}
|
||||
};
|
||||
}, new function() { // PostScript-style drawing commands
|
||||
},
|
||||
new function() { // PostScript-style drawing commands
|
||||
/**
|
||||
* Helper method that returns the current segment and checks if a moveTo()
|
||||
* command is required first.
|
||||
|
@ -2477,7 +2503,6 @@ var Path = PathItem.extend(/** @lends Path# */{
|
|||
x = pt.x,
|
||||
y = pt.y,
|
||||
abs = Math.abs,
|
||||
epsilon = /*#=*/Numerical.EPSILON,
|
||||
rx = abs(radius.width),
|
||||
ry = abs(radius.height),
|
||||
rxSq = rx * rx,
|
||||
|
@ -2494,7 +2519,7 @@ var Path = PathItem.extend(/** @lends Path# */{
|
|||
}
|
||||
factor = (rxSq * rySq - rxSq * ySq - rySq * xSq) /
|
||||
(rxSq * ySq + rySq * xSq);
|
||||
if (abs(factor) < epsilon)
|
||||
if (abs(factor) < /*#=*/Numerical.EPSILON)
|
||||
factor = 0;
|
||||
if (factor < 0)
|
||||
throw new Error(
|
||||
|
@ -2664,21 +2689,6 @@ var Path = PathItem.extend(/** @lends Path# */{
|
|||
|
||||
// Mess with indentation in order to get more line-space below:
|
||||
statics: {
|
||||
/**
|
||||
* Determines whether the segments describe a path in clockwise or counter-
|
||||
* clockwise orientation.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
isClockwise: function(segments) {
|
||||
var sum = 0;
|
||||
// TODO: Check if this works correctly for all open paths.
|
||||
for (var i = 0, l = segments.length; i < l; i++)
|
||||
sum += Curve.getEdgeSum(Curve.getValues(
|
||||
segments[i], segments[i + 1 < l ? i + 1 : 0]));
|
||||
return sum > 0;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the bounding rectangle of the item excluding stroke width.
|
||||
*
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -32,10 +32,12 @@ var PathItem = Item.extend(/** @lends PathItem# */{
|
|||
* supported.
|
||||
*
|
||||
* @param {PathItem} path the other item to find the intersections with
|
||||
* @param {Boolean} [sorted=false] specifies whether the returned
|
||||
* {@link CurveLocation} objects should be sorted by path and offset
|
||||
* @param {Function} [include] a callback function that can be used to
|
||||
* filter out undesired locations right while they are collected.
|
||||
* When defined, it shall return {@true to include a location}.
|
||||
* @return {CurveLocation[]} the locations of all intersection between the
|
||||
* paths
|
||||
* @see #getCrossings(path)
|
||||
* @example {@paperscript} // Finding the intersections between two paths
|
||||
* var path = new Path.Rectangle(new Point(30, 25), new Size(50, 50));
|
||||
* path.strokeColor = 'black';
|
||||
|
@ -59,88 +61,100 @@ var PathItem = Item.extend(/** @lends PathItem# */{
|
|||
* }
|
||||
* }
|
||||
*/
|
||||
getIntersections: function(path, _matrix) {
|
||||
getIntersections: function(path, include, _matrix, _returnFirst) {
|
||||
// NOTE: For self-intersection, path is null. This means you can also
|
||||
// just call path.getIntersections() without an argument to get self
|
||||
// intersections.
|
||||
// NOTE: The hidden argument _matrix is used internally to override the
|
||||
// passed path's transformation matrix.
|
||||
return Curve.filterIntersections(this._getIntersections(
|
||||
this !== path ? path : null, _matrix, []));
|
||||
},
|
||||
|
||||
_getIntersections: function(path, matrix, locations, returnFirst) {
|
||||
var curves1 = this.getCurves(),
|
||||
curves2 = path ? path.getCurves() : curves1,
|
||||
var self = this === path || !path, // self-intersections?
|
||||
matrix1 = this._matrix.orNullIfIdentity(),
|
||||
matrix2 = path ? (matrix || path._matrix).orNullIfIdentity()
|
||||
: matrix1,
|
||||
length1 = curves1.length,
|
||||
length2 = path ? curves2.length : length1,
|
||||
values2 = [],
|
||||
tMin = /*#=*/Numerical.TOLERANCE,
|
||||
tMax = 1 - tMin;
|
||||
matrix2 = self ? matrix1
|
||||
: (_matrix || path._matrix).orNullIfIdentity();
|
||||
// First check the bounds of the two paths. If they don't intersect,
|
||||
// we don't need to iterate through their curves.
|
||||
if (path && !this.getBounds(matrix1).touches(path.getBounds(matrix2)))
|
||||
if (!self && !this.getBounds(matrix1).touches(path.getBounds(matrix2)))
|
||||
return [];
|
||||
var curves1 = this.getCurves(),
|
||||
curves2 = self ? curves1 : path.getCurves(),
|
||||
length1 = curves1.length,
|
||||
length2 = self ? length1 : curves2.length,
|
||||
values2 = [],
|
||||
arrays = [],
|
||||
locations,
|
||||
path;
|
||||
// Cache values for curves2 as we re-iterate them for each in curves1.
|
||||
for (var i = 0; i < length2; i++)
|
||||
values2[i] = curves2[i].getValues(matrix2);
|
||||
for (var i = 0; i < length1; i++) {
|
||||
var curve1 = curves1[i],
|
||||
values1 = path ? curve1.getValues(matrix1) : values2[i];
|
||||
if (!path) {
|
||||
// First check for self-intersections within the same curve
|
||||
var seg1 = curve1.getSegment1(),
|
||||
seg2 = curve1.getSegment2(),
|
||||
h1 = seg1._handleOut,
|
||||
h2 = seg2._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.
|
||||
if (new Line(seg1._point.subtract(h1), h1.multiply(2), true)
|
||||
.intersect(new Line(seg2._point.subtract(h2),
|
||||
h2.multiply(2), true), false)) {
|
||||
// Self intersecting is found by dividing the curve in two
|
||||
// and and then applying the normal curve intersection code.
|
||||
var parts = Curve.subdivide(values1);
|
||||
Curve.getIntersections(
|
||||
parts[0], parts[1], curve1, curve1, locations,
|
||||
function(loc) {
|
||||
if (loc._parameter <= tMax) {
|
||||
// Since the curve was split above, we need to
|
||||
// adjust the parameters for both locations.
|
||||
loc._parameter /= 2;
|
||||
loc._parameter2 = 0.5 + loc._parameter2 / 2;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
values1 = self ? values2[i] : curve1.getValues(matrix1),
|
||||
path1 = curve1.getPath();
|
||||
// NOTE: Due to the nature of Curve._getIntersections(), we need to
|
||||
// use separate location arrays per path1, to make sure the
|
||||
// circularity checks are not getting confused by locations on
|
||||
// separate paths. We are flattening the separate arrays at the end.
|
||||
if (path1 !== path) {
|
||||
path = path1;
|
||||
locations = [];
|
||||
arrays.push(locations);
|
||||
}
|
||||
if (self) {
|
||||
// First check for self-intersections within the same curve.
|
||||
Curve._getSelfIntersection(values1, curve1, locations, {
|
||||
include: include,
|
||||
// Only possible if there is only one closed curve:
|
||||
startConnected: length1 === 1 &&
|
||||
curve1.getPoint1().equals(curve1.getPoint2())
|
||||
});
|
||||
}
|
||||
// Check for intersections with other curves. For self intersection,
|
||||
// we can start at i + 1 instead of 0
|
||||
for (var j = path ? 0 : i + 1; j < length2; j++) {
|
||||
for (var j = self ? i + 1 : 0; j < length2; j++) {
|
||||
// There might be already one location from the above
|
||||
// self-intersection check:
|
||||
if (returnFirst && locations.length)
|
||||
break;
|
||||
Curve.getIntersections(
|
||||
values1, values2[j], curve1, curves2[j], locations,
|
||||
// Avoid end point intersections on consecutive curves when
|
||||
// self intersecting.
|
||||
!path && (j === i + 1 || j === length2 - 1 && i === 0)
|
||||
&& function(loc) {
|
||||
var t = loc._parameter;
|
||||
return t >= tMin && t <= tMax;
|
||||
}
|
||||
if (_returnFirst && locations.length)
|
||||
return locations;
|
||||
var curve2 = curves2[j];
|
||||
// Avoid end point intersections on consecutive curves when
|
||||
// self intersecting.
|
||||
Curve._getIntersections(
|
||||
values1, values2[j], curve1, curve2, locations,
|
||||
{
|
||||
include: include,
|
||||
// Do not compare indices here to determine connection,
|
||||
// since one array of curves can contain curves from
|
||||
// separate sup-paths of a compound path.
|
||||
startConnected: self && curve1.getPrevious() === curve2,
|
||||
endConnected: self && curve1.getNext() === curve2
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
// Now flatten the list of location arrays to one array and return it.
|
||||
locations = [];
|
||||
for (var i = 0, l = arrays.length; i < l; i++) {
|
||||
locations.push.apply(locations, arrays[i]);
|
||||
}
|
||||
return locations;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns all crossings between two {@link PathItem} items as an array
|
||||
* of {@link CurveLocation} objects. {@link CompoundPath} items are also
|
||||
* supported.
|
||||
* Crossings are intersections where the paths actually are crossing each
|
||||
* other, as opposed to simply touching.
|
||||
*
|
||||
* @param {PathItem} path the other item to find the crossings with
|
||||
* @see #getIntersections(path)
|
||||
*/
|
||||
getCrossings: function(path) {
|
||||
return this.getIntersections(path, function(inter) {
|
||||
return inter.isCrossing();
|
||||
});
|
||||
},
|
||||
|
||||
_asPathItem: function() {
|
||||
// See Item#_asPathItem()
|
||||
return this;
|
||||
|
|
|
@ -58,7 +58,7 @@ var PathIterator = Base.extend({
|
|||
// appears to offer a good trade-off between speed and
|
||||
// precision for display purposes.
|
||||
&& !Curve.isFlatEnough(curve, tolerance || 0.25)) {
|
||||
var split = Curve.subdivide(curve),
|
||||
var split = Curve.subdivide(curve, 0.5),
|
||||
halfT = (minT + maxT) / 2;
|
||||
// Recursively subdivide and compute parts again.
|
||||
computeParts(split[0], index, minT, halfT);
|
||||
|
|
|
@ -119,7 +119,7 @@ var Segment = Base.extend(/** @lends Segment# */{
|
|||
// Nothing
|
||||
} else if (count === 1) {
|
||||
// Note: This copies from existing segments through accessors.
|
||||
if (arg0.point) {
|
||||
if ('point' in arg0) {
|
||||
point = arg0.point;
|
||||
handleIn = arg0.handleIn;
|
||||
handleOut = arg0.handleOut;
|
||||
|
@ -145,9 +145,10 @@ var Segment = Base.extend(/** @lends Segment# */{
|
|||
},
|
||||
|
||||
_serialize: function(options) {
|
||||
// If it is straight, only serialize point, otherwise handles too.
|
||||
return Base.serialize(this.isStraight() ? this._point
|
||||
: [this._point, this._handleIn, this._handleOut],
|
||||
// If it is has no handles, only serialize point, otherwise handles too.
|
||||
return Base.serialize(this.hasHandles()
|
||||
? [this._point, this._handleIn, this._handleOut]
|
||||
: this._point,
|
||||
options, true);
|
||||
},
|
||||
|
||||
|
@ -229,87 +230,27 @@ var Segment = Base.extend(/** @lends Segment# */{
|
|||
},
|
||||
|
||||
/**
|
||||
* Checks whether the segment has curve handles defined, meaning it is not
|
||||
* a straight segment.
|
||||
* Checks if the segment has any curve handles set.
|
||||
*
|
||||
* @return {Boolean} {@true if the segment has handles defined}
|
||||
* @return {Boolean} {@true if the segment has handles set}
|
||||
* @see Segment#getHandleIn()
|
||||
* @see Segment#getHandleOut()
|
||||
* @see Curve#hasHandles()
|
||||
* @see Path#hasHandles()
|
||||
*/
|
||||
hasHandles: function() {
|
||||
return !this.isStraight();
|
||||
return !this._handleIn.isZero() || !this._handleOut.isZero();
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks whether the segment is straight, meaning it has no curve handles
|
||||
* defined. If two straight segments follow each each other in a path, the
|
||||
* curve between them will appear as a straight line.
|
||||
* Note that this is not the same as {@link #isLinear()}, which performs a
|
||||
* full linearity check that includes handles running collinear to the line
|
||||
* direction.
|
||||
*
|
||||
* @return {Boolean} {@true if the segment is straight}
|
||||
* @see Curve#isStraight()
|
||||
* Clears the segment's handles by setting their coordinates to zero,
|
||||
* turning the segment into a corner.
|
||||
*/
|
||||
isStraight: function() {
|
||||
return this._handleIn.isZero() && this._handleOut.isZero();
|
||||
clearHandles: function() {
|
||||
this._handleIn.set(0, 0);
|
||||
this._handleOut.set(0, 0);
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks if the curve that starts in this segment appears as a line. This
|
||||
* can mean that it has no handles defined, or that the handles run
|
||||
* collinear with the line.
|
||||
*
|
||||
* @return {Boolean} {@true if the curve is linear}
|
||||
* @see Curve#isLinear()
|
||||
* @see Path#isLinear()
|
||||
*/
|
||||
isLinear: function() {
|
||||
return Segment.isLinear(this, this.getNext());
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks if the the two segments are the beginning of two lines that are
|
||||
* collinear, meaning they run in parallel.
|
||||
*
|
||||
* @param {Segment} the other segment to check against
|
||||
* @return {Boolean} {@true if the two lines are collinear}
|
||||
* @see Curve#isCollinear(curve)
|
||||
*/
|
||||
isCollinear: function(segment) {
|
||||
return Segment.isCollinear(this, this.getNext(),
|
||||
segment, segment.getNext());
|
||||
},
|
||||
|
||||
// TODO: Remove version with typo after a while (deprecated June 2015)
|
||||
isColinear: '#isCollinear',
|
||||
|
||||
/**
|
||||
* Checks if the segment is connecting two lines that are orthogonal,
|
||||
* meaning they connect at an 90° angle.
|
||||
*
|
||||
* @return {Boolean} {@true if the two lines connected by this segment are
|
||||
* orthogonal}
|
||||
*/
|
||||
isOrthogonal: function() {
|
||||
return Segment.isOrthogonal(this.getPrevious(), this, this.getNext());
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks if the segment is the beginning of an orthogonal arc, as used in
|
||||
* the construction of circles and ellipses.
|
||||
*
|
||||
* @return {Boolean} {@true if the segment is the beginning of an orthogonal
|
||||
* arc}
|
||||
* @see Curve#isOrthogonalArc()
|
||||
*/
|
||||
isOrthogonalArc: function() {
|
||||
return Segment.isOrthogonalArc(this, this.getNext());
|
||||
},
|
||||
|
||||
// TODO: Remove a while (deprecated August 2015)
|
||||
isArc: '#isOrthogonalArc',
|
||||
|
||||
_selectionState: 0,
|
||||
|
||||
/**
|
||||
|
@ -452,10 +393,45 @@ var Segment = Base.extend(/** @lends Segment# */{
|
|||
},
|
||||
|
||||
/**
|
||||
* Returns the reversed the segment, without modifying the segment itself.
|
||||
* Checks if the this is the first segment in the {@link Path#segments}
|
||||
* array.
|
||||
*
|
||||
* @return {Boolean} {@true if this is the first segment}
|
||||
*/
|
||||
isFirst: function() {
|
||||
return this._index === 0;
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks if the this is the last segment in the {@link Path#segments}
|
||||
* array.
|
||||
*
|
||||
* @return {Boolean} {@true if this is the last segment}
|
||||
*/
|
||||
isLast: function() {
|
||||
var path = this._path;
|
||||
return path && this._index === path._segments.length - 1 || false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Reverses the {@link #handleIn} and {@link #handleOut} vectors of this
|
||||
* segment. Note: the actual segment is modified, no copy is created.
|
||||
* @return {Segment} the reversed segment
|
||||
*/
|
||||
reverse: function() {
|
||||
var handleIn = this._handleIn,
|
||||
handleOut = this._handleOut,
|
||||
inX = handleIn._x,
|
||||
inY = handleIn._y;
|
||||
handleIn.set(handleOut._x, handleOut._y);
|
||||
handleOut.set(inX, inY);
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the reversed the segment, without modifying the segment itself.
|
||||
* @return {Segment} the reversed segment
|
||||
*/
|
||||
reversed: function() {
|
||||
return new Segment(this._point, this._handleOut, this._handleIn);
|
||||
},
|
||||
|
||||
|
@ -564,53 +540,5 @@ var Segment = Base.extend(/** @lends Segment# */{
|
|||
}
|
||||
}
|
||||
return coords;
|
||||
},
|
||||
|
||||
statics: {
|
||||
// These statics are shared between Segment and Curve, for versions of
|
||||
// these methods that are implemented in both places.
|
||||
|
||||
isLinear: function(seg1, seg2) {
|
||||
var l = seg2._point.subtract(seg1._point);
|
||||
return l.isCollinear(seg1._handleOut)
|
||||
&& l.isCollinear(seg2._handleIn);
|
||||
},
|
||||
|
||||
isCollinear: function(seg1, seg2, seg3, seg4) {
|
||||
// TODO: This assumes isStraight(), while isLinear() allows handles!
|
||||
return seg1._handleOut.isZero() && seg2._handleIn.isZero()
|
||||
&& seg3._handleOut.isZero() && seg4._handleIn.isZero()
|
||||
&& seg2._point.subtract(seg1._point).isCollinear(
|
||||
seg4._point.subtract(seg3._point));
|
||||
},
|
||||
|
||||
isOrthogonal: function(seg1, seg2, seg3) {
|
||||
// TODO: This assumes isStraight(), while isLinear() allows handles!
|
||||
return seg1._handleOut.isZero() && seg2._handleIn.isZero()
|
||||
&& seg2._handleOut.isZero() && seg3._handleIn.isZero()
|
||||
&& seg2._point.subtract(seg1._point).isOrthogonal(
|
||||
seg3._point.subtract(seg2._point));
|
||||
},
|
||||
|
||||
isOrthogonalArc: function(seg1, seg2) {
|
||||
var handle1 = seg1._handleOut,
|
||||
handle2 = seg2._handleIn,
|
||||
kappa = /*#=*/Numerical.KAPPA;
|
||||
// Look at handle length and the distance to the imaginary corner
|
||||
// point and see if it their relation is kappa.
|
||||
if (handle1.isOrthogonal(handle2)) {
|
||||
var pt1 = seg1._point,
|
||||
pt2 = seg2._point,
|
||||
// Find the corner point by intersecting the lines described
|
||||
// by both handles:
|
||||
corner = new Line(pt1, handle1, true).intersect(
|
||||
new Line(pt2, handle2, true), true);
|
||||
return corner && Numerical.isZero(handle1.getLength() /
|
||||
corner.subtract(pt1).getLength() - kappa)
|
||||
&& Numerical.isZero(handle2.getLength() /
|
||||
corner.subtract(pt2).getLength() - kappa);
|
||||
}
|
||||
return false;
|
||||
},
|
||||
}
|
||||
});
|
||||
|
|
|
@ -304,7 +304,7 @@ var Project = PaperScopeItem.extend(/** @lends Project# */{
|
|||
* and may contain a combination of the following values:
|
||||
*
|
||||
* @option [options.tolerance={@link PaperScope#settings}.hitTolerance]
|
||||
* {Number} the tolerance of the hit-test in points
|
||||
* {Number} the tolerance of the hit-test
|
||||
* @option options.class {Function} only hit-test again a certain item class
|
||||
* and its sub-classes: {@code Group, Layer, Path, CompoundPath,
|
||||
* Shape, Raster, PlacedSymbol, PointText}, etc
|
||||
|
|
|
@ -133,7 +133,7 @@ var Symbol = Base.extend(/** @lends Symbol# */{
|
|||
/**
|
||||
* Places in instance of the symbol in the project.
|
||||
*
|
||||
* @param [position] The position of the placed symbol
|
||||
* @param {Point} [position] the position of the placed symbol
|
||||
* @return {PlacedSymbol}
|
||||
*/
|
||||
place: function(position) {
|
||||
|
|
|
@ -1112,7 +1112,8 @@ var Color = Base.extend(new function() {
|
|||
}
|
||||
}
|
||||
});
|
||||
}, new function() {
|
||||
},
|
||||
new function() {
|
||||
var operators = {
|
||||
add: function(a, b) {
|
||||
return a + b;
|
||||
|
|
|
@ -309,7 +309,7 @@ var Style = Base.extend(new function() {
|
|||
/**
|
||||
* @private
|
||||
* @bean
|
||||
* @deprecated use {@link #getFontFamily()} instead.
|
||||
* @deprecated use {@link #fontFamily} instead.
|
||||
*/
|
||||
getFont: '#getFontFamily',
|
||||
setFont: '#setFontFamily',
|
||||
|
@ -582,7 +582,7 @@ var Style = Base.extend(new function() {
|
|||
*/
|
||||
|
||||
/**
|
||||
* The font size of text content, as {@Number} in pixels, or as {@String}
|
||||
* The font size of text content, as a number in pixels, or as a string
|
||||
* with optional units {@code 'px'}, {@code 'pt'} and {@code 'em'}.
|
||||
*
|
||||
* @name Style#fontSize
|
||||
|
@ -592,12 +592,12 @@ var Style = Base.extend(new function() {
|
|||
|
||||
/**
|
||||
*
|
||||
* The font-family to be used in text content, as one {@String}.
|
||||
* @deprecated use {@link #fontFamily} instead.
|
||||
* The font-family to be used in text content, as one string.
|
||||
*
|
||||
* @name Style#font
|
||||
* @default 'sans-serif'
|
||||
* @type String
|
||||
* @deprecated use {@link #fontFamily} instead.
|
||||
*/
|
||||
|
||||
/**
|
||||
|
|
|
@ -120,7 +120,7 @@ var TextItem = Item.extend(/** @lends TextItem# */{
|
|||
*/
|
||||
|
||||
/**
|
||||
* The font size of text content, as {@Number} in pixels, or as {@String}
|
||||
* The font size of text content, as a number in pixels, or as a string
|
||||
* with optional units {@code 'px'}, {@code 'pt'} and {@code 'em'}.
|
||||
*
|
||||
* @name TextItem#fontSize
|
||||
|
@ -130,7 +130,7 @@ var TextItem = Item.extend(/** @lends TextItem# */{
|
|||
|
||||
/**
|
||||
*
|
||||
* The font-family to be used in text content, as one {@String}.
|
||||
* The font-family to be used in text content, as one string.
|
||||
* @deprecated use {@link #fontFamily} instead.
|
||||
*
|
||||
* @name TextItem#font
|
||||
|
@ -159,7 +159,7 @@ var TextItem = Item.extend(/** @lends TextItem# */{
|
|||
/**
|
||||
* @private
|
||||
* @bean
|
||||
* @deprecated use {@link #getStyle()} instead.
|
||||
* @deprecated use {@link #style} instead.
|
||||
*/
|
||||
getCharacterStyle: '#getStyle',
|
||||
setCharacterStyle: '#setStyle',
|
||||
|
@ -167,7 +167,7 @@ var TextItem = Item.extend(/** @lends TextItem# */{
|
|||
/**
|
||||
* @private
|
||||
* @bean
|
||||
* @deprecated use {@link #getStyle()} instead.
|
||||
* @deprecated use {@link #style} instead.
|
||||
*/
|
||||
getParagraphStyle: '#getStyle',
|
||||
setParagraphStyle: '#setStyle'
|
||||
|
|
|
@ -60,12 +60,15 @@ var Numerical = new function() {
|
|||
var abs = Math.abs,
|
||||
sqrt = Math.sqrt,
|
||||
pow = Math.pow,
|
||||
TOLERANCE = 1e-6,
|
||||
EPSILON = 1e-12,
|
||||
MACHINE_EPSILON = 1.12e-16;
|
||||
|
||||
function clip(value, min, max) {
|
||||
return value < min ? min : value > max ? max : value;
|
||||
}
|
||||
|
||||
return /** @lends Numerical */{
|
||||
TOLERANCE: TOLERANCE,
|
||||
TOLERANCE: 1e-6,
|
||||
// Precision when comparing against 0
|
||||
// References:
|
||||
// http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html
|
||||
|
@ -78,28 +81,49 @@ var Numerical = new function() {
|
|||
*/
|
||||
EPSILON: EPSILON,
|
||||
/**
|
||||
* MACHINE_EPSILON for a double precision (Javascript Number) is
|
||||
* 2.220446049250313e-16. (try this in the js console)
|
||||
* The machine epsilon for a double precision (Javascript Number) is
|
||||
* 2.220446049250313e-16. (try this in the js console:
|
||||
* (function(){for(var e=1;1<1+e/2;)e/=2;return e}())
|
||||
*
|
||||
* Here the constant MACHINE_EPSILON refers to the constants 'δ' and 'ε'
|
||||
* such that, the error introduced by addition, multiplication
|
||||
* on a 64bit float (js Number) will be less than δ and ε. That is to
|
||||
* say, for all X and Y representable by a js Number object, S and P
|
||||
* be their 'exact' sum and product respectively, then
|
||||
* The constant MACHINE_EPSILON here refers to the constants δ and ε
|
||||
* such that, the error introduced by addition, multiplication on a
|
||||
* 64bit float (js Number) will be less than δ and ε. That is to say,
|
||||
* for all X and Y representable by a js Number object, S and P be their
|
||||
* 'exact' sum and product respectively, then
|
||||
* |S - (x+y)| <= δ|S|, and |P - (x*y)| <= ε|P|.
|
||||
* This amounts to about half of the actual MACHINE_EPSILON
|
||||
* This amounts to about half of the actual machine epsilon.
|
||||
*/
|
||||
MACHINE_EPSILON: MACHINE_EPSILON,
|
||||
/**
|
||||
* The epsilon to be used when handling curve-time parameters. This
|
||||
* cannot be smaller, because errors add up to around 8e-7 in the bezier
|
||||
* fat-line clipping code as a result of recursive sub-division.
|
||||
*/
|
||||
CURVETIME_EPSILON: 8e-7,
|
||||
/**
|
||||
* The epsilon to be used when performing "geometric" checks, such as
|
||||
* point distances and examining cross products to check for
|
||||
* collinearity.
|
||||
*/
|
||||
GEOMETRIC_EPSILON: 4e-7, // NOTE: 1e-7 doesn't work in some edge-cases!
|
||||
/**
|
||||
* The epsilon to be used when performing winding contribution checks.
|
||||
*/
|
||||
WINDING_EPSILON: 2e-7, // NOTE: 1e-7 doesn't work in some edge-cases!
|
||||
/**
|
||||
* The epsilon to be used when performing "trigonometric" checks, such
|
||||
* as examining cross products to check for collinearity.
|
||||
*/
|
||||
TRIGONOMETRIC_EPSILON: 1e-7,
|
||||
// Kappa, see: http://www.whizkidtech.redprince.net/bezier/circle/kappa/
|
||||
KAPPA: 4 * (sqrt(2) - 1) / 3,
|
||||
|
||||
/**
|
||||
* Check if the value is 0, within a tolerance defined by
|
||||
* Checks if the value is 0, within a tolerance defined by
|
||||
* Numerical.EPSILON.
|
||||
*/
|
||||
isZero: function(val) {
|
||||
return abs(val) <= EPSILON;
|
||||
return val >= -EPSILON && val <= EPSILON;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -158,7 +182,7 @@ var Numerical = new function() {
|
|||
*
|
||||
* References:
|
||||
* Kahan W. - "To Solve a Real Cubic Equation"
|
||||
* http://www.cs.berkeley.edu/~wkahan/Math128/Cubic.pdf
|
||||
* http://www.cs.berkeley.edu/~wkahan/Math128/Cubic.pdf
|
||||
* Blinn J. - "How to solve a Quadratic Equation"
|
||||
*
|
||||
* @param {Number} a the quadratic term
|
||||
|
@ -174,10 +198,14 @@ var Numerical = new function() {
|
|||
*/
|
||||
solveQuadratic: function(a, b, c, roots, min, max) {
|
||||
var count = 0,
|
||||
eMin = min - EPSILON,
|
||||
eMax = max + EPSILON,
|
||||
x1, x2 = Infinity,
|
||||
B = b,
|
||||
D;
|
||||
b /= 2;
|
||||
// a, b, c are expected to be the coefficients of the equation:
|
||||
// Ax² - 2Bx + C == 0, so we take b = -B/2:
|
||||
b /= -2;
|
||||
D = b * b - a * c; // Discriminant
|
||||
// If the discriminant is very small, we can try to pre-condition
|
||||
// the coefficients, so that we may get better accuracy
|
||||
|
@ -188,8 +216,8 @@ var Numerical = new function() {
|
|||
// We multiply with a factor to normalize the coefficients.
|
||||
// The factor is just the nearest exponent of 10, big enough
|
||||
// to raise all the coefficients to nearly [-1, +1] range.
|
||||
var mult = pow(10, abs(
|
||||
Math.floor(Math.log(gmC) * Math.LOG10E)));
|
||||
var mult = pow(10,
|
||||
abs(Math.floor(Math.log(gmC) * Math.LOG10E)));
|
||||
if (!isFinite(mult))
|
||||
mult = 0;
|
||||
a *= mult;
|
||||
|
@ -204,29 +232,25 @@ var Numerical = new function() {
|
|||
if (abs(B) < EPSILON)
|
||||
return abs(c) < EPSILON ? -1 : 0;
|
||||
x1 = -c / B;
|
||||
} else {
|
||||
// No real roots if D < 0
|
||||
if (D >= -MACHINE_EPSILON) {
|
||||
D = D < 0 ? 0 : D;
|
||||
var R = sqrt(D);
|
||||
// Try to minimise floating point noise.
|
||||
if (b >= MACHINE_EPSILON && b <= MACHINE_EPSILON) {
|
||||
x1 = abs(a) >= abs(c) ? R / a : -c / R;
|
||||
x2 = -x1;
|
||||
} else {
|
||||
var q = -(b + (b < 0 ? -1 : 1) * R);
|
||||
x1 = q / a;
|
||||
x2 = c / q;
|
||||
}
|
||||
// Do we actually have two real roots?
|
||||
// count = D > MACHINE_EPSILON ? 2 : 1;
|
||||
} else if (D >= -MACHINE_EPSILON) { // No real roots if D < 0
|
||||
var Q = D < 0 ? 0 : sqrt(D),
|
||||
R = b + (b < 0 ? -Q : Q);
|
||||
// Try to minimize floating point noise.
|
||||
if (R === 0) {
|
||||
x1 = c / a;
|
||||
x2 = -x1;
|
||||
} else {
|
||||
x1 = R / a;
|
||||
x2 = c / R;
|
||||
}
|
||||
}
|
||||
if (isFinite(x1) && (min == null || x1 >= min && x1 <= max))
|
||||
roots[count++] = x1;
|
||||
// We need to include EPSILON in the comparisons with min / max,
|
||||
// as some solutions are ever so lightly out of bounds.
|
||||
if (isFinite(x1) && (min == null || x1 > eMin && x1 < eMax))
|
||||
roots[count++] = min == null ? x1 : clip(x1, min, max);
|
||||
if (x2 !== x1
|
||||
&& isFinite(x2) && (min == null || x2 >= min && x2 <= max))
|
||||
roots[count++] = x2;
|
||||
&& isFinite(x2) && (min == null || x2 > eMin && x2 < eMax))
|
||||
roots[count++] = min == null ? x2 : clip(x2, min, max);
|
||||
return count;
|
||||
},
|
||||
|
||||
|
@ -321,8 +345,8 @@ var Numerical = new function() {
|
|||
// The cubic has been deflated to a quadratic.
|
||||
var count = Numerical.solveQuadratic(a, b1, c2, roots, min, max);
|
||||
if (isFinite(x) && (count === 0 || x !== roots[count - 1])
|
||||
&& (min == null || x >= min && x <= max))
|
||||
roots[count++] = x;
|
||||
&& (min == null || x > min - EPSILON && x < max + EPSILON))
|
||||
roots[count++] = min == null ? x : clip(x, min, max);
|
||||
return count;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -144,8 +144,8 @@ var CanvasView = View.extend(/** @lends CanvasView# */{
|
|||
project._needsUpdate = false;
|
||||
return true;
|
||||
}
|
||||
}, new function() { // Item based mouse handling:
|
||||
|
||||
},
|
||||
new function() { // Item based mouse handling:
|
||||
var downPoint,
|
||||
lastPoint,
|
||||
overPoint,
|
||||
|
|
|
@ -675,8 +675,8 @@ var View = Base.extend(Emitter, /** @lends View# */{
|
|||
return new CanvasView(project, element);
|
||||
}
|
||||
}
|
||||
}, new function() {
|
||||
// Injection scope for mouse events on the browser
|
||||
},
|
||||
new function() { // Injection scope for mouse events on the browser
|
||||
/*#*/ if (__options.environment == 'browser') {
|
||||
var tool,
|
||||
prevFocus,
|
||||
|
|
|
@ -286,7 +286,7 @@ function asyncTest(testName, expected) {
|
|||
var project = new Project();
|
||||
expected(function() {
|
||||
project.remove();
|
||||
start();
|
||||
QUnit.start();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -161,7 +161,7 @@ test('Curve#getParameterAt()', function() {
|
|||
var t2 = curve.getParameterAt(o2);
|
||||
equals(t1, t2, 'Curve parameter at offset ' + o1
|
||||
+ ' should be the same value as at offset' + o2,
|
||||
Numerical.TOLERANCE);
|
||||
Numerical.CURVETIME_EPSILON);
|
||||
}
|
||||
|
||||
equals(curve.getParameterAt(curve.length + 1), null,
|
||||
|
@ -178,3 +178,48 @@ test('Curve#getLocationAt()', function() {
|
|||
'Should return null when offset is out of range.');
|
||||
// 'Should return null when point is not on the curve.');
|
||||
});
|
||||
|
||||
test('Curve#isStraight()', function() {
|
||||
equals(function() {
|
||||
return new Curve([100, 100], null, null, [200, 200]).isStraight();
|
||||
}, true);
|
||||
equals(function() {
|
||||
return new Curve([100, 100], [-50, -50], null, [200, 200]).isStraight();
|
||||
}, false);
|
||||
equals(function() {
|
||||
return new Curve([100, 100], [50, 50], null, [200, 200]).isStraight();
|
||||
}, true);
|
||||
equals(function() {
|
||||
return new Curve([100, 100], [50, 50], [-50, -50], [200, 200]).isStraight();
|
||||
}, true);
|
||||
equals(function() {
|
||||
return new Curve([100, 100], [50, 50], [50, 50], [200, 200]).isStraight();
|
||||
}, false);
|
||||
equals(function() {
|
||||
return new Curve([100, 100], null, [-50, -50], [200, 200]).isStraight();
|
||||
}, true);
|
||||
equals(function() {
|
||||
return new Curve([100, 100], null, [50, 50], [200, 200]).isStraight();
|
||||
}, false);
|
||||
equals(function() {
|
||||
return new Curve([100, 100], null, null, [100, 100]).isStraight();
|
||||
}, true);
|
||||
equals(function() {
|
||||
return new Curve([100, 100], [50, 50], null, [100, 100]).isStraight();
|
||||
}, false);
|
||||
equals(function() {
|
||||
return new Curve([100, 100], null, [-50, -50], [100, 100]).isStraight();
|
||||
}, false);
|
||||
});
|
||||
|
||||
test('Curve#isLinear()', function() {
|
||||
equals(function() {
|
||||
return new Curve([100, 100], [100 / 3, 100 / 3], [-100 / 3, -100 / 3], [200, 200]).isLinear();
|
||||
}, true);
|
||||
equals(function() {
|
||||
return new Curve([100, 100], null, null, [100, 100]).isLinear();
|
||||
}, true);
|
||||
equals(function() {
|
||||
return new Curve([100, 100], null, null, [200, 200]).isLinear();
|
||||
}, false);
|
||||
});
|
||||
|
|
|
@ -16,8 +16,7 @@ test('Decomposition: rotate()', function() {
|
|||
var m = new Matrix().rotate(a),
|
||||
s = 'new Matrix().rotate(' + a + ')';
|
||||
equals(m.getRotation(), Base.pick(ea, a),
|
||||
s + '.getRotation()',
|
||||
Numerical.TOLERANCE);
|
||||
s + '.getRotation()');
|
||||
equals(m.getScaling(), new Point(1, 1),
|
||||
s + '.getScaling()');
|
||||
}
|
||||
|
@ -42,8 +41,7 @@ test('Decomposition: scale()', function() {
|
|||
var m = new Matrix().scale(sx, sy),
|
||||
s = 'new Matrix().scale(' + sx + ', ' + sy + ')';
|
||||
equals(m.getRotation(), ea || 0,
|
||||
s + '.getRotation()',
|
||||
Numerical.TOLERANCE);
|
||||
s + '.getRotation()');
|
||||
equals(m.getScaling(), new Point(Base.pick(ex, sx), Base.pick(ey, sy)),
|
||||
s + '.getScaling()');
|
||||
}
|
||||
|
@ -64,8 +62,7 @@ test('Decomposition: rotate() & scale()', function() {
|
|||
var m = new Matrix().scale(sx, sy).rotate(a),
|
||||
s = 'new Matrix().scale(' + sx + ', ' + sy + ').rotate(' + a + ')';
|
||||
equals(m.getRotation(), ea || a,
|
||||
s + '.getRotation()',
|
||||
Numerical.TOLERANCE);
|
||||
s + '.getRotation()');
|
||||
equals(m.getScaling(), new Point(Base.pick(ex, sx), Base.pick(ey, sy)),
|
||||
s + '.getScaling()');
|
||||
}
|
||||
|
|
|
@ -51,5 +51,5 @@ test('ring.subtract(square); #610', function() {
|
|||
var ring = outer.subtract(inner);
|
||||
var result = ring.subtract(square);
|
||||
|
||||
equals(result.pathData, 'M-10,131.62689c-68.2302,-5.11075 -122,-62.08951 -122,-131.62689c0,-69.53737 53.7698,-126.51614 122,-131.62689l0,32.12064c-50.53323,5.01724 -90,47.65277 -90,99.50625c0,51.85348 39.46677,94.489 90,99.50625z');
|
||||
equals(result.pathData, 'M-132,0c0,-69.53737 53.7698,-126.51614 122,-131.62689l0,32.12064c-50.53323,5.01724 -90,47.65277 -90,99.50625c0,51.85348 39.46677,94.489 90,99.50625l0,32.12064c-68.2302,-5.11075 -122,-62.08951 -122,-131.62689z');
|
||||
});
|
||||
|
|
|
@ -107,10 +107,10 @@ test('Curve list after removing a segment - 2', function() {
|
|||
}, 1, 'After removing the last segment, we should be left with one curve');
|
||||
});
|
||||
|
||||
test('Splitting a straight path should produce straight segments', function() {
|
||||
var path = new Path.Line([0, 0], [50, 50]);
|
||||
var path2 = path.split(0, 0.5);
|
||||
test('Splitting a straight path should produce segments without handles', function() {
|
||||
var path1 = new Path.Line([0, 0], [50, 50]);
|
||||
var path2 = path1.split(0, 0.5);
|
||||
equals(function() {
|
||||
return path2.firstSegment.isStraight();
|
||||
return !path1.lastSegment.hasHandles() && !path2.firstSegment.hasHandles();
|
||||
}, true);
|
||||
});
|
||||
|
|
|
@ -43,7 +43,7 @@ test('new Segment(size)', function() {
|
|||
|
||||
test('segment.reverse()', function() {
|
||||
var segment = new Segment(new Point(10, 10), new Point(5, 5), new Point(15, 15));
|
||||
segment = segment.reverse();
|
||||
segment.reverse();
|
||||
equals(segment.toString(), '{ point: { x: 10, y: 10 }, handleIn: { x: 15, y: 15 }, handleOut: { x: 5, y: 5 } }');
|
||||
});
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ module('TextItem');
|
|||
|
||||
test('PointText', function() {
|
||||
var text = new PointText({
|
||||
fontFamily: 'Arial',
|
||||
fontFamily: 'Helvetica, Arial',
|
||||
fontSize: 14,
|
||||
point: [100, 100],
|
||||
content: 'Hello World!'
|
||||
|
@ -22,7 +22,7 @@ test('PointText', function() {
|
|||
equals(text.fillColor, new Color(0, 0, 0), 'text.fillColor should be black by default');
|
||||
equals(text.point, new Point(100, 100), 'text.point');
|
||||
equals(text.bounds.point, new Point(100, 87.4), 'text.bounds.point');
|
||||
equals(text.bounds.size, new Size(77, 16.8), 'text.bounds.size', { tolerance: 1.0 });
|
||||
equals(text.bounds.size, new Size(76, 16.8), 'text.bounds.size', { tolerance: 1.0 });
|
||||
equals(function() {
|
||||
return text.hitTest(text.bounds.center) != null;
|
||||
}, true);
|
||||
|
|
Loading…
Reference in a new issue