Path: blob/trunk/third_party/closure/goog/math/rect.js
4101 views
/**1* @license2* Copyright The Closure Library Authors.3* SPDX-License-Identifier: Apache-2.04*/56/**7* @fileoverview A utility class for representing rectangles. Some of these8* functions should be migrated over to non-nullable params.9*/1011goog.provide('goog.math.Rect');1213goog.require('goog.asserts');14goog.require('goog.math.Box');15goog.require('goog.math.Coordinate');16goog.require('goog.math.IRect');17goog.require('goog.math.Size');18192021/**22* Class for representing rectangular regions.23* @param {number} x Left.24* @param {number} y Top.25* @param {number} w Width.26* @param {number} h Height.27* @struct28* @constructor29* @implements {goog.math.IRect}30*/31goog.math.Rect = function(x, y, w, h) {32'use strict';33/** @type {number} */34this.left = x;3536/** @type {number} */37this.top = y;3839/** @type {number} */40this.width = w;4142/** @type {number} */43this.height = h;44};454647/**48* @return {!goog.math.Rect} A new copy of this Rectangle.49*/50goog.math.Rect.prototype.clone = function() {51'use strict';52return new goog.math.Rect(this.left, this.top, this.width, this.height);53};545556/**57* Returns a new Box object with the same position and dimensions as this58* rectangle.59* @return {!goog.math.Box} A new Box representation of this Rectangle.60*/61goog.math.Rect.prototype.toBox = function() {62'use strict';63var right = this.left + this.width;64var bottom = this.top + this.height;65return new goog.math.Box(this.top, right, bottom, this.left);66};676869/**70* Creates a new Rect object with the position and size given.71* @param {!goog.math.Coordinate} position The top-left coordinate of the Rect72* @param {!goog.math.Size} size The size of the Rect73* @return {!goog.math.Rect} A new Rect initialized with the given position and74* size.75*/76goog.math.Rect.createFromPositionAndSize = function(position, size) {77'use strict';78return new goog.math.Rect(position.x, position.y, size.width, size.height);79};808182/**83* Creates a new Rect object with the same position and dimensions as a given84* Box. Note that this is only the inverse of toBox if left/top are defined.85* @param {goog.math.Box} box A box.86* @return {!goog.math.Rect} A new Rect initialized with the box's position87* and size.88*/89goog.math.Rect.createFromBox = function(box) {90'use strict';91return new goog.math.Rect(92box.left, box.top, box.right - box.left, box.bottom - box.top);93};949596if (goog.DEBUG) {97/**98* Returns a nice string representing size and dimensions of rectangle.99* @return {string} In the form (50, 73 - 75w x 25h).100* @override101*/102goog.math.Rect.prototype.toString = function() {103'use strict';104return '(' + this.left + ', ' + this.top + ' - ' + this.width + 'w x ' +105this.height + 'h)';106};107}108109110/**111* Compares rectangles for equality.112* @param {goog.math.IRect} a A Rectangle.113* @param {goog.math.IRect} b A Rectangle.114* @return {boolean} True iff the rectangles have the same left, top, width,115* and height, or if both are null.116*/117goog.math.Rect.equals = function(a, b) {118'use strict';119if (a == b) {120return true;121}122if (!a || !b) {123return false;124}125return a.left == b.left && a.width == b.width && a.top == b.top &&126a.height == b.height;127};128129130/**131* Computes the intersection of this rectangle and the rectangle parameter. If132* there is no intersection, returns false and leaves this rectangle as is.133* @param {goog.math.IRect} rect A Rectangle.134* @return {boolean} True iff this rectangle intersects with the parameter.135*/136goog.math.Rect.prototype.intersection = function(rect) {137'use strict';138var x0 = Math.max(this.left, rect.left);139var x1 = Math.min(this.left + this.width, rect.left + rect.width);140141if (x0 <= x1) {142var y0 = Math.max(this.top, rect.top);143var y1 = Math.min(this.top + this.height, rect.top + rect.height);144145if (y0 <= y1) {146this.left = x0;147this.top = y0;148this.width = x1 - x0;149this.height = y1 - y0;150151return true;152}153}154return false;155};156157158/**159* Returns the intersection of two rectangles. Two rectangles intersect if they160* touch at all, for example, two zero width and height rectangles would161* intersect if they had the same top and left.162* @param {goog.math.IRect} a A Rectangle.163* @param {goog.math.IRect} b A Rectangle.164* @return {goog.math.Rect} A new intersection rect (even if width and height165* are 0), or null if there is no intersection.166*/167goog.math.Rect.intersection = function(a, b) {168'use strict';169// There is no nice way to do intersection via a clone, because any such170// clone might be unnecessary if this function returns null. So, we duplicate171// code from above.172173var x0 = Math.max(a.left, b.left);174var x1 = Math.min(a.left + a.width, b.left + b.width);175176if (x0 <= x1) {177var y0 = Math.max(a.top, b.top);178var y1 = Math.min(a.top + a.height, b.top + b.height);179180if (y0 <= y1) {181return new goog.math.Rect(x0, y0, x1 - x0, y1 - y0);182}183}184return null;185};186187188/**189* Returns whether two rectangles intersect. Two rectangles intersect if they190* touch at all, for example, two zero width and height rectangles would191* intersect if they had the same top and left.192* @param {goog.math.IRect} a A Rectangle.193* @param {goog.math.IRect} b A Rectangle.194* @return {boolean} Whether a and b intersect.195*/196goog.math.Rect.intersects = function(a, b) {197'use strict';198return (199a.left <= b.left + b.width && b.left <= a.left + a.width &&200a.top <= b.top + b.height && b.top <= a.top + a.height);201};202203204/**205* Returns whether a rectangle intersects this rectangle.206* @param {goog.math.IRect} rect A rectangle.207* @return {boolean} Whether rect intersects this rectangle.208*/209goog.math.Rect.prototype.intersects = function(rect) {210'use strict';211return goog.math.Rect.intersects(this, rect);212};213214215/**216* Computes the difference regions between two rectangles. The return value is217* an array of 0 to 4 rectangles defining the remaining regions of the first218* rectangle after the second has been subtracted.219* @param {goog.math.Rect} a A Rectangle.220* @param {goog.math.IRect} b A Rectangle.221* @return {!Array<!goog.math.Rect>} An array with 0 to 4 rectangles which222* together define the difference area of rectangle a minus rectangle b.223*/224goog.math.Rect.difference = function(a, b) {225'use strict';226var intersection = goog.math.Rect.intersection(a, b);227if (!intersection || !intersection.height || !intersection.width) {228return [a.clone()];229}230231var result = [];232233var top = a.top;234var height = a.height;235236var ar = a.left + a.width;237var ab = a.top + a.height;238239var br = b.left + b.width;240var bb = b.top + b.height;241242// Subtract off any area on top where A extends past B243if (b.top > a.top) {244result.push(new goog.math.Rect(a.left, a.top, a.width, b.top - a.top));245top = b.top;246// If we're moving the top down, we also need to subtract the height diff.247height -= b.top - a.top;248}249// Subtract off any area on bottom where A extends past B250if (bb < ab) {251result.push(new goog.math.Rect(a.left, bb, a.width, ab - bb));252height = bb - top;253}254// Subtract any area on left where A extends past B255if (b.left > a.left) {256result.push(new goog.math.Rect(a.left, top, b.left - a.left, height));257}258// Subtract any area on right where A extends past B259if (br < ar) {260result.push(new goog.math.Rect(br, top, ar - br, height));261}262263return result;264};265266267/**268* Computes the difference regions between this rectangle and `rect`. The269* return value is an array of 0 to 4 rectangles defining the remaining regions270* of this rectangle after the other has been subtracted.271* @param {goog.math.IRect} rect A Rectangle.272* @return {!Array<!goog.math.Rect>} An array with 0 to 4 rectangles which273* together define the difference area of rectangle a minus rectangle b.274*/275goog.math.Rect.prototype.difference = function(rect) {276'use strict';277return goog.math.Rect.difference(this, rect);278};279280281/**282* Expand this rectangle to also include the area of the given rectangle.283* @param {goog.math.IRect} rect The other rectangle.284*/285goog.math.Rect.prototype.boundingRect = function(rect) {286'use strict';287// We compute right and bottom before we change left and top below.288var right = Math.max(this.left + this.width, rect.left + rect.width);289var bottom = Math.max(this.top + this.height, rect.top + rect.height);290291this.left = Math.min(this.left, rect.left);292this.top = Math.min(this.top, rect.top);293294this.width = right - this.left;295this.height = bottom - this.top;296};297298299/**300* Returns a new rectangle which completely contains both input rectangles.301* @param {goog.math.IRect} a A rectangle.302* @param {goog.math.IRect} b A rectangle.303* @return {goog.math.Rect} A new bounding rect, or null if either rect is304* null.305*/306goog.math.Rect.boundingRect = function(a, b) {307'use strict';308if (!a || !b) {309return null;310}311312var newRect = new goog.math.Rect(a.left, a.top, a.width, a.height);313newRect.boundingRect(b);314315return newRect;316};317318319/**320* Tests whether this rectangle entirely contains another rectangle or321* coordinate.322*323* @param {goog.math.IRect|goog.math.Coordinate} another The rectangle or324* coordinate to test for containment.325* @return {boolean} Whether this rectangle contains given rectangle or326* coordinate.327*/328goog.math.Rect.prototype.contains = function(another) {329'use strict';330if (another instanceof goog.math.Coordinate) {331return another.x >= this.left && another.x <= this.left + this.width &&332another.y >= this.top && another.y <= this.top + this.height;333} else { // (another instanceof goog.math.IRect)334return this.left <= another.left &&335this.left + this.width >= another.left + another.width &&336this.top <= another.top &&337this.top + this.height >= another.top + another.height;338}339};340341342/**343* @param {!goog.math.Coordinate} point A coordinate.344* @return {number} The squared distance between the point and the closest345* point inside the rectangle. Returns 0 if the point is inside the346* rectangle.347*/348goog.math.Rect.prototype.squaredDistance = function(point) {349'use strict';350var dx = point.x < this.left ?351this.left - point.x :352Math.max(point.x - (this.left + this.width), 0);353var dy = point.y < this.top ? this.top - point.y :354Math.max(point.y - (this.top + this.height), 0);355return dx * dx + dy * dy;356};357358359/**360* @param {!goog.math.Coordinate} point A coordinate.361* @return {number} The distance between the point and the closest point362* inside the rectangle. Returns 0 if the point is inside the rectangle.363*/364goog.math.Rect.prototype.distance = function(point) {365'use strict';366return Math.sqrt(this.squaredDistance(point));367};368369370/**371* @return {!goog.math.Size} The size of this rectangle.372*/373goog.math.Rect.prototype.getSize = function() {374'use strict';375return new goog.math.Size(this.width, this.height);376};377378379/**380* @return {!goog.math.Coordinate} A new coordinate for the top-left corner of381* the rectangle.382*/383goog.math.Rect.prototype.getTopLeft = function() {384'use strict';385return new goog.math.Coordinate(this.left, this.top);386};387388389/**390* @return {!goog.math.Coordinate} A new coordinate for the center of the391* rectangle.392*/393goog.math.Rect.prototype.getCenter = function() {394'use strict';395return new goog.math.Coordinate(396this.left + this.width / 2, this.top + this.height / 2);397};398399400/**401* @return {!goog.math.Coordinate} A new coordinate for the bottom-right corner402* of the rectangle.403*/404goog.math.Rect.prototype.getBottomRight = function() {405'use strict';406return new goog.math.Coordinate(407this.left + this.width, this.top + this.height);408};409410411/**412* Rounds the fields to the next larger integer values.413* @return {!goog.math.Rect} This rectangle with ceil'd fields.414*/415goog.math.Rect.prototype.ceil = function() {416'use strict';417this.left = Math.ceil(this.left);418this.top = Math.ceil(this.top);419this.width = Math.ceil(this.width);420this.height = Math.ceil(this.height);421return this;422};423424425/**426* Rounds the fields to the next smaller integer values.427* @return {!goog.math.Rect} This rectangle with floored fields.428*/429goog.math.Rect.prototype.floor = function() {430'use strict';431this.left = Math.floor(this.left);432this.top = Math.floor(this.top);433this.width = Math.floor(this.width);434this.height = Math.floor(this.height);435return this;436};437438439/**440* Rounds the fields to nearest integer values.441* @return {!goog.math.Rect} This rectangle with rounded fields.442*/443goog.math.Rect.prototype.round = function() {444'use strict';445this.left = Math.round(this.left);446this.top = Math.round(this.top);447this.width = Math.round(this.width);448this.height = Math.round(this.height);449return this;450};451452453/**454* Translates this rectangle by the given offsets. If a455* `goog.math.Coordinate` is given, then the left and top values are456* translated by the coordinate's x and y values. Otherwise, left and top are457* translated by `tx` and `opt_ty` respectively.458* @param {number|goog.math.Coordinate} tx The value to translate left by or the459* the coordinate to translate this rect by.460* @param {number=} opt_ty The value to translate top by.461* @return {!goog.math.Rect} This rectangle after translating.462*/463goog.math.Rect.prototype.translate = function(tx, opt_ty) {464'use strict';465if (tx instanceof goog.math.Coordinate) {466this.left += tx.x;467this.top += tx.y;468} else {469this.left += goog.asserts.assertNumber(tx);470if (typeof opt_ty === 'number') {471this.top += opt_ty;472}473}474return this;475};476477478/**479* Scales this rectangle by the given scale factors. The left and width values480* are scaled by `sx` and the top and height values are scaled by481* `opt_sy`. If `opt_sy` is not given, then all fields are scaled482* by `sx`.483* @param {number} sx The scale factor to use for the x dimension.484* @param {number=} opt_sy The scale factor to use for the y dimension.485* @return {!goog.math.Rect} This rectangle after scaling.486*/487goog.math.Rect.prototype.scale = function(sx, opt_sy) {488'use strict';489var sy = (typeof opt_sy === 'number') ? opt_sy : sx;490this.left *= sx;491this.width *= sx;492this.top *= sy;493this.height *= sy;494return this;495};496497498