/* * Paper.js * * This file is part of Paper.js, a JavaScript Vector Graphics Library, * based on Scriptographer.org and designed to be largely API compatible. * http://paperjs.org/ * http://scriptographer.org/ * * Distributed under the MIT license. See LICENSE file for details. * * Copyright (c) 2011, Juerg Lehni & Jonathan Puckey * http://lehni.org/ & http://jonathanpuckey.com/ * * All rights reserved. */ var Rectangle = this.Rectangle = Base.extend({ /** @lends Rectangle# */ beans: true, /** * Creates a Rectangle object. * * @name Rectangle * @constructor * @param {Point} point the top-left point of the rectangle * @param {Size} size the size of the rectangle * * @class A Rectangle specifies an area that is enclosed by it's top-left * point (x, y), its width, and its height. It should not be confused with a * rectangular path, it is not an item. */ initialize: function(arg0, arg1, arg2, arg3) { if (arguments.length == 4) { // new Rectangle(x, y, width, height) this.x = arg0; this.y = arg1; this.width = arg2; this.height = arg3; } else if (arguments.length == 2) { if (arg1 && arg1.x !== undefined) { // new Rectangle(point1, point2) var point1 = Point.read(arguments, 0, 1); var point2 = Point.read(arguments, 1, 1); this.x = point1.x; this.y = point1.y; this.width = point2.x - point1.x; this.height = point2.y - point1.y; if (this.width < 0) { this.x = point2.x; this.width = -this.width; } if (this.height < 0) { this.y = point2.y; this.height = -this.height; } } else { // new Rectangle(point, size) var point = Point.read(arguments, 0, 1); var size = Size.read(arguments, 1, 1); this.x = point.x; this.y = point.y; this.width = size.width; this.height = size.height; } } else if (arg0) { // Use 0 as defaults, in case we're reading from a Point or Size this.x = arg0.x || 0; this.y = arg0.y || 0; this.width = arg0.width || 0; this.height = arg0.height || 0; } else { // new Rectangle() this.x = this.y = this.width = this.height = 0; } }, /** * The x position of the rectangle. * * @name Rectangle#x * @type number */ /** * The y position of the rectangle. * * @name Rectangle#y * @type number */ /** * The width of the rectangle. * * @name Rectangle#width * @type number */ /** * The height of the rectangle. * * @name Rectangle#height * @type number */ // DOCS: why does jsdocs document this function, when there are no comments? /** * @ignore */ set: function(x, y, width, height) { this.x = x; this.y = y; this.width = width; this.height = height; return this; }, /* * The top-left point of the rectangle * * @type Point * @bean */ getPoint: function() { return LinkedPoint.create(this, 'setPoint', this.x, this.y); }, setPoint: function(point) { point = Point.read(arguments); this.x = point.x; this.y = point.y; return this; }, /* * The size of the rectangle * * @type Size * @bean */ getSize: function() { return LinkedSize.create(this, 'setSize', this.width, this.height); }, setSize: function(size) { size = Size.read(arguments); this.width = size.width; this.height = size.height; return this; }, /** * {@grouptitle Side Positions} * * The position of the left hand side of the rectangle. Note that this * doesn't move the whole rectangle; the right hand side stays where it was. * * @type number * @bean */ getLeft: function() { return this.x; }, setLeft: function(left) { this.width -= left - this.x; this.x = left; return this; }, /** * The top coordinate of the rectangle. Note that this doesn't move the * whole rectangle: the bottom won't move. * * @type number * @bean */ getTop: function() { return this.y; }, setTop: function(top) { this.height -= top - this.y; this.y = top; return this; }, /** * The position of the right hand side of the rectangle. Note that this * doesn't move the whole rectangle; the left hand side stays where it was. * * @type number * @bean */ getRight: function() { return this.x + this.width; }, setRight: function(right) { this.width = right - this.x; return this; }, /** * The bottom coordinate of the rectangle. Note that this doesn't move the * whole rectangle: the top won't move. * * @type number * @bean */ getBottom: function() { return this.y + this.height; }, setBottom: function(bottom) { this.height = bottom - this.y; return this; }, /** * The center-x coordinate of the rectangle. * * @type number * @bean */ getCenterX: function() { return this.x + this.width * 0.5; }, setCenterX: function(x) { this.x = x - this.width * 0.5; return this; }, /** * The center-y coordinate of the rectangle. * * @type number * @bean */ getCenterY: function() { return this.y + this.height * 0.5; }, setCenterY: function(y) { this.y = y - this.height * 0.5; return this; }, /** * The center point of the rectangle. * * @type Point * @bean */ getCenter: function() { return LinkedPoint.create(this, 'setCenter', this.getCenterX(), this.getCenterY()); }, setCenter: function(point) { point = Point.read(arguments); return this.setCenterX(point.x).setCenterY(point.y); }, // DOCS: Rectangle#topLeft, Rectangle#topRight etc. // DOCS: Rectangle#equals /** * @param {Rectangle} rect * @return {boolean} */ equals: function(rect) { rect = Rectangle.read(arguments); return this.x == rect.x && this.y == rect.y && this.width == rect.width && this.height == rect.height; }, /** * @return {boolean} true if the rectangle is empty, false otherwise. */ isEmpty: function() { return this.width == 0 || this.height == 0; }, /** * {@grouptitle Geometric Tests} * * Tests if the specified point is inside the boundary of the rectangle. * * @name Rectangle#contains^2 * @function * @param {Point} point the specified point * @return {boolean} true if the point is inside the rectangle's * boundary, false otherwise */ /** * Tests if the interior of the rectangle entirely contains the specified * rectangle. * * @param {Rectangle} rect The specified rectangle * @return {boolean} true if the rectangle entirely contains the * specified rectangle, false otherwise */ contains: function(rect) { if (rect.width !== undefined) { return rect.x >= this.x && rect.y >= this.y && rect.x + rect.width <= this.x + this.width && rect.y + rect.height <= this.y + this.height; } else { var point = Point.read(arguments); return point.x >= this.x && point.y >= this.y && point.x <= this.x + this.width && point.y <= this.y + this.height; } }, /** * Tests if the interior of this rectangle intersects the interior of * another rectangle. * * @param {Rectangle} rect the specified rectangle * @return {boolean} true if the rectangle and the specified rectangle * intersect each other, false otherwise */ intersects: function(rect) { rect = Rectangle.read(arguments); return rect.x + rect.width > this.x && rect.y + rect.height > this.y && rect.x < this.x + this.width && rect.y < this.y + this.height; }, /** * {@grouptitle Boolean Operations} * * Returns a new rectangle representing the intersection of this rectangle * with the specified rectangle. * * @param {Rectangle} rect The rectangle to be intersected with this * rectangle * @return {Rectangle} The largest rectangle contained in both the specified * rectangle and in this rectangle. */ intersect: function(rect) { rect = Rectangle.read(arguments); var x1 = Math.max(this.x, rect.x), y1 = Math.max(this.y, rect.y), x2 = Math.min(this.x + this.width, rect.x + rect.width), y2 = Math.min(this.y + this.height, rect.y + rect.height); return Rectangle.create(x1, y1, x2 - x1, y2 - y1); }, /** * Returns a new rectangle representing the union of this rectangle with the * specified rectangle. * * @param {Rectangle} rect the rectangle to be combined with this rectangle * @return {Rectangle} the smallest rectangle containing both the specified * rectangle and this rectangle. */ unite: function(rect) { rect = Rectangle.read(arguments); var x1 = Math.min(this.x, rect.x), y1 = Math.min(this.y, rect.y), x2 = Math.max(this.x + this.width, rect.x + rect.width), y2 = Math.max(this.y + this.height, rect.y + rect.height); return Rectangle.create(x1, y1, x2 - x1, y2 - y1); }, /** * Adds a point to this rectangle. The resulting rectangle is the * smallest rectangle that contains both the original rectangle and the * specified point. * * After adding a point, a call to {@code contains} with the added * point as an argument does not necessarily return {@code true}. * The {@code contains} method does not return {@code true} * for points on the right or bottom edges of a rectangle. Therefore, if the * added point falls on the left or bottom edge of the enlarged rectangle, * {@code contains} returns {@code false} for that point. * * @param {Point} point */ include: function(point) { point = Point.read(arguments); var x1 = Math.min(this.x, point.x), y1 = Math.min(this.y, point.y), x2 = Math.max(this.x + this.width, point.x), y2 = Math.max(this.y + this.height, point.y); return Rectangle.create(x1, y1, x2 - x1, y2 - y1); }, /** * @return {string} A string representation of this rectangle. */ toString: function() { var format = Base.formatNumber; return '{ x: ' + format(this.x) + ', y: ' + format(this.y) + ', width: ' + format(this.width) + ', height: ' + format(this.height) + ' }'; }, statics: { // See Point.create() create: function(x, y, width, height) { return new Rectangle(Rectangle.dont).set(x, y, width, height); } } }, new function() { return Base.each([ ['Top', 'Left'], ['Top', 'Right'], ['Bottom', 'Left'], ['Bottom', 'Right'], ['Left', 'Center'], ['Top', 'Center'], ['Right', 'Center'], ['Bottom', 'Center'] ], function(parts, index) { var part = parts.join(''); // find out if the first of the pair is an x or y property, // by checking the first character for [R]ight or [L]eft; var xFirst = /^[RL]/.test(part); // Rename Center to CenterX or CenterY: if (index >= 4) parts[1] += xFirst ? 'Y' : 'X'; var x = parts[xFirst ? 0 : 1], y = parts[xFirst ? 1 : 0], getX = 'get' + x, getY = 'get' + y, setX = 'set' + x, setY = 'set' + y, get = 'get' + part, set = 'set' + part; this[get] = function() { return LinkedPoint.create(this, set, this[getX](), this[getY]()); }; this[set] = function(point) { point = Point.read(arguments); // Note: call chaining happens here. return this[setX](point.x)[setY](point.y); }; }, { beans: true }); }); /** * An internal version of Rectangle that notifies its owner of each change * through setting itself again on the setter that corresponds to the getter * that produced this LinkedRectangle. See uses of LinkedRectangle.create() * Note: This prototype is not exported. * @ignore */ var LinkedRectangle = Rectangle.extend({ beans: true, set: function(x, y, width, height, dontNotify) { this._x = x; this._y = y; this._width = width; this._height = height; if (!dontNotify) this._owner[this._setter](this); return this; }, statics: { /** * Provide a faster creator for Points out of two coordinates that * does not rely on Point#initialize at all. This speeds up all math * operations a lot. * @ignore */ create: function(owner, setter, x, y, width, height) { var rect = new LinkedRectangle(LinkedRectangle.dont).set( x, y, width, height, true); rect._owner = owner; rect._setter = setter; return rect; } } }, new function() { var proto = Rectangle.prototype; return Base.each(['x', 'y', 'width', 'height'], function(key) { var part = Base.capitalize(key); var internal = '_' + key; this['get' + part] = function() { return this[internal]; }; this['set' + part] = function(value) { this[internal] = value; // Check if this setter is called from another one which sets // _dontNotify, as it will notify itself if (!this._dontNotify) this._owner[this._setter](this); }; }, Base.each(['Point', 'Size', 'Center', 'Left', 'Top', 'Right', 'Bottom', 'CenterX', 'CenterY', 'TopLeft', 'TopRight', 'BottomLeft', 'BottomRight', 'LeftCenter', 'TopCenter', 'RightCenter', 'BottomCenter'], function(key) { var name = 'set' + key; this[name] = function(value) { // Make sure the above setters of x, y, width, height do not // each notify the owner, as we're going to take care of this // afterwards here, only once per change. this._dontNotify = true; proto[name].apply(this, arguments); delete this._dontNotify; this._owner[this._setter](this); return this; }; }, { beans: true }) ); });