diff --git a/src/item/Item.js b/src/item/Item.js index ddee7e03..61985d38 100644 --- a/src/item/Item.js +++ b/src/item/Item.js @@ -1248,13 +1248,26 @@ var Item = this.Item = Base.extend(Callback, /** @lends Item# */{ parent = parent._parent; } return false; - }, - + } +}, Base.each(['bounds', 'strokeBounds', 'handleBounds', 'roughBounds'], function(name) { + this['get' + Base.capitalize(name)] = function(/* matrix */) { + var matrix = arguments[0]; + // If the matrix is an identity transformation, set it to null for + // faster processing + if (matrix && matrix.isIdentity()) + matrix = null; + // TODO: Caching! + var bounds = this._getBounds(name, matrix); + return name == 'bounds' ? this._createBounds(bounds) : bounds; + }; +}, /** @lends Item# */{ /** - * Loops through all children, gets their bounds and finds the bounds around - * all of them. + * Internal method used in all the bounds getters. It loops through all the + * children, gets their bounds and finds the bounds around all of them. + * Subclasses override it to define calculations for the various required + * bounding types. */ - _getBounds: function(getter, cacheName, matrix) { + _getBounds: function(type, matrix) { // Note: We cannot cache these results here, since we do not get // _changed() notifications here for changing geometry in children. // But cacheName is used in sub-classes such as PlacedItem. @@ -1270,15 +1283,14 @@ var Item = this.Item = Base.extend(Callback, /** @lends Item# */{ for (var i = 0, l = children.length; i < l; i++) { var child = children[i]; if (child._visible) { - var rect = child[getter](matrix); + var rect = child._getBounds(type, matrix); x1 = Math.min(rect.x, x1); y1 = Math.min(rect.y, y1); x2 = Math.max(rect.x + rect.width, x2); y2 = Math.max(rect.y + rect.height, y2); } } - var bounds = Rectangle.create(x1, y1, x2 - x1, y2 - y1); - return getter == 'getBounds' ? this._createBounds(bounds) : bounds; + return Rectangle.create(x1, y1, x2 - x1, y2 - y1); }, /** @@ -1296,9 +1308,9 @@ var Item = this.Item = Base.extend(Callback, /** @lends Item# */{ * @type Rectangle * @bean */ - getBounds: function(/* matrix */) { - return this._getBounds('getBounds', '_bounds', arguments[0]); - }, +// getBounds: function(/* matrix */) { +// return this._getBounds('getBounds', '_bounds', arguments[0]); +// }, setBounds: function(rect) { rect = Rectangle.read(arguments); @@ -1319,7 +1331,7 @@ var Item = this.Item = Base.extend(Callback, /** @lends Item# */{ matrix.translate(-center.x, -center.y); // Now execute the transformation: this.transform(matrix); - }, + } /** * The bounding rectangle of the item including stroke width. @@ -1327,9 +1339,9 @@ var Item = this.Item = Base.extend(Callback, /** @lends Item# */{ * @type Rectangle * @bean */ - getStrokeBounds: function(/* matrix */) { - return this._getBounds('getStrokeBounds', '_strokeBounds', arguments[0]); - }, +// getStrokeBounds: function(/* matrix */) { +// return this._getBounds('getStrokeBounds', '_strokeBounds', arguments[0]); +// }, /** * The bounding rectangle of the item including handles. @@ -1337,9 +1349,9 @@ var Item = this.Item = Base.extend(Callback, /** @lends Item# */{ * @type Rectangle * @bean */ - getHandleBounds: function(/* matrix */) { - return this._getBounds('getHandleBounds', '_handleBounds', arguments[0]); - }, +// getHandleBounds: function(/* matrix */) { +// return this._getBounds('getHandleBounds', '_handleBounds', arguments[0]); +// }, /** * The rough bounding rectangle of the item that is shure to include all of @@ -1349,10 +1361,10 @@ var Item = this.Item = Base.extend(Callback, /** @lends Item# */{ * @bean * @ignore */ - getRoughBounds: function(/* matrix */) { - return this._getBounds('getRoughBounds', '_roughBounds', arguments[0]); - }, - +// getRoughBounds: function(/* matrix */) { +// return this._getBounds('getRoughBounds', '_roughBounds', arguments[0]); +// }, +}), /** @lends Item# */{ /** * {@grouptitle Stroke Style} * diff --git a/src/item/PlacedItem.js b/src/item/PlacedItem.js index bc2c6327..1e237823 100644 --- a/src/item/PlacedItem.js +++ b/src/item/PlacedItem.js @@ -59,29 +59,14 @@ var PlacedItem = this.PlacedItem = Item.extend(/** @lends PlacedItem# */{ this._changed(Change.GEOMETRY); }, - getBounds: function(/* matrix */) { - var useCache = arguments[0] === undefined; - if (useCache && this._bounds) - return this._bounds; - // The bounds of PlacedItems are the same as the strokeBounds, but are - // wrapped in a LinkedRectangle that catch changes for us. - var bounds = this.getStrokeBounds(arguments[0]); - if (useCache) - bounds = this._bounds = this._createBounds(bounds); - return bounds; - }, - - _getBounds: function(getter, cacheName, matrix) { - var useCache = matrix === undefined; - if (useCache && this[cacheName]) - return this[cacheName]; + _getBounds: function(type, matrix) { + // The bounds of PlacedItems are the same as the strokeBounds + if (type == 'bounds') + type = 'strokeBounds'; // Concatenate the passed matrix with the internal one matrix = matrix ? matrix.clone().concatenate(this._matrix) : this._matrix; // Call _calculateBounds, which needs to be defined in the subclasses: - var bounds = this._calculateBounds(getter, matrix); - if (useCache) - this[cacheName] = bounds; - return bounds; + return this._calculateBounds(type, matrix); } }); diff --git a/src/item/PlacedSymbol.js b/src/item/PlacedSymbol.js index a7c5721d..78733c2e 100644 --- a/src/item/PlacedSymbol.js +++ b/src/item/PlacedSymbol.js @@ -94,9 +94,9 @@ var PlacedSymbol = this.PlacedSymbol = PlacedItem.extend(/** @lends PlacedSymbol return this._clone(new PlacedSymbol(this.symbol, this._matrix.clone())); }, - _calculateBounds: function(getter, matrix) { + _calculateBounds: function(type, matrix) { // Ask the symbol definition to calculate the bounds for us - return this.symbol._definition[getter](matrix); + return this.symbol._definition._getBounds(type, matrix); }, draw: function(ctx, param) { diff --git a/src/item/Raster.js b/src/item/Raster.js index 8705cc64..f8ca0946 100644 --- a/src/item/Raster.js +++ b/src/item/Raster.js @@ -380,24 +380,13 @@ var Raster = this.Raster = PlacedItem.extend(/** @lends Raster# */{ this.getContext(true).putImageData(data, point.x, point.y); }, - _calculateBounds: function(getter, matrix) { - // The getter is only used for PlacedSymbol, not Raster + _calculateBounds: function(type, matrix) { + // Note: Raster doesn't make the distinction between the different + // bounds return matrix._transformBounds( new Rectangle(this._size).setCenter(0, 0)); }, - // Since Raster doesn't make the distinction between the different bounds, - // simply redirect to strokeBounds so the cached values can be reused. - // TODO: Shouldn't this be moved to PlacedItem - - getHandleBounds: function(/* matrix */) { - return this.getStrokeBounds(arguments[0]); - }, - - getRoughBounds: function(/* matrix */) { - return this.getStrokeBounds(arguments[0]); - }, - _hitTest: function(point, options) { point = this._matrix._inverseTransform(point); if (point.isInside(new Rectangle(this._size).setCenter(0, 0))) { diff --git a/src/path/Path.js b/src/path/Path.js index 7865da65..fe54d831 100644 --- a/src/path/Path.js +++ b/src/path/Path.js @@ -208,7 +208,7 @@ var Path = this.Path = PathItem.extend(/** @lends Path# */{ // taken into account. _transform: function(matrix, flags) { - if (!matrix.isIdentity()) { + if (matrix && !matrix.isIdentity()) { var coords = new Array(6); for (var i = 0, l = this._segments.length; i < l; i++) { this._segments[i]._transformCoordinates(matrix, coords, true); @@ -1761,109 +1761,6 @@ var Path = this.Path = PathItem.extend(/** @lends Path# */{ } }; }, new function() { // A dedicated scope for the tricky bounds calculations - - function getBounds(that, matrix, strokePadding) { - // Code ported and further optimised from: - // http://blog.hackers-cafe.net/2009/06/how-to-calculate-bezier-curves-bounding.html - var segments = that._segments, - first = segments[0]; - if (!first) - return null; - var coords = new Array(6), - prevCoords = new Array(6); - // If the matrix is an identity transformation, set it to null for - // faster processing - if (matrix && matrix.isIdentity()) - matrix = null; - // Make coordinates for first segment available in prevCoords. - first._transformCoordinates(matrix, prevCoords, false); - var min = prevCoords.slice(0, 2), - max = min.slice(0), // clone - // Add some tolerance for good roots, as t = 0 / 1 are added - // seperately anyhow, and we don't want joins to be added with - // radiuses in getStrokeBounds() - tMin = Numerical.TOLERANCE, - tMax = 1 - tMin; - function processSegment(segment) { - segment._transformCoordinates(matrix, coords, false); - - for (var i = 0; i < 2; i++) { - var v0 = prevCoords[i], // prev.point - v1 = prevCoords[i + 4], // prev.handleOut - v2 = coords[i + 2], // segment.handleIn - v3 = coords[i]; // segment.point - - function add(value, t) { - var padding = 0; - if (value == null) { - // Calculate bezier polynomial at t - var u = 1 - t; - value = u * u * u * v0 - + 3 * u * u * t * v1 - + 3 * u * t * t * v2 - + t * t * t * v3; - // Only add strokeWidth to bounds for points which lie - // within 0 < t < 1. The corner cases for cap and join - // are handled in getStrokeBounds() - padding = strokePadding ? strokePadding[i] : 0; - } - var left = value - padding, - right = value + padding; - if (left < min[i]) - min[i] = left; - if (right > max[i]) - max[i] = right; - - } - add(v3, null); - - // Calculate derivative of our bezier polynomial, divided by 3. - // Dividing by 3 allows for simpler calculations of a, b, c and - // leads to the same quadratic roots below. - var a = 3 * (v1 - v2) - v0 + v3, - b = 2 * (v0 + v2) - 4 * v1, - c = v1 - v0; - - // Solve for derivative for quadratic roots. Each good root - // (meaning a solution 0 < t < 1) is an extrema in the cubic - // polynomial and thus a potential point defining the bounds - // TODO: Use tolerance here, just like Numerical.solveQuadratic - if (a == 0) { - if (b == 0) - continue; - var t = -c / b; - // Test for good root and add to bounds if good (same below) - if (tMin < t && t < tMax) - add(null, t); - continue; - } - - var q = b * b - 4 * a * c; - if (q < 0) - continue; - // TODO: Match this with Numerical.solveQuadratic - var sqrt = Math.sqrt(q), - f = -0.5 / a, - t1 = (b - sqrt) * f, - t2 = (b + sqrt) * f; - if (tMin < t1 && t1 < tMax) - add(null, t1); - if (tMin < t2 && t2 < tMax) - add(null, t2); - } - // Swap coordinate buffers - var tmp = prevCoords; - prevCoords = coords; - coords = tmp; - } - for (var i = 1, l = segments.length; i < l; i++) - processSegment(segments[i]); - if (that._closed) - processSegment(first); - return Rectangle.create(min[0], min[1], - max[0] - min[0], max[1] - min[1]); - } - /** * Returns the horizontal and vertical padding that a transformed round * stroke adds to the bounding box, by calculating the dimensions of a @@ -1906,51 +1803,129 @@ var Path = this.Path = PathItem.extend(/** @lends Path# */{ return [Math.abs(x), Math.abs(y)]; } - return { + var get = { /** - * The bounding rectangle of the item excluding stroke width. - * - * @param matrix optional - * - * @ignore + * Returns the bounding rectangle of the item excluding stroke width. */ - getBounds: function(/* matrix */) { - var useCache = arguments[0] === undefined; - // Pass the matrix hidden from Bootstrap, so it still inject - // getBounds as bean too. - if (useCache && this._bounds) - return this._bounds; - var bounds = this._createBounds(getBounds(this, arguments[0])); - if (useCache) - this._bounds = bounds; - return bounds; + bounds: function(that, matrix, strokePadding) { + // Code ported and further optimised from: + // http://blog.hackers-cafe.net/2009/06/how-to-calculate-bezier-curves-bounding.html + var segments = that._segments, + first = segments[0]; + if (!first) + return null; + var coords = new Array(6), + prevCoords = new Array(6); + // Make coordinates for first segment available in prevCoords. + first._transformCoordinates(matrix, prevCoords, false); + var min = prevCoords.slice(0, 2), + max = min.slice(0), // clone + // Add some tolerance for good roots, as t = 0 / 1 are added + // seperately anyhow, and we don't want joins to be added with + // radiuses in getStrokeBounds() + tMin = Numerical.TOLERANCE, + tMax = 1 - tMin; + function processSegment(segment) { + segment._transformCoordinates(matrix, coords, false); + + for (var i = 0; i < 2; i++) { + var v0 = prevCoords[i], // prev.point + v1 = prevCoords[i + 4], // prev.handleOut + v2 = coords[i + 2], // segment.handleIn + v3 = coords[i]; // segment.point + + function add(value, t) { + var padding = 0; + if (value == null) { + // Calculate bezier polynomial at t + var u = 1 - t; + value = u * u * u * v0 + + 3 * u * u * t * v1 + + 3 * u * t * t * v2 + + t * t * t * v3; + // Only add strokeWidth to bounds for points which lie + // within 0 < t < 1. The corner cases for cap and join + // are handled in getStrokeBounds() + padding = strokePadding ? strokePadding[i] : 0; + } + var left = value - padding, + right = value + padding; + if (left < min[i]) + min[i] = left; + if (right > max[i]) + max[i] = right; + + } + add(v3, null); + + // Calculate derivative of our bezier polynomial, divided by 3. + // Dividing by 3 allows for simpler calculations of a, b, c and + // leads to the same quadratic roots below. + var a = 3 * (v1 - v2) - v0 + v3, + b = 2 * (v0 + v2) - 4 * v1, + c = v1 - v0; + + // Solve for derivative for quadratic roots. Each good root + // (meaning a solution 0 < t < 1) is an extrema in the cubic + // polynomial and thus a potential point defining the bounds + // TODO: Use tolerance here, just like Numerical.solveQuadratic + if (a == 0) { + if (b == 0) + continue; + var t = -c / b; + // Test for good root and add to bounds if good (same below) + if (tMin < t && t < tMax) + add(null, t); + continue; + } + + var q = b * b - 4 * a * c; + if (q < 0) + continue; + // TODO: Match this with Numerical.solveQuadratic + var sqrt = Math.sqrt(q), + f = -0.5 / a, + t1 = (b - sqrt) * f, + t2 = (b + sqrt) * f; + if (tMin < t1 && t1 < tMax) + add(null, t1); + if (tMin < t2 && t2 < tMax) + add(null, t2); + } + // Swap coordinate buffers + var tmp = prevCoords; + prevCoords = coords; + coords = tmp; + } + for (var i = 1, l = segments.length; i < l; i++) + processSegment(segments[i]); + if (that._closed) + processSegment(first); + return Rectangle.create(min[0], min[1], + max[0] - min[0], max[1] - min[1]); }, /** - * The bounding rectangle of the item including stroke width. - * - * @ignore + * Returns the bounding rectangle of the item including stroke width. */ - getStrokeBounds: function(/* matrix */) { - if (!this._style._strokeColor || !this._style._strokeWidth) - return this.getBounds.apply(this, arguments); - var useCache = arguments[0] === undefined; - if (useCache && this._strokeBounds) - return this._strokeBounds; - var matrix = arguments[0], // set #getBounds() - width = this.getStrokeWidth(), + strokeBounds: function(that, matrix) { + // TODO: Should we access this.getStrokeColor, as we do in _transform? + // TODO: Find a way to reuse 'bounds' cache instead? + if (!that._style._strokeColor || !that._style._strokeWidth) + return get.bounds(that, matrix); + var width = that.getStrokeWidth(), radius = width / 2, padding = getPenPadding(radius, matrix), - join = this.getStrokeJoin(), - cap = this.getStrokeCap(), + join = that.getStrokeJoin(), + cap = that.getStrokeCap(), // miter is relative to width. Divide it by 2 since we're // measuring half the distance below - miter = this.getMiterLimit() * width / 2, - segments = this._segments, + miter = that.getMiterLimit() * width / 2, + segments = that._segments, length = segments.length, // It seems to be compatible with Ai we need to pass pen padding - // untransformed to getBounds() - bounds = getBounds(this, matrix, getPenPadding(radius)); + // untransformed to get.bounds + bounds = get.bounds(that, matrix, getPenPadding(radius)); // Create a rectangle of padding size, used for union with bounds // further down var joinBounds = new Rectangle(new Size(padding).multiply(2)); @@ -2020,43 +1995,31 @@ var Path = this.Path = PathItem.extend(/** @lends Path# */{ } } - for (var i = 1, l = length - (this._closed ? 0 : 1); i < l; i++) { + for (var i = 1, l = length - (that._closed ? 0 : 1); i < l; i++) { addJoin(segments[i], join); } - if (this._closed) { + if (that._closed) { addJoin(segments[0], join); } else { addCap(segments[0], cap, 0); addCap(segments[length - 1], cap, 1); } - if (useCache) - this._strokeBounds = bounds; return bounds; }, /** - * The bounding rectangle of the item including handles. - * - * @ignore + * Returns the bounding rectangle of the item including handles. */ - getHandleBounds: function(/* matrix, stroke, join */) { - // Do not check for matrix but count parameters to determine if we - // can cache or not, as the other parameters have an influence on - // that too: - var useCache = arguments.length == 0; - if (useCache && this._handleBounds) - return this._handleBounds; + handleBounds: function(that, matrix, stroke, join) { var coords = new Array(6), - matrix = arguments[0], - stroke = arguments[1] / 2 || 0, // Stroke padding - join = arguments[2] / 2 || 0, // Join padding, for miterLimit - open = !this._closed, x1 = Infinity, x2 = -x1, y1 = x1, y2 = x2; - for (var i = 0, l = this._segments.length; i < l; i++) { - var segment = this._segments[i]; + stroke = stroke / 2 || 0; // Stroke padding + join = join / 2 || 0; // Join padding, for miterLimit + for (var i = 0, l = that._segments.length; i < l; i++) { + var segment = that._segments[i]; segment._transformCoordinates(matrix, coords, false); for (var j = 0; j < 6; j += 2) { // Use different padding for points or handles @@ -2073,32 +2036,28 @@ var Path = this.Path = PathItem.extend(/** @lends Path# */{ if (yx > y2) y2 = yx; } } - var bounds = Rectangle.create(x1, y1, x2 - x1, y2 - y1); - if (useCache) - this._handleBounds = bounds; - return bounds; + return Rectangle.create(x1, y1, x2 - x1, y2 - y1); }, /** - * The rough bounding rectangle of the item that is shure to include all - * of the drawing, including stroke width. - * - * @ignore + * Returns the rough bounding rectangle of the item that is shure to + * include all of the drawing, including stroke width. */ - getRoughBounds: function(/* matrix */) { - var useCache = arguments[0] === undefined; - if (useCache && this._roughBounds) - return this._roughBounds; - // Delegate to #getHandleBounds(), but pass on radius values for - // stroke and joins. Hanlde miter joins specially, by passing the - // largets radius possible. - var bounds = this.getHandleBounds(arguments[0], this.strokeWidth, - this.getStrokeJoin() == 'miter' - ? this.strokeWidth * this.getMiterLimit() - : this.strokeWidth); - if (useCache) - this._roughBounds = bounds; - return bounds; + roughBounds: function(that, matrix) { + // Delegate to handleBounds, but pass on radius values for stroke + // and joins. Hanlde miter joins specially, by passing the largets + // radius possible. + var width = that.getStrokeWidth(); + return get.handleBounds(that, matrix, width, + that.getStrokeJoin() == 'miter' + ? width * that.getMiterLimit() + : width); + } + }; + + return { + _getBounds: function(type, matrix) { + return get[type](this, matrix); } }; }); diff --git a/src/text/PointText.js b/src/text/PointText.js index 73f172fb..638b75ab 100644 --- a/src/text/PointText.js +++ b/src/text/PointText.js @@ -101,14 +101,10 @@ var PointText = this.PointText = TextItem.extend(/** @lends PointText# */{ var context = null; return { - _getBounds: function(getter, cacheName, matrix) { - // TODO: What if first argument is a Matrix? See PlacedItem... - // Return from the cache if we can - if (this[cacheName]) - return this[cacheName]; + _getBounds: function(type, matrix) { // If there is no text, there are no bounds if (!this._content) - return this[cacheName] = new Rectangle(); + return new Rectangle(); // Create an in-memory canvas on which to do the measuring if (!context) context = CanvasProvider.getCanvas(Size.create(1, 1)).getContext('2d'); @@ -129,10 +125,8 @@ var PointText = this.PointText = TextItem.extend(/** @lends PointText# */{ // rough guess var bounds = Rectangle.create(x, leading / 4 + (count - 1) * leading, width, -count * leading); - this._matrix._transformBounds(bounds, bounds); - // TODO: Only cache if no matrix is provided - this[cacheName] = bounds; - return getter == 'getBounds' ? this._createBounds(bounds) : bounds; + // TODO: matrix! + return this._matrix._transformBounds(bounds, bounds); } }; });