Merge remote-tracking branch 'origin/boolean-fix' into develop

This commit is contained in:
Jürg Lehni 2015-10-21 01:44:15 +02:00
commit 2cf637d13b
33 changed files with 2293 additions and 1470 deletions

View file

@ -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:

View file

@ -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));
}
}

View file

@ -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"
},

View file

@ -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);
}
}
});

View file

@ -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) {

View file

@ -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) {

View file

@ -120,7 +120,7 @@ var PaperScope = Base.extend(/** @lends PaperScope# */{
*
* @type String
*/
version: '/*#=*/__options.version',
version: /*#=*/__options.version,
// DOCS: PaperScope#settings
/**

View file

@ -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.
*

View file

@ -207,7 +207,7 @@ var Raster = Item.extend(/** @lends Raster# */{
/**
* @private
* @bean
* @deprecated use {@link #getResolution()} instead.
* @deprecated use {@link #resolution} instead.
*/
getPpi: '#getResolution',

View file

@ -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) {

View file

@ -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.

File diff suppressed because it is too large Load diff

View file

@ -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;
}
}};
});

View file

@ -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

View file

@ -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;

View file

@ -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);

View file

@ -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;
},
}
});

View file

@ -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

View file

@ -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) {

View file

@ -1112,7 +1112,8 @@ var Color = Base.extend(new function() {
}
}
});
}, new function() {
},
new function() {
var operators = {
add: function(a, b) {
return a + b;

View file

@ -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.
*/
/**

View file

@ -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'

View file

@ -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;
}
};

View file

@ -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,

View file

@ -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,

View file

@ -286,7 +286,7 @@ function asyncTest(testName, expected) {
var project = new Project();
expected(function() {
project.remove();
start();
QUnit.start();
});
});
}

View file

@ -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);
});

View file

@ -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()');
}

View file

@ -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');
});

View file

@ -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);
});

View file

@ -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 } }');
});

View file

@ -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);