diff --git a/examples/Games/Paperoids.html b/examples/Games/Paperoids.html
index 6d81acd0..5d5b382e 100644
--- a/examples/Games/Paperoids.html
+++ b/examples/Games/Paperoids.html
@@ -125,7 +125,6 @@
path.closed = true;
var thrust = new Path([-8, -4], [-14, 0], [-8, 4]);
var group = new Group(path, thrust);
- group.applyMatrix = true;
group.position = view.bounds.center;
return {
item: group,
diff --git a/examples/Paperjs.org/BouncingBalls.html b/examples/Paperjs.org/BouncingBalls.html
index bd338c9b..b1d9d38e 100644
--- a/examples/Paperjs.org/BouncingBalls.html
+++ b/examples/Paperjs.org/BouncingBalls.html
@@ -27,21 +27,21 @@
var radius = this.radius = 50 * Math.random() + 30;
// Wrap CompoundPath in a Group, since CompoundPaths directly
// applies the transformations to the content, just like Path.
- this.item = new Group({
+ var ball = new CompoundPath({
children: [
- new CompoundPath({
- children: [
- new Path.Circle({
- radius: radius
- }),
- new Path.Circle({
- center: radius / 8,
- radius: radius / 3
- })
- ],
- fillColor: new Color(gradient, 0, radius, radius / 8),
+ new Path.Circle({
+ radius: radius
+ }),
+ new Path.Circle({
+ center: radius / 8,
+ radius: radius / 3
})
],
+ fillColor: new Color(gradient, 0, radius, radius / 8),
+ });
+
+ this.item = new Clip({
+ children: [ball],
position: this.point
});
}
diff --git a/examples/Paperjs.org/InteractiveTiger.html b/examples/Paperjs.org/InteractiveTiger.html
index 5da0690f..05572ad8 100644
--- a/examples/Paperjs.org/InteractiveTiger.html
+++ b/examples/Paperjs.org/InteractiveTiger.html
@@ -9,7 +9,6 @@
project.importSVG(document.getElementById('svg'));
// Resize the tiger to fit within the window:
- project.activeLayer.applyMatrix = true;
project.activeLayer.fitBounds(view.bounds);
var items = project.activeLayer.firstChild.children;
diff --git a/examples/Paperjs.org/NyanRainbow.html b/examples/Paperjs.org/NyanRainbow.html
index 6a0f69f6..fc7f5775 100644
--- a/examples/Paperjs.org/NyanRainbow.html
+++ b/examples/Paperjs.org/NyanRainbow.html
@@ -123,7 +123,6 @@
var count = 30;
var group = new Group(paths);
- group.applyMatrix = true;
var headGroup;
var eyePosition = new Point();
var eyeFollow = (Point.random() - 0.5);
diff --git a/examples/Paperjs.org/PathIntersections.html b/examples/Paperjs.org/PathIntersections.html
index 48c76c9f..8a98c6f6 100644
--- a/examples/Paperjs.org/PathIntersections.html
+++ b/examples/Paperjs.org/PathIntersections.html
@@ -12,10 +12,6 @@
var yesGroup = words.children.yes;
var noGroup = words.children.no;
- project.activeLayer.applyMatrix = true;
- noGroup.applyMatrix = true;
- yesGroup.applyMatrix = true;
-
// Resize the words to fit snugly inside the view:
project.activeLayer.fitBounds(view.bounds);
project.activeLayer.scale(0.8);
diff --git a/examples/Paperjs.org/Qbertify.html b/examples/Paperjs.org/Qbertify.html
index 362c202f..fcad642b 100644
--- a/examples/Paperjs.org/Qbertify.html
+++ b/examples/Paperjs.org/Qbertify.html
@@ -69,7 +69,6 @@
// Transform the raster, so it fills the view:
raster.fitBounds(view.bounds, true);
group = new Group();
- group.applyMatrix = true;
for (var y = 0; y < values.amount; y++) {
for (var x = 0; x < values.amount; x++) {
var copy = piece.clone();
diff --git a/examples/Rasters/PhyllotaxisRaster.html b/examples/Rasters/PhyllotaxisRaster.html
index 568886d3..6fdc638d 100644
--- a/examples/Rasters/PhyllotaxisRaster.html
+++ b/examples/Rasters/PhyllotaxisRaster.html
@@ -28,7 +28,6 @@
// Create the group of circle shaped paths and scale it up a bit:
var group = createPhyllotaxis(values.amount);
- group.applyMatrix = true;
group.scale(3);
function createPhyllotaxis(amount) {
diff --git a/src/item/Clip.js b/src/item/Clip.js
new file mode 100644
index 00000000..a311f223
--- /dev/null
+++ b/src/item/Clip.js
@@ -0,0 +1,26 @@
+/*
+ * Paper.js - The Swiss Army Knife of Vector Graphics Scripting.
+ * http://paperjs.org/
+ *
+ * Copyright (c) 2011 - 2013, Juerg Lehni & Jonathan Puckey
+ * http://lehni.org/ & http://jonathanpuckey.com/
+ *
+ * Distributed under the MIT license. See LICENSE file for details.
+ *
+ * All rights reserved.
+ */
+
+/**
+ * @name Clip
+ *
+ * @class A Clip is a collection of items, similar to a Group. But instead of
+ * automatically passing on transformations to its children by calling
+ * {@link Item#applyMatrix()}, the transformations are stored in the internal
+ * matrix.
+ *
+ * @extends Group
+ */
+var Clip = this.Clip = Group.extend(/** @lends Clip# */{
+ _class: 'Clip',
+ _applyMatrix: false
+});
diff --git a/src/item/Item.js b/src/item/Item.js
index c975b897..4b494781 100644
--- a/src/item/Item.js
+++ b/src/item/Item.js
@@ -37,6 +37,9 @@ var Item = this.Item = Base.extend(Callback, {
}
}
}, /** @lends Item# */{
+ // All items apply their matrix by default.
+ // Exceptions are Raster, PlacedSymbol, Clip and Shape.
+ _applyMatrix: true,
_boundsSelected: false,
// Provide information about fields to be serialized, with their defaults
// that can be ommited.
@@ -508,16 +511,6 @@ var Item = this.Item = Base.extend(Callback, {
*/
_guide: false,
- /**
- * Specifies whether the item directly transforms its contents when
- * transformations are applied to it, or wether it simply stores them in
- * {@link Item#matrix}.
- *
- * @type Boolean
- * @default false
- */
- applyMatrix: false,
-
/**
* Specifies whether an item is selected and will also return {@code true}
* if the item is partially selected (groups with some selected or partially
@@ -1203,7 +1196,7 @@ var Item = this.Item = Base.extend(Callback, {
// TODO: Consider moving this to Base once it's useful in more than one
// place
var keys = ['_locked', '_visible', '_blendMode', '_opacity',
- '_clipMask', '_guide', 'applyMatrix'];
+ '_clipMask', '_guide'];
for (var i = 0, l = keys.length; i < l; i++) {
var key = keys[i];
if (this.hasOwnProperty(key))
@@ -2193,26 +2186,10 @@ var Item = this.Item = Base.extend(Callback, {
position = this._position;
// Simply preconcatenate the internal matrix with the passed one:
this._matrix.preConcatenate(matrix);
- if (this._transform)
- this._transform(matrix);
- // If we need to directly apply the accumulated transformations, call
- // #_applyMatrix() with the internal _matrix, and set it to the identity
- // transformation if it was possible to apply it. Application is not
- // possible on Raster, PointText, PlacedSymbol, since the matrix is
- // storing the actual location / transformation state.
- if ((this.applyMatrix || arguments[1])
- && this._applyMatrix(this._matrix)) {
- // When the matrix could be applied, we also need to transform
- // color styles with matrices (only gradients so far):
- var style = this._style,
- fillColor = style.getFillColor(),
- strokeColor = style.getStrokeColor();
- if (fillColor)
- fillColor.transform(this._matrix);
- if (strokeColor)
- strokeColor.transform(this._matrix);
- this._matrix.reset();
- }
+ // Call applyMatrix if we need to directly apply the accumulated
+ // transformations to the item's content.
+ if (this._applyMatrix || arguments[1])
+ this.applyMatrix(false);
// We always need to call _changed since we're caching bounds on all
// items, including Group.
this._changed(/*#=*/ Change.GEOMETRY);
@@ -2242,16 +2219,39 @@ var Item = this.Item = Base.extend(Callback, {
return this;
},
- _applyMatrix: function(matrix) {
- // Pass on the transformation to the children, and apply it there too,
- // by passing true for the 2nd hidden parameter.
+ _transformContent: function(matrix, applyMatrix) {
if (this._children) {
for (var i = 0, l = this._children.length; i < l; i++)
- this._children[i].transform(matrix, true);
+ this._children[i].transform(matrix, applyMatrix);
return true;
}
},
+ applyMatrix: function(_dontNotify) {
+ // Call #_transformContent() with the internal _matrix and pass true for
+ // applyMatrix. Application is not possible on Raster, PointText,
+ // PlacedSymbol, since the matrix is where the actual location /
+ // transformation state is stored.
+ // Pass on the transformation to the content, and apply it there too,
+ // by passing true for the 2nd hidden parameter.
+ if (this._transformContent(this._matrix, true)) {
+ // When the matrix could be applied, we also need to transform
+ // color styles with matrices (only gradients so far):
+ var style = this._style,
+ fillColor = style.getFillColor(true),
+ strokeColor = style.getStrokeColor(true);
+ if (fillColor)
+ fillColor.transform(this._matrix);
+ if (strokeColor)
+ strokeColor.transform(this._matrix);
+ // Reset the internal matrix to the identity transformation if it
+ // was possible to apply it.
+ this._matrix.reset();
+ }
+ if (!_dontNotify)
+ this._changed(/*#=*/ Change.GEOMETRY);
+ },
+
/**
* Transform the item so that its {@link #bounds} fit within the specified
* rectangle, without changing its aspect ratio.
diff --git a/src/item/PlacedSymbol.js b/src/item/PlacedSymbol.js
index 30326970..81def804 100644
--- a/src/item/PlacedSymbol.js
+++ b/src/item/PlacedSymbol.js
@@ -20,6 +20,7 @@
*/
var PlacedSymbol = this.PlacedSymbol = Item.extend(/** @lends PlacedSymbol# */{
_class: 'PlacedSymbol',
+ _applyMatrix: false,
// PlacedSymbol uses strokeBounds for bounds
_boundsGetter: { getBounds: 'getStrokeBounds' },
_boundsSelected: true,
diff --git a/src/item/Raster.js b/src/item/Raster.js
index 3cc8082e..5899222f 100644
--- a/src/item/Raster.js
+++ b/src/item/Raster.js
@@ -19,6 +19,7 @@
*/
var Raster = this.Raster = Item.extend(/** @lends Raster# */{
_class: 'Raster',
+ _applyMatrix: false,
// Raster doesn't make the distinction between the different bounds,
// so use the same name for all of them
_boundsGetter: 'getBounds',
diff --git a/src/item/Shape.js b/src/item/Shape.js
index ae2bda56..3a11335f 100644
--- a/src/item/Shape.js
+++ b/src/item/Shape.js
@@ -19,6 +19,7 @@
*/
var Shape = this.Shape = Item.extend(/** @lends Shape# */{
_class: 'Shape',
+ _applyMatrix: false,
initialize: function(type, point, size) {
this.base(point);
diff --git a/src/paper.js b/src/paper.js
index 26c10b4a..964f64fc 100644
--- a/src/paper.js
+++ b/src/paper.js
@@ -67,6 +67,7 @@ var paper = new function() {
/*#*/ include('item/Item.js');
/*#*/ include('item/Group.js');
/*#*/ include('item/Layer.js');
+/*#*/ include('item/Clip.js');
/*#*/ include('item/Shape.js');
/*#*/ include('item/Raster.js');
/*#*/ include('item/PlacedSymbol.js');
diff --git a/src/path/Path.js b/src/path/Path.js
index 106905be..de5afa2c 100644
--- a/src/path/Path.js
+++ b/src/path/Path.js
@@ -300,7 +300,7 @@ var Path = this.Path = PathItem.extend(/** @lends Path# */{
return true;
},
- _applyMatrix: function(matrix) {
+ _transformContent: function(matrix) {
var coords = new Array(6);
for (var i = 0, l = this._segments.length; i < l; i++)
this._segments[i]._transformCoordinates(matrix, coords, true);
@@ -1539,7 +1539,7 @@ var Path = this.Path = PathItem.extend(/** @lends Path# */{
* the specified point
*/
getNearestLocation: function(point) {
- point = this._matrix.inverseTransform(Point.read(arguments));
+ point = Point.read(arguments);
var curves = this.getCurves(),
minDist = Infinity,
minLoc = null;
diff --git a/src/path/PathItem.js b/src/path/PathItem.js
index 2568de87..734e86fa 100644
--- a/src/path/PathItem.js
+++ b/src/path/PathItem.js
@@ -20,9 +20,6 @@
* @extends Item
*/
var PathItem = this.PathItem = Item.extend(/** @lends PathItem# */{
- // All PathItems directly apply transformations by default.
- applyMatrix: true,
-
/**
* Returns all intersections between two {@link PathItem} items as an array
* of {@link CurveLocation} objects. {@link CompoundPath} items are also
diff --git a/src/style/Style.js b/src/style/Style.js
index 44f821ce..b965dbb8 100644
--- a/src/style/Style.js
+++ b/src/style/Style.js
@@ -151,12 +151,13 @@ var Style = this.Style = Base.extend(new function() {
}
};
- fields[get] = function() {
+ fields[get] = function(/* dontMerge */) {
var value,
children = this._item && this._item._children;
// If this item has children, walk through all of them and see if
// they all have the same style.
- if (!children || children.length === 0
+ // If true is passed for dontMerge, don't merge children styles
+ if (!children || children.length === 0 || arguments[0]
|| this._item._type === 'compound-path') {
var value = this._values[key];
if (value === undefined) {
diff --git a/test/tests/Item_Bounds.js b/test/tests/Item_Bounds.js
index 8978dbf8..920acdeb 100644
--- a/test/tests/Item_Bounds.js
+++ b/test/tests/Item_Bounds.js
@@ -53,7 +53,7 @@ test('path.bounds when contained in a transformed group', function() {
var group = new Group([path]);
compareRectangles(path.bounds, { x: 10, y: 10, width: 50, height: 50 }, 'path.bounds before group translation');
group.translate(100, 100);
- compareRectangles(path.bounds, { x: 10, y: 10, width: 50, height: 50 }, 'path.bounds after group translation');
+ compareRectangles(path.bounds, { x: 110, y: 110, width: 50, height: 50 }, 'path.bounds after group translation');
});
test('text.bounds', function() {