diff --git a/src/basic/Point.js b/src/basic/Point.js
index 5cf1a07c..b5f5d7cf 100644
--- a/src/basic/Point.js
+++ b/src/basic/Point.js
@@ -1,3 +1,8 @@
+/**
+ * The Point object represents a point in the two dimensional space of the
+ * Paper.js document. It is also used to represent two dimensional vector
+ * objects.
+ */
var Point = Base.extend({
beans: true,
@@ -28,6 +33,21 @@ var Point = Base.extend({
}
},
+ /**
+ * Returns a copy of the point.
+ * This is useful as the following code only generates a flat copy:
+ *
+ *
+ * var point1 = new Point();
+ * var point2 = point1;
+ * point2.x = 1; // also changes point1.x
+ *
+ * var point2 = point1.clone();
+ * point2.x = 1; // doesn't change point1.x
+ *
+ *
+ * @return the cloned point
+ */
clone: function() {
return Point.create(this.x, this.y);
},
@@ -66,6 +86,26 @@ var Point = Base.extend({
return this.x == point.x && this.y == point.y;
},
+ transform: function(matrix) {
+ return matrix.transform(this);
+ },
+
+ /**
+ * Returns the distance between the point and another point.
+ *
+ * Sample code:
+ *
+ * var firstPoint = new Point(5, 10);
+ * var secondPoint = new Point(5, 20);
+ *
+ * var distance = firstPoint.getDistance(secondPoint);
+ *
+ * print(distance); // 10
+ *
+ *
+ * @param px
+ * @param py
+ */
getDistance: function() {
var point = Point.read(arguments);
var px = point.x - this.x;
@@ -80,6 +120,12 @@ var Point = Base.extend({
return px * px + py * py;
},
+ /**
+ * The length of the vector that is represented by this point's coordinates.
+ * Each point can be interpreted as a vector that points from the origin
+ * ({@code x = 0},{@code y = 0}) to the point's location.
+ * Setting the length changes the location but keeps the vector's angle.
+ */
getLength: function() {
var point = Point.read(arguments);
return Math.sqrt(this.x * this.x + this.y * this.y);
@@ -131,7 +177,6 @@ var Point = Base.extend({
return Math.atan2(this.y, this.x) * 180 / Math.PI;
},
-
getQuadrant: function() {
if (this.x >= 0) {
if (this.y >= 0) {
@@ -148,15 +193,20 @@ var Point = Base.extend({
}
},
- setAngle: function(angle) {
- angle = this._angle = angle * Math.PI / 180;
- if (!this.isZero()) {
- var length = this.length;
- this.x = Math.cos(angle) * length;
- this.y = Math.sin(angle) * length;
- }
- },
-
+ /**
+ * {@grouptitle Angle & Rotation}
+ *
+ * The vector's angle, measured from the x-axis to the vector.
+ *
+ * When supplied with a point, returns the smaller angle between two
+ * vectors. The angle is unsigned, no information about rotational
+ * direction is given.
+ *
+ * Read more about angle units and orientation in the description of the
+ * {@link #getAngle()} property.
+ *
+ * @param point
+ */
getAngle: function() {
var angle;
if (arguments.length) {
@@ -175,6 +225,24 @@ var Point = Base.extend({
return angle * 180 / Math.PI;
},
+ setAngle: function(angle) {
+ angle = this._angle = angle * Math.PI / 180;
+ if (!this.isZero()) {
+ var length = this.length;
+ this.x = Math.cos(angle) * length;
+ this.y = Math.sin(angle) * length;
+ }
+ },
+
+ /**
+ * Returns the angle between two vectors. The angle is directional and
+ * signed, giving information about the rotational direction.
+ *
+ * Read more about angle units and orientation in the description of the
+ * {@link #getAngle()} property.
+ *
+ * @param point
+ */
getDirectedAngle: function() {
var point = Point.read(arguments);
var angle = this.angle - point.angle;
@@ -188,7 +256,17 @@ var Point = Base.extend({
}
},
- // TODO: Add center parameter support back to Scriptographer
+ /**
+ * Rotates the point by the given angle around an optional center point.
+ * The object itself is not modified.
+ *
+ * Read more about angle units and orientation in the description of the
+ * {@link #getAngle()} property.
+ *
+ * @param angle the rotation angle
+ * @param center the center point of the rotation
+ * @return the rotated point
+ */
rotate: function(angle, center) {
var point = center ? this.subtract(center) : this;
angle = angle * Math.PI / 180;
@@ -201,6 +279,16 @@ var Point = Base.extend({
return center ? point.add(center) : point;
},
+ /**
+ * Returns the interpolation point between the point and another point.
+ * The object itself is not modified!
+ *
+ * @param point
+ * @param t the position between the two points as a value between 0 and 1
+ * @return the interpolation point
+ *
+ * @jshide
+ */
interpolate: function(point, t) {
return Point.create(
this.x * (1 - t) + point.x * t,
@@ -208,52 +296,152 @@ var Point = Base.extend({
);
},
+ /**
+ * {@grouptitle Tests}
+ *
+ * Checks whether the point is inside the boundaries of the rectangle.
+ *
+ * @param rect the rectangle to check against
+ * @return {@true if the point is inside the rectangle}
+ */
isInside: function(rect) {
return rect.contains(this);
},
+ /**
+ * Checks if the point is within a given distance of another point.
+ *
+ * @param point the point to check against
+ * @param tolerance the maximum distance allowed
+ * @return {@true if it is within the given distance}
+ */
isClose: function(point, tolerance) {
return this.getDistance(point) < tolerance;
},
+ /**
+ * Checks if the vector represented by this point is parallel (collinear) to
+ * another vector.
+ *
+ * @param point the vector to check against
+ * @return {@true if it is parallel}
+ */
isParallel: function(point) {
return Math.abs(this.x / point.x - this.y / point.y) < 0.00001;
},
+ /**
+ * Checks if this point has both the x and y coordinate set to 0.
+ *
+ * @return {@true if both x and y are 0}
+ */
isZero: function() {
return this.x == 0 && this.y == 0;
},
+ /**
+ * Checks if this point has an undefined value for at least one of its
+ * coordinates.
+ *
+ * @return {@true if either x or y are not a number}
+ */
isNaN: function() {
return isNaN(this.x) || isNaN(this.y);
},
+ /**
+ * {@grouptitle Math Functions}
+ *
+ * Returns a new point with rounded {@link #x} and {@link #y} values. The
+ * object itself is not modified!
+ *
+ * Sample code:
+ *
+ * var point = new Point(10.2, 10.9);
+ * var roundPoint = point.round();
+ * print(roundPoint); // { x: 10.0, y: 11.0 }
+ *
+ */
round: function() {
return Point.create(Math.round(this.x), Math.round(this.y));
},
+ /**
+ * Returns a new point with the nearest greater non-fractional values to the
+ * specified {@link #x} and {@link #y} values. The object itself is not
+ * modified!
+ *
+ * Sample code:
+ *
+ * var point = new Point(10.2, 10.9);
+ * var ceilPoint = point.ceil();
+ * print(ceilPoint); // { x: 11.0, y: 11.0 }
+ *
+ */
ceil: function() {
return Point.create(Math.ceil(this.x), Math.ceil(this.y));
},
+ /**
+ * Returns a new point with the nearest smaller non-fractional values to the
+ * specified {@link #x} and {@link #y} values. The object itself is not
+ * modified!
+ *
+ * Sample code:
+ *
+ * var point = new Point(10.2, 10.9);
+ * var floorPoint = point.floor();
+ * print(floorPoint); // { x: 10.0, y: 10.0 }
+ *
+ */
floor: function() {
return Point.create(Math.floor(this.x), Math.floor(this.y));
},
+ /**
+ * Returns a new point with the absolute values of the specified {@link #x}
+ * and {@link #y} values. The object itself is not modified!
+ *
+ * Sample code:
+ *
+ * var point = new Point(-5, 10);
+ * var absPoint = point.abs();
+ * print(absPoint); // { x: 5.0, y: 10.0 }
+ *
+ */
abs: function() {
return Point.create(Math.abs(this.x), Math.abs(this.y));
},
+ /**
+ * {@grouptitle Vectorial Math Functions}
+ *
+ * Returns the dot product of the point and another point.
+ * @param point
+ * @return the dot product of the two points
+ */
dot: function() {
var point = Point.read(arguments);
return this.x * point.x + this.y * point.y;
},
+ /**
+ * Returns the cross product of the point and another point.
+ * @param point
+ * @return the cross product of the two points
+ */
cross: function() {
var point = Point.read(arguments);
return this.x * point.y - this.y - point.x;
},
+ /**
+ * Returns the projection of the point on another point.
+ * Both points are interpreted as vectors.
+ *
+ * @param point
+ * @return the project of the point on another point
+ */
project: function() {
var point = Point.read(arguments);
if (point.isZero()) {
@@ -297,18 +485,63 @@ var Point = Base.extend({
return null;
},
+ /**
+ * Returns a new point object with the smallest {@link #x} and
+ * {@link #y} of the supplied points.
+ *
+ * Sample code:
+ *
+ * var point1 = new Point(10, 100);
+ * var point2 = new Point(200, 5);
+ * var minPoint = Point.min(point1, point2);
+ * print(minPoint); // { x: 10.0, y: 5.0 }
+ *
+ *
+ * @param point1
+ * @param point2
+ * @return The newly created point object
+ */
min: function(point1, point2) {
return Point.create(
Math.min(point1.x, point2.x),
Math.min(point1.y, point2.y));
},
+ /**
+ * Returns a new point object with the largest {@link #x} and
+ * {@link #y} of the supplied points.
+ *
+ * Sample code:
+ *
+ * var point1 = new Point(10, 100);
+ * var point2 = new Point(200, 5);
+ * var maxPoint = Point.max(point1, point2);
+ * print(maxPoint); // { x: 200.0, y: 100.0 }
+ *
+ *
+ * @param point1
+ * @param point2
+ * @return The newly created point object
+ */
max: function(point1, point2) {
return Point.create(
Math.max(point1.x, point2.x),
Math.max(point1.y, point2.y));
},
+ /**
+ * Returns a point object with random {@link #x} and {@link #y} values
+ * between {@code 0} and {@code 1}.
+ *
+ * Sample code:
+ *
+ * var maxPoint = new Point(100, 100);
+ * var randomPoint = Point.random();
+ *
+ * // A point between {x:0, y:0} and {x:100, y:100}:
+ * var point = maxPoint * randomPoint;
+ *
+ */
random: function() {
return Point.create(Math.random(), Math.random());
}
diff --git a/src/color/RGBColor.js b/src/color/RGBColor.js
index ff6ace2b..f995f95b 100644
--- a/src/color/RGBColor.js
+++ b/src/color/RGBColor.js
@@ -157,7 +157,7 @@ RGBColor = Color.extend(new function() {
setGray: function(gray) {
this._cssString = null;
- this._red = this._green = this._blue = gray;
+ this._red = this._green = this._blue = 1 - gray;
},
/**
diff --git a/src/document/Doc.js b/src/document/Doc.js
index 8b5136da..c62667a0 100644
--- a/src/document/Doc.js
+++ b/src/document/Doc.js
@@ -29,11 +29,12 @@ Doc = Base.extend({
redraw: function() {
if (this.canvas) {
- // TODO: clearing the canvas by setting
- // this.canvas.width = this.canvas.width might be faster..
- this.ctx.clearRect(0, 0, this.size.width + 1, this.size.height);
+ // Initial tests conclude that clearing the canvas is always
+ // faster than using clearRect:
+ // http://jsperf.com/clearrect-vs-setting-width/7
+ this.ctx.clearRect(0, 0, this.size.width + 1, this.size.height + 1);
for (var i = 0, l = this.layers.length; i < l; i++) {
- this.layers[i].draw(this.ctx);
+ this.layers[i].draw(this.ctx, {});
}
}
}
diff --git a/src/item/BlendMode.js b/src/item/BlendMode.js
new file mode 100644
index 00000000..37f9fa3e
--- /dev/null
+++ b/src/item/BlendMode.js
@@ -0,0 +1,215 @@
+/*
+ * BlendMode code ported from Context Blender JavaScript Library
+ *
+ * Copyright © 2010 Gavin Kistner
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+BlendMode = {
+ // TODO: Should we remove the blend modes that are not in Scriptographer?
+ // TODO: Add missing blendmodes like hue / saturation / color / luminosity
+ // TODO: Clean up codespacing of original code, or keep it as is, so
+ // we can easily encorporate changes?
+ process: function(documentContext, item, param) {
+ // TODO: use strokeBounds
+ var itemBounds = item.bounds;
+ var top = Math.floor(itemBounds.top);
+ var left = Math.floor(itemBounds.left);
+ var size = itemBounds.size.ceil();
+ var width = size.width;
+ var height = size.height;
+
+ var itemCanvas = CanvasProvider.getCanvas(size);
+ var itemContext = itemCanvas.getContext('2d');
+ if(item.matrix) {
+ var matrix = item.matrix.clone();
+ var transMatrix = Matrix.getTranslateInstance(-left, -top);
+ matrix.preConcatenate(transMatrix);
+ // TODO: Profiling shows this as a hotspot
+ matrix.applyToContext(itemContext);
+ } else {
+ itemContext.translate(-itemBounds.left, -itemBounds.top);
+ }
+ param.ignoreBlendMode = true;
+ item.draw(itemContext, param);
+
+ var dstD = documentContext.getImageData(
+ left, top,
+ width, height
+ );
+
+ var srcD = itemContext.getImageData(
+ 0, 0,
+ width, height
+ );
+
+ var src = srcD.data;
+ var dst = dstD.data;
+ var sA, dA, len=dst.length;
+ var sRA, sGA, sBA, dRA, dGA, dBA, dA2;
+ var demultiply;
+
+ for (var px=0;pxdRA ? dRA : sRA) * demultiply;
+ dst[px+1] = (sGA>dGA ? dGA : sGA) * demultiply;
+ dst[px+2] = (sBA>dBA ? dBA : sBA) * demultiply;
+ break;
+
+ case 'lighten':
+ case 'lighter':
+ dst[px ] = (sRA
+ * var circle = new Path.Circle(new Point(50, 50), 10);
+ * print(circle.blendMode); // normal
+ *
+ * // Change the blend mode of the path item:
+ * circle.blendMode = 'multiply';
+ *
+ */
+ blendMode: 'normal',
+
/**
* Specifies whether the item is hidden.
*
@@ -137,6 +154,18 @@ Item = Base.extend({
}
},
+ // TODO: getIsolated / setIsolated (print specific feature)
+ // TODO: get/setKnockout (print specific feature)
+ // TODO get/setAlphaIsShape
+ // TODO: get/setData
+
+ /**
+ * Reverses the order of this item's children
+ */
+ reverseChildren: function() {
+ this.children.reverse();
+ },
+
/**
* The first item contained within this item.
*/
@@ -228,6 +257,21 @@ Item = Base.extend({
return true;
},
+ /**
+ * Checks whether the item is valid, i.e. it hasn't been removed.
+ *
+ * Sample code:
+ *
+ * var path = new Path();
+ * print(path.isValid()); // true
+ * path.remove();
+ * print(path.isValid()); // false
+ *
+ *
+ * @return {@true if the item is valid}
+ */
+ // TODO: isValid / checkValid
+
/**
* {@grouptitle Hierarchy Operations}
*
@@ -331,6 +375,40 @@ Item = Base.extend({
return true;
},
+ /**
+ * {@grouptitle Hierarchy Tests}
+ *
+ * Checks if this item is above the specified item in the stacking order of
+ * the document.
+ *
+ * Sample code:
+ *
+ * var firstPath = new Path();
+ * var secondPath = new Path();
+ * print(secondPath.isAbove(firstPath)); // true
+ *
+ *
+ * @param item The item to check against
+ * @return {@true if it is above the specified item}
+ */
+ // TODO: isAbove
+
+ /**
+ * Checks if the item is below the specified item in the stacking order of
+ * the document.
+ *
+ * Sample code:
+ *
+ * var firstPath = new Path();
+ * var secondPath = new Path();
+ * print(firstPath.isBelow(secondPath)); // true
+ *
+ *
+ * @param item The item to check against
+ * @return {@true if it is below the specified item}
+ */
+ // TODO: isBelow
+
// TODO: this is confusing the beans
// isParent: function(item) {
// return this.parent == item;
@@ -355,7 +433,7 @@ Item = Base.extend({
* @return {@true if it is inside the specified item}
*/
isDescendant: function(item) {
- var parent = this;
+ var parent = this.parent;
while(parent) {
if (parent == item)
return true;
@@ -380,7 +458,7 @@ Item = Base.extend({
* @return {@true if the item is an ancestor of the specified item}
*/
isAncestor: function(item) {
- var parent = item;
+ var parent = item.parent;
while(parent) {
if (parent == this)
return true;
@@ -388,7 +466,28 @@ Item = Base.extend({
}
return false;
},
-
+
+ /**
+ * Checks whether the item is grouped with the specified item.
+ *
+ * @param item
+ * @return {@true if the items are grouped together}
+ */
+ isGroupedWith: function(item) {
+ var parent = this.parent;
+ while(parent) {
+ // Find group parents. Check for parent.parent, since don't want
+ // top level layers, because they also inherit from Group
+ if(parent.parent
+ && (parent instanceof Group || parent instanceof CompoundPath)
+ && item.isDescendant(parent))
+ return true;
+ // Keep walking up otherwise
+ parent = parent.parent
+ }
+ return false;
+ },
+
getBounds: function() {
// TODO: Implement for items other than paths
return new Rectangle();
@@ -416,7 +515,45 @@ Item = Base.extend({
// Now execute the transformation:
this.transform(matrix);
},
+
+ /**
+ * The bounding rectangle of the item including stroke width.
+ */
+ // TODO: getStrokeBounds
+ /**
+ * The bounding rectangle of the item including stroke width and controls.
+ */
+ // TODO: getControlBounds
+
+ /**
+ * Rasterizes the item into a newly created Raster object. The item itself
+ * is not removed after rasterization.
+ *
+ * @param resolution the resolution of the raster in dpi {@default 72}
+ * @return the newly created Raster item
+ */
+ rasterize: function(resolution) {
+ // TODO: why would we want to pass a size to rasterize? Seems to produce
+ // weird results on Scriptographer. Also we can't use antialiasing, since
+ // Canvas doesn't support it yet. Document colorMode is also out of the
+ // question for now.
+ if(!resolution)
+ resolution = 72;
+ // TODO: use strokebounds for this:
+ var bounds = this.bounds;
+ var scale = resolution / 72;
+ var canvas = CanvasProvider.getCanvas(bounds.size.multiply(scale));
+ var context = canvas.getContext('2d');
+ var matrix = new Matrix().scale(scale).translate(-bounds.x, -bounds.y);
+ matrix.applyToContext(context);
+ this.draw(context);
+ var raster = new Raster(canvas);
+ raster.position = this.bounds.center;
+ raster.scale(1 / scale);
+ return raster;
+ },
+
/**
* The item's position within the art board. This is the
* {@link Rectangle#getCenter()} of the {@link Item#getBounds()} rectangle.
@@ -569,4 +706,6 @@ Item = Base.extend({
setStyle: function(style) {
this._style = new PathStyle(this, style);
}
+
+ // TODO: toString
});
\ No newline at end of file
diff --git a/src/item/PlacedSymbol.js b/src/item/PlacedSymbol.js
index 2c38bcb7..788d0e91 100644
--- a/src/item/PlacedSymbol.js
+++ b/src/item/PlacedSymbol.js
@@ -19,31 +19,58 @@ PlacedSymbol = Item.extend({
} else {
this.matrix = new Matrix();
}
+ // TODO: this should use strokeBounds:
this._bounds = this.symbol.definition.bounds.clone();
+ // TODO: should size be cached here, or on Symbol?
+ this._size = this._bounds.size;
},
transformContent: function(matrix, flags) {
- var bounds = this.bounds;
- var coords = [bounds.x, bounds.y,
- bounds.x + bounds.width, bounds.y + bounds.height];
- matrix.transform(coords, 0, coords, 0, 2);
+ var width = this._size.width;
+ var height = this._size.height;
+ var x = width * -0.5;
+ var y = height * -0.5;
+ var coords = [
+ x, y,
+ x + width, y,
+ x + width, y + height,
+ x, y + height];
this.matrix.preConcatenate(matrix);
- bounds.x = coords[0];
- bounds.y = coords[1];
- bounds.width = coords[2] - coords[0];
- bounds.height = coords[3] - coords[1];
+ this.matrix.transform(coords, 0, coords, 0, 4);
+
+ var xMin = coords[0], xMax = coords[0];
+ var yMin = coords[1], yMax = coords[1];
+ for(var i = 2; i < 8; i += 2) {
+ var x = coords[i];
+ var y = coords[i + 1];
+ xMin = Math.min(x, xMin);
+ xMax = Math.max(x, xMax);
+ yMin = Math.min(y, yMin);
+ yMax = Math.max(y, yMax);
+ };
+ var bounds = this._bounds;
+ bounds.x = xMin;
+ bounds.y = yMin;
+ bounds.width = xMax - xMin;
+ bounds.height = yMax - yMin;
},
getBounds: function() {
return this._bounds;
},
- draw: function(ctx) {
- // TODO: we need to preserve strokewidth, but still transform the fill
- ctx.save();
- this.matrix.applyToContext(ctx);
- this.symbol.definition.draw(ctx);
- ctx.restore();
+ draw: function(ctx, param) {
+ if(this.blendMode != 'normal' && !param.ignoreBlendMode) {
+ BlendMode.process(ctx, this, param);
+ } else {
+ // TODO: we need to preserve strokewidth, but still transform the fill
+ ctx.save();
+ if(param.ignoreBlendMode !== true)
+ this.matrix.applyToContext(ctx);
+ param.ignoreBlendMode = false;
+ this.symbol.definition.draw(ctx, param);
+ ctx.restore();
+ }
}
// TODO:
// embed()
diff --git a/src/item/Raster.js b/src/item/Raster.js
index 1c484aff..78c84bd1 100644
--- a/src/item/Raster.js
+++ b/src/item/Raster.js
@@ -3,80 +3,122 @@ Raster = Item.extend({
// TODO: implement url / type, width, height
// TODO: have PlacedSymbol & Raster inherit from a shared class?
- initialize: function(image) {
+ initialize: function(object) {
+ var width, height;
this.base();
- if (image) {
- this.image = image;
- var width = image.width;
- var height = image.height;
- this.size = new Size(width, height);
- this._bounds = new Rectangle(-width / 2, -height / 2, width, height);
- this.matrix = new Matrix();
+ if (object.getContext) {
+ this.canvas = object;
+ width = this.canvas.width;
+ height = this.canvas.height;
+ } else {
+ this._image = object;
+ // TODO: cross browser compatible?
+ width = object.naturalWidth;
+ height = object.naturalHeight;
}
+ this._size = new Size(width, height);
+ this._bounds = new Rectangle(-width / 2, -height / 2, width, height);
+ this.matrix = new Matrix();
+ },
+
+ /**
+ * The size of the raster in pixels.
+ */
+ getSize: function() {
+ return this._size;
},
- // TODO: getSize / setSize
+ setSize: function() {
+ var size = Size.read(arguments);
+ var canvas = CanvasProvider.getCanvas(size);
+ var context = canvas.getContext('2d');
+ context.drawImage(this._canvas ? this._canvas : this._image,
+ 0, 0, size.width, size.height);
+ // If we already had a canvas, return it to be reused.
+ if (this._canvas)
+ CanvasProvider.returnCanvas(this._canvas);
+ this._size = size;
+ this._context = null;
+ this._canvas = canvas;
+ },
/**
* The width of the raster in pixels.
*/
getWidth: function() {
- return this.size.width;
+ return this._size.width;
},
/**
* The height of the raster in pixels.
*/
getHeight: function() {
- return this.size.height;
+ return this._size.height;
},
- // TODO: getPpi
- // TODO: getSubImage
- // TODO: getImage
- // TODO: drawImage
-
- // TODO: support getAverageColor paramaters: point, rect, path
- // TODO: Idea for getAverageColor(path): set globalCompositeOperation = 'xor',
- // then fillRect with black, then draw the path, then draw the image, then
- // resize and count values.
- getAverageColor: function() {
- var size = 32;
- var tempCanvas = CanvasProvider.getCanvas(size, size);
- var ctx = tempCanvas.getContext('2d');
- ctx.drawImage(this.image, 0, 0, size, size);
- var pixels = ctx.getImageData(0.5, 0.5, size, size).data;
- var channels = [0, 0, 0];
-
- for (var i = 0; i < size; i++) {
- var offset = i * size;
- var alpha = pixels[offset + 3] / 255;
- channels[0] += pixels[offset] * alpha;
- channels[1] += pixels[offset + 1] * alpha;
- channels[2] += pixels[offset + 2] * alpha;
- }
-
- for (var i = 0; i < 3; i++)
- channels[i] /= size * 255;
-
- CanvasProvider.returnCanvas(tempCanvas);
- return Color.read(channels);
+ /**
+ * Pixels per inch of the raster at it's current size.
+ */
+ getPpi: function() {
+ var matrix = this.matrix;
+ var orig = new Point(0, 0).transform(matrix);
+ var u = new Point(1, 0).transform(matrix).subtract(orig);
+ var v = new Point(0, 1).transform(matrix).subtract(orig);
+ return new Size(
+ 72 / u.length,
+ 72 / v.length
+ );
},
- // TODO: getPixel(point)
- // TODO: test this
- getPixel: function(x, y) {
- var pixels = this.context.getImageData(x + 0.5, y + 0.5, 1, 1).data;
+ getSubImage: function(/* rectangle */) {
+ var rectangle = Rectangle.read(arguments);
+ var canvas = CanvasProvider.getCanvas(rectangle.size);
+ var context = canvas.getContext('2d');
+ context.drawImage(this.canvas, rectangle.x, rectangle.y,
+ canvas.width, canvas.height, 0, 0, canvas.width, canvas.height);
+ return canvas;
+ },
+
+ getImage: function() {
+ return this._image || this.canvas;
+ },
+
+ // TODO: setImage
+
+ // TODO: drawImage(image, point)
+ drawImage: function(image, x, y) {
+ var point = center = Point.read(arguments, 1);
+ this.context.drawImage(image, x, y);
+ },
+
+ /**
+ * {@grouptitle Pixels}
+ *
+ * Gets the color of a pixel in the raster.
+ * @param x
+ * @param y
+ */
+ getPixel: function() {
+ var point = Point.read(arguments);
+ var ctx = this.context;
+ var pixels = ctx.getImageData(point.x + 0.5, point.y + 0.5, 1, 1).data;
var channels = [];
- for(var i = 0; i < 4; i++)
+ for (var i = 0; i < 4; i++)
channels.push(pixels[i] / 255);
return Color.read(channels);
},
// TODO: setPixel(point, color)
- // setPixel: function(x, y, color) {
- //
- // }
+ setPixel: function(x, y, color) {
+ color = Color.read(arguments, 2);
+ var ctx = this.context;
+ var imageData = ctx.getImageData(x, y, 1, 1);
+ imageData.data[0] = color.red * 255;
+ imageData.data[1] = color.green * 255;
+ imageData.data[2] = color.blue * 255;
+ imageData.data[3] = color.alpha != -1 ? color.alpha * 255 : 255;
+ ctx.putImageData(imageData, x, y);
+ },
getContext: function() {
if (!this._context)
@@ -90,40 +132,144 @@ Raster = Item.extend({
getCanvas: function() {
if (!this._canvas) {
- this._canvas = CanvasProvider.getCanvas(this.size.width, this.size.height);
+ this._canvas = CanvasProvider.getCanvas(this.size);
this.ctx = this._canvas.getContext('2d');
- this.ctx.drawImage(this.image, 0, 0);
+ this.ctx.drawImage(this._image, 0, 0);
}
return this._canvas;
},
setCanvas: function(canvas) {
- CanvasProvider.returnCanvas(this._canvas);
+ if (this._canvas)
+ CanvasProvider.returnCanvas(this._canvas);
+ // TODO: should the width / height of the bounds be reset too?
+ this._size = new Size(canvas.width, canvas.height);
+ this._image = null;
this._ctx = null;
this._canvas = canvas;
},
transformContent: function(matrix, flags) {
- var bounds = this.bounds;
- var coords = [bounds.x, bounds.y,
- bounds.x + bounds.width, bounds.y + bounds.height];
- matrix.transform(coords, 0, coords, 0, 2);
+ var width = this._size.width;
+ var height = this._size.height;
+ var x = width * -0.5;
+ var y = height * -0.5;
+ var coords = [
+ x, y,
+ x + width, y,
+ x + width, y + height,
+ x, y + height];
this.matrix.preConcatenate(matrix);
- bounds.x = coords[0];
- bounds.y = coords[1];
- bounds.width = coords[2] - coords[0];
- bounds.height = coords[3] - coords[1];
+ this.matrix.transform(coords, 0, coords, 0, 4);
+
+ var xMin = coords[0], xMax = coords[0];
+ var yMin = coords[1], yMax = coords[1];
+ for(var i = 2; i < 8; i += 2) {
+ var x = coords[i];
+ var y = coords[i + 1];
+ xMin = Math.min(x, xMin);
+ xMax = Math.max(x, xMax);
+ yMin = Math.min(y, yMin);
+ yMax = Math.max(y, yMax);
+ };
+ var bounds = this._bounds;
+ bounds.x = xMin;
+ bounds.y = yMin;
+ bounds.width = xMax - xMin;
+ bounds.height = yMax - yMin;
},
getBounds: function() {
return this._bounds;
},
- draw: function(ctx) {
- ctx.save();
- this.matrix.applyToContext(ctx);
- ctx.drawImage(this._canvas || this.image,
- -this.size.width / 2, -this.size.height / 2);
- ctx.restore();
+ draw: function(ctx, param) {
+ if(this.blendMode != 'normal' && !param.ignoreBlendMode) {
+ BlendMode.process(ctx, this, param);
+ } else {
+ ctx.save();
+ if(param.ignoreBlendMode !== true)
+ this.matrix.applyToContext(ctx);
+ ctx.drawImage(this._canvas || this._image,
+ -this.size.width / 2, -this.size.height / 2);
+ ctx.restore();
+ param.ignoreBlendMode = false;
+ }
+ }
+}, new function() {
+ function getAverageColor(pixels) {
+ var channels = [0, 0, 0];
+ var total = 0;
+ for (var i = 0, l = pixels.length / 4; i < l; i++) {
+ var offset = i * 4;
+ var alpha = pixels[offset + 3] / 255;
+ total += alpha;
+ channels[0] += pixels[offset] * alpha;
+ channels[1] += pixels[offset + 1] * alpha;
+ channels[2] += pixels[offset + 2] * alpha;
+ }
+ for (var i = 0; i < 3; i++)
+ channels[i] /= total * 255;
+ return total ? Color.read(channels) : null;
+ }
+
+ return {
+ /**
+ * {@grouptitle Average Color}
+ * Calculates the average color of the image within the given path,
+ * rectangle or point. This can be used for creating raster image
+ * effects.
+ *
+ * @param object
+ * @return the average color contained in the area covered by the
+ * specified path, rectangle or point.
+ */
+ getAverageColor: function(object) {
+ var image;
+ if (object) {
+ var bounds, path;
+ if (object instanceof Path) {
+ // TODO: what if the path is smaller than 1 px?
+ // TODO: how about rounding of bounds.size?
+ // TODO: test with compound paths.
+ path = object;
+ bounds = object.bounds;
+ } else if (object.width) {
+ bounds = new Rectangle(object);
+ } else if (object.x) {
+ bounds = new Rectangle(object.x - 0.5, object.y - 0.5, 1, 1);
+ }
+
+ var canvas = CanvasProvider.getCanvas(bounds.size);
+ var ctx = canvas.getContext('2d');
+ var delta = bounds.topLeft.multiply(-1);
+ ctx.translate(delta.x, delta.y);
+ if (path) {
+ var style = object.style;
+ path.draw(ctx);
+ ctx.clip();
+ path.style = style;
+ }
+ var matrix = this.matrix.clone();
+ var transMatrix = Matrix.getTranslateInstance(delta);
+ matrix.preConcatenate(transMatrix);
+ matrix.applyToContext(ctx);
+ ctx.drawImage(this._canvas || this._image,
+ -this.size.width / 2, -this.size.height / 2);
+ image = canvas;
+ } else {
+ image = this.image;
+ }
+ var size = new Size(32);
+ var sampleCanvas = CanvasProvider.getCanvas(size);
+ var ctx = sampleCanvas.getContext('2d');
+ ctx.drawImage(image, 0, 0, size.width, size.height);
+ var pixels = ctx.getImageData(0.5, 0.5, size.width, size.height).data;
+ var color = getAverageColor(pixels);
+ CanvasProvider.returnCanvas(sampleCanvas);
+ if (image instanceof HTMLCanvasElement)
+ CanvasProvider.returnCanvas(image);
+ return color;
+ }
}
});
\ No newline at end of file
diff --git a/src/path/CompoundPath.js b/src/path/CompoundPath.js
index 6df06394..1833e942 100644
--- a/src/path/CompoundPath.js
+++ b/src/path/CompoundPath.js
@@ -19,29 +19,53 @@ CompoundPath = PathItem.extend(new function() {
}
},
- draw: function(ctx) {
+ draw: function(ctx, param) {
if(!this.visible)
return;
if (this.children.length) {
- var firstChild = this.children[0];
- ctx.beginPath();
- for (var i = 0, l = this.children.length; i < l; i++) {
- var child = this.children[i];
- child.draw(ctx, true);
- }
- firstChild.setCtxStyles(ctx);
- if (firstChild.fillColor) {
- ctx.fillStyle = firstChild.fillColor.getCssString();
- ctx.fill();
- }
- if (firstChild.strokeColor) {
- ctx.strokeStyle = firstChild.strokeColor.getCssString();
- ctx.stroke();
+ if(this.blendMode != 'normal' && !param.ignoreBlendMode) {
+ BlendMode.process(ctx, this, param);
+ } else {
+ var firstChild = this.children[0];
+ ctx.beginPath();
+ param.compound = true;
+ for (var i = 0, l = this.children.length; i < l; i++) {
+ var child = this.children[i];
+ child.draw(ctx, param);
+ }
+ param.compound = false;
+ firstChild.setCtxStyles(ctx);
+ if (firstChild.fillColor) {
+ ctx.fillStyle = firstChild.fillColor.getCssString();
+ ctx.fill();
+ }
+ if (firstChild.strokeColor) {
+ ctx.strokeStyle = firstChild.strokeColor.getCssString();
+ ctx.stroke();
+ }
}
}
},
- // TODO: add getBounds
+ // TODO: have getBounds of Group / Layer / CompoundPath use the same
+ // code (from a utility script?)
+ getBounds: function() {
+ if (this.children.length) {
+ var rect = this.children[0].bounds;
+ var x1 = rect.x;
+ var y1 = rect.y;
+ var x2 = rect.x + rect.width;
+ var y2 = rect.y + rect.height;
+ for (var i = 1, l = this.children.length; i < l; i++) {
+ var rect2 = this.children[i].bounds;
+ x1 = Math.min(rect2.x, x1);
+ y1 = Math.min(rect2.y, y1);
+ x2 = Math.max(rect2.x + rect2.width, x1 + x2 - x1);
+ y2 = Math.max(rect2.y + rect2.height, y1 + y2 - y1);
+ }
+ }
+ return new Rectangle(x1, y1, x2 - x1, y2 - y1);
+ },
/**
* If this is a compound path with only one path inside,
@@ -73,17 +97,10 @@ CompoundPath = PathItem.extend(new function() {
},
moveBy: function() {
- if (!arguments.length) {
- // TODO: Shouldn't this be relative to the previous position
- // in lack of an argument? This should then be corrected in
- // Scriptographer too.
- this.moveTo(0, 0);
- } else {
- var point = Point.read(arguments);
- var path = getCurrentPath(this);
- var current = path.segments[path.segments.length - 1].point;
- this.moveTo(current.add(point));
- }
+ var point = arguments.length ? Point.read(arguments) : new Point();
+ var path = getCurrentPath(this);
+ var current = path.segments[path.segments.length - 1].point;
+ this.moveTo(current.add(point));
},
closePath: function() {
diff --git a/src/path/Path.js b/src/path/Path.js
index f14ad3ac..fcfb06a8 100644
--- a/src/path/Path.js
+++ b/src/path/Path.js
@@ -228,7 +228,7 @@ Path = PathItem.extend({
lineTo: function() {
var segment = Segment.read(arguments);
- if (segment && this._segments.length)
+ if (segment)
this.addSegment(segment);
},
@@ -403,57 +403,63 @@ Path = PathItem.extend({
this.closed = ture;
},
- draw: function(ctx, compound) {
+ draw: function(ctx, param) {
if (!this.visible) return;
- if (!compound)
- ctx.beginPath();
-
- var segments = this._segments;
- var length = segments.length;
- for (var i = 0; i < length; i++) {
- var segment = segments[i];
- var x = segment.point.x;
- var y = segment.point.y;
- var handleIn = segment.handleIn;
- if (i == 0) {
- ctx.moveTo(x, y);
- } else {
- if (handleOut.isZero() && handleIn.isZero()) {
- ctx.lineTo(x, y);
+ if(this.blendMode != 'normal' && !param.ignoreBlendMode) {
+ BlendMode.process(ctx, this, param);
+ } else {
+ param.ignoreBlendMode = false;
+ if (!param.compound)
+ ctx.beginPath();
+
+ var segments = this._segments;
+ var length = segments.length;
+ for (var i = 0; i < length; i++) {
+ var segment = segments[i];
+ var x = segment.point.x;
+ var y = segment.point.y;
+ var handleIn = segment.handleIn;
+ if (i == 0) {
+ ctx.moveTo(x, y);
} else {
- ctx.bezierCurveTo(
- outX, outY,
- handleIn.x + x, handleIn.y + y,
- x, y
- );
+ if (handleOut.isZero() && handleIn.isZero()) {
+ ctx.lineTo(x, y);
+ } else {
+ ctx.bezierCurveTo(
+ outX, outY,
+ handleIn.x + x, handleIn.y + y,
+ x, y
+ );
+ }
}
+ var handleOut = segment.handleOut;
+ var outX = handleOut.x + x;
+ var outY = handleOut.y + y;
}
- var handleOut = segment.handleOut;
- var outX = handleOut.x + x;
- var outY = handleOut.y + y;
- }
- if (this.closed && length > 1) {
- var segment = segments[0];
- var x = segment.point.x;
- var y = segment.point.y;
- var handleIn = segment.handleIn;
- ctx.bezierCurveTo(outX, outY, handleIn.x + x, handleIn.y + y, x, y);
- ctx.closePath();
- }
- if (!compound) {
- this.setCtxStyles(ctx);
- ctx.save();
- ctx.globalAlpha = this.opacity;
- if (this.fillColor) {
- ctx.fillStyle = this.fillColor.getCanvasStyle(ctx);
- ctx.fill();
+ if (this.closed && length > 1) {
+ var segment = segments[0];
+ var x = segment.point.x;
+ var y = segment.point.y;
+ var handleIn = segment.handleIn;
+ ctx.bezierCurveTo(outX, outY, handleIn.x + x, handleIn.y + y, x, y);
+ ctx.closePath();
}
- if (this.strokeColor) {
- ctx.strokeStyle = this.strokeColor.getCanvasStyle(ctx);
- ctx.stroke();
+ if (!param.compound) {
+ this.setCtxStyles(ctx);
+ ctx.save();
+ ctx.globalAlpha = this.opacity;
+ if (this.fillColor) {
+ ctx.fillStyle = this.fillColor.getCanvasStyle(ctx);
+ ctx.fill();
+ }
+ if (this.strokeColor) {
+ ctx.strokeStyle = this.strokeColor.getCanvasStyle(ctx);
+ ctx.stroke();
+ }
+ ctx.restore();
}
- ctx.restore();
}
+
}
}, new function() { // inject methods that require scoped privates
/**
diff --git a/src/path/Segment.js b/src/path/Segment.js
index 79c2d4c7..8039c75e 100644
--- a/src/path/Segment.js
+++ b/src/path/Segment.js
@@ -34,15 +34,6 @@ Segment = Base.extend({
this.handleOut = new Point();
},
- // TODO:
- // insert: function() {
- // if (this._segments && this._segments.path) {
- // var path = this._segments.path;
- // path.checkValid();
- //
- // }
- // },
-
getPoint: function() {
return this.point;
},
diff --git a/src/tool/Tool.js b/src/tool/Tool.js
index 3808c21a..44dfeb2a 100644
--- a/src/tool/Tool.js
+++ b/src/tool/Tool.js
@@ -12,6 +12,7 @@ Tool = ToolHandler.extend({
$(this._document.canvas).removeEvents();
this._document = doc || Paper.document;
var that = this, curPoint;
+ var dragging = false;
var events = {
dragstart: function(e) {
curPoint = new Point(e.offset);
@@ -20,6 +21,7 @@ Tool = ToolHandler.extend({
that._document.redraw();
if (that.eventInterval != -1)
this.intervalId = setInterval(events.drag, that.eventInterval);
+ dragging = true;
},
drag: function(e) {
if (e) curPoint = new Point(e.offset);
@@ -36,12 +38,15 @@ Tool = ToolHandler.extend({
that.onHandleEvent('MOUSE_UP', new Point(e.offset), null, null);
if (that.onMouseUp)
that._document.redraw();
+ dragging = false;
+ },
+ mousemove: function(e) {
+ if(!dragging) {
+ that.onHandleEvent('MOUSE_MOVE', new Point(e.offset), null, null);
+ if (that.onMouseMove)
+ that._document.redraw();
+ }
}
- // TODO: This is currently interfering with the drag code, needs fixing:
- // mousemove: function(e) {
- // that.onHandleEvent('MOUSE_MOVE', new Point(e.offset), null, null);
- // that._document.redraw();
- // }
};
$(doc.canvas).addEvents(events);
},
diff --git a/src/util/CanvasProvider.js b/src/util/CanvasProvider.js
index e2f139d1..0622bfbd 100644
--- a/src/util/CanvasProvider.js
+++ b/src/util/CanvasProvider.js
@@ -1,15 +1,32 @@
+// TODO: it might be better to make a ContextProvider class, since you
+// can always find the canvas through context.canvas. This saves code and
+// speed by not having to do canvas.getContext('2d')
+// TODO: Run through the canvas array to find a canvas with the requested
+// width / height, so we don't need to resize it?
CanvasProvider = {
canvases: [],
- getCanvas: function(width, height) {
- var canvas = this.canvases.length
- ? this.canvases.pop()
- : document.createElement('canvas');
- canvas.width = width;
- canvas.height = height;
- return canvas;
+ getCanvas: function(size) {
+ if(this.canvases.length) {
+ var canvas = this.canvases.pop();
+ // If they are not the same size, we don't need to clear them
+ // using clearRect and visa versa.
+ if((canvas.width != size.width) || (canvas.height != size.height)) {
+ canvas.width = size.width;
+ canvas.height = size.height;
+ } else {
+ var context = canvas.getContext('2d');
+ context.clearRect(0, 0, size.width + 1, size.height + 1);
+ }
+ return canvas;
+ } else {
+ var canvas = document.createElement('canvas');
+ canvas.width = size.width;
+ canvas.height = size.height;
+ return canvas;
+ }
},
returnCanvas: function(canvas) {
this.canvases.push(canvas);
}
-};
+};
\ No newline at end of file
diff --git a/test/index.html b/test/index.html
index 3b6774ef..f6bbbba3 100644
--- a/test/index.html
+++ b/test/index.html
@@ -14,9 +14,11 @@
+
+
@@ -48,6 +50,7 @@
+
diff --git a/test/tests/Color.js b/test/tests/Color.js
index 92c2f6aa..f4a561cb 100644
--- a/test/tests/Color.js
+++ b/test/tests/Color.js
@@ -68,5 +68,11 @@ test('Converting Colors', function() {
var color = new GrayColor(0.2);
var rgbColor = new RGBColor(color);
- compareRGBColors(rgbColor, [ 0.8, 0.8, 0.8, 1 ]);
+ compareRGBColors(rgbColor, [ 0.8, 0.8, 0.8, 1]);
+});
+
+test('Setting RGBColor#gray', function() {
+ var color = new RGBColor(1, 0.5, 0.2);
+ color.gray = 0.1;
+ compareRGBColors(color, [ 0.9, 0.9, 0.9, 1]);
});
\ No newline at end of file
diff --git a/test/tests/Path.js b/test/tests/Path.js
index cb55ce9b..5295434b 100644
--- a/test/tests/Path.js
+++ b/test/tests/Path.js
@@ -1,6 +1,7 @@
module('Path');
test('path.currentSegment', function() {
+ var doc = new Doc();
var path = new Path();
path.moveTo([50, 50]);
path.lineTo([100, 100]);
diff --git a/test/tests/Path_Bounds.js b/test/tests/Path_Bounds.js
index 43db98c2..44d02a1f 100644
--- a/test/tests/Path_Bounds.js
+++ b/test/tests/Path_Bounds.js
@@ -28,4 +28,8 @@ test('path.bounds', function() {
// Set new bounds and check segment list as result of resizing / positioning
path.bounds = { x: 100, y: 100, width: 200, height: 200 };
compareSegmentLists(path.segments, [{ point: { x: 107.93066, y: 179.56982 }, handleIn: { x: -24.41211, y: 51.30664 }, handleOut: { x: 39.52734, y: -83.08447 } }, { point: { x: 271.10107, y: 160.66553 }, handleIn: { x: -53.96289, y: -99.9126 }, handleOut: { x: 53.96143, y: 99.91406 } }, { point: { x: 215.85303, y: 296.96045 }, handleIn: { x: 85.81299, y: -17.18555 }, handleOut: { x: -101.49854, y: 20.32861 } }])
-});
+
+ path.rotate(40);
+ compareRectangles(path.bounds, { x: 92.38155, y: 106.78981, width: 191.48048, height: 203.66789 });
+ compareSegmentLists(path.segments, [{ point: { x: 142.604, y: 125.16748 }, handleIn: { x: -51.6792, y: 23.61182 }, handleOut: { x: 83.68457, y: -38.23438 } }, { point: { x: 279.75, y: 215.57129 }, handleIn: { x: 22.88525, y: -111.22363 }, handleOut: { x: -22.88623, y: 111.22363 } }, { point: { x: 149.81982, y: 284.46729 }, handleIn: { x: 76.78223, y: 41.99219 }, handleOut: { x: -90.81885, y: -49.67139 } }]);
+});
\ No newline at end of file
diff --git a/test/tests/PlacedSymbol.js b/test/tests/PlacedSymbol.js
new file mode 100644
index 00000000..48be51ae
--- /dev/null
+++ b/test/tests/PlacedSymbol.js
@@ -0,0 +1,24 @@
+module('Placed Symbol');
+
+test('placedSymbol bounds', function() {
+ var doc = new Doc();
+ var path = new Path.Circle([50, 50], 50);
+ var symbol = new Symbol(path);
+ var placedSymbol = new PlacedSymbol(symbol);
+
+ // These tests currently fail because we haven't implemented
+ // Item#strokeBounds yet.
+ compareRectangles(placedSymbol.bounds,
+ new Rectangle(-50.5, -50.5, 101, 101),
+ 'PlacedSymbol initial bounds.');
+
+ placedSymbol.scale(0.5);
+ compareRectangles(placedSymbol.bounds,
+ { x: -25.5, y: -25.5, width: 51, height: 51 },
+ 'Bounds after scale');
+
+ placedSymbol.rotate(40);
+ compareRectangles(placedSymbol.bounds,
+ { x: -25.50049, y: -25.50049, width: 51.00098, height: 51.00098 },
+ 'Bounds after rotation');
+});
\ No newline at end of file
diff --git a/test/tests/item.js b/test/tests/item.js
index 7262a714..5185864b 100644
--- a/test/tests/item.js
+++ b/test/tests/item.js
@@ -89,6 +89,33 @@ test('isDescendant(item) / isAncestor(item)', function() {
equals(path.isAncestor(doc.activeLayer), false);
equals(doc.activeLayer.isAncestor(path), true);
+
+ // an item can't be its own descendant:
+ equals(doc.activeLayer.isDescendant(doc.activeLayer), false);
+
+ // an item can't be its own ancestor:
+ equals(doc.activeLayer.isAncestor(doc.activeLayer), false);
+});
+
+test('isGroupedWith', function() {
+ var doc = new Doc();
+ var path = new Path();
+ var secondPath = new Path();
+ var group = new Group([path]);
+ var secondGroup = new Group([secondPath]);
+
+ equals(path.isGroupedWith(secondPath), false);
+ secondGroup.appendTop(path);
+ equals(path.isGroupedWith(secondPath), true);
+ equals(path.isGroupedWith(group), false);
+ equals(path.isDescendant(secondGroup), true);
+ equals(secondGroup.isDescendant(path), false);
+ equals(secondGroup.isDescendant(secondGroup), false);
+ equals(path.isGroupedWith(secondGroup), false);
+ Paper.document.activeLayer.appendTop(path);
+ equals(path.isGroupedWith(secondPath), false);
+ Paper.document.activeLayer.appendTop(secondPath);
+ equals(path.isGroupedWith(secondPath), false);
});
test('getPreviousSibling() / getNextSibling()', function() {
@@ -105,4 +132,16 @@ test('hidden', function() {
var firstPath = new Path();
firstPath.visible = false;
equals(firstPath.hidden, true);
-});
\ No newline at end of file
+});
+
+test('reverseChildren()', function() {
+ var doc = new Doc();
+ var path = new Path();
+ var secondPath = new Path();
+ var thirdPath = new Path();
+ equals(doc.activeLayer.firstChild == path, true);
+ doc.activeLayer.reverseChildren();
+ equals(doc.activeLayer.firstChild == path, false);
+ equals(doc.activeLayer.firstChild == thirdPath, true);
+ equals(doc.activeLayer.lastChild == path, true);
+})
\ No newline at end of file