michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #ifndef MOZILLA_GFX_BASERECT_H_ michael@0: #define MOZILLA_GFX_BASERECT_H_ michael@0: michael@0: #include michael@0: #include michael@0: michael@0: #include "mozilla/Assertions.h" michael@0: #include "mozilla/FloatingPoint.h" michael@0: #include "mozilla/TypeTraits.h" michael@0: michael@0: namespace mozilla { michael@0: namespace gfx { michael@0: michael@0: /** michael@0: * Rectangles have two interpretations: a set of (zero-size) points, michael@0: * and a rectangular area of the plane. Most rectangle operations behave michael@0: * the same no matter what interpretation is being used, but some operations michael@0: * differ: michael@0: * -- Equality tests behave differently. When a rectangle represents an area, michael@0: * all zero-width and zero-height rectangles are equal to each other since they michael@0: * represent the empty area. But when a rectangle represents a set of michael@0: * mathematical points, zero-width and zero-height rectangles can be unequal. michael@0: * -- The union operation can behave differently. When rectangles represent michael@0: * areas, taking the union of a zero-width or zero-height rectangle with michael@0: * another rectangle can just ignore the empty rectangle. But when rectangles michael@0: * represent sets of mathematical points, we may need to extend the latter michael@0: * rectangle to include the points of a zero-width or zero-height rectangle. michael@0: * michael@0: * To ensure that these interpretations are explicitly disambiguated, we michael@0: * deny access to the == and != operators and require use of IsEqualEdges and michael@0: * IsEqualInterior instead. Similarly we provide separate Union and UnionEdges michael@0: * methods. michael@0: * michael@0: * Do not use this class directly. Subclass it, pass that subclass as the michael@0: * Sub parameter, and only use that subclass. michael@0: */ michael@0: template michael@0: struct BaseRect { michael@0: T x, y, width, height; michael@0: michael@0: // Constructors michael@0: BaseRect() : x(0), y(0), width(0), height(0) {} michael@0: BaseRect(const Point& aOrigin, const SizeT &aSize) : michael@0: x(aOrigin.x), y(aOrigin.y), width(aSize.width), height(aSize.height) michael@0: { michael@0: } michael@0: BaseRect(T aX, T aY, T aWidth, T aHeight) : michael@0: x(aX), y(aY), width(aWidth), height(aHeight) michael@0: { michael@0: } michael@0: michael@0: // Emptiness. An empty rect is one that has no area, i.e. its height or width michael@0: // is <= 0 michael@0: bool IsEmpty() const { return height <= 0 || width <= 0; } michael@0: void SetEmpty() { width = height = 0; } michael@0: michael@0: // "Finite" means not inf and not NaN michael@0: bool IsFinite() const michael@0: { michael@0: typedef typename mozilla::Conditional::value, float, double>::Type FloatType; michael@0: return (mozilla::IsFinite(FloatType(x)) && michael@0: mozilla::IsFinite(FloatType(y)) && michael@0: mozilla::IsFinite(FloatType(width)) && michael@0: mozilla::IsFinite(FloatType(height))); michael@0: } michael@0: michael@0: // Returns true if this rectangle contains the interior of aRect. Always michael@0: // returns true if aRect is empty, and always returns false is aRect is michael@0: // nonempty but this rect is empty. michael@0: bool Contains(const Sub& aRect) const michael@0: { michael@0: return aRect.IsEmpty() || michael@0: (x <= aRect.x && aRect.XMost() <= XMost() && michael@0: y <= aRect.y && aRect.YMost() <= YMost()); michael@0: } michael@0: // Returns true if this rectangle contains the rectangle (aX,aY,1,1). michael@0: bool Contains(T aX, T aY) const michael@0: { michael@0: return x <= aX && aX + 1 <= XMost() && michael@0: y <= aY && aY + 1 <= YMost(); michael@0: } michael@0: // Returns true if this rectangle contains the rectangle (aPoint.x,aPoint.y,1,1). michael@0: bool Contains(const Point& aPoint) const { return Contains(aPoint.x, aPoint.y); } michael@0: michael@0: // Intersection. Returns TRUE if the receiver's area has non-empty michael@0: // intersection with aRect's area, and FALSE otherwise. michael@0: // Always returns false if aRect is empty or 'this' is empty. michael@0: bool Intersects(const Sub& aRect) const michael@0: { michael@0: return x < aRect.XMost() && aRect.x < XMost() && michael@0: y < aRect.YMost() && aRect.y < YMost(); michael@0: } michael@0: // Returns the rectangle containing the intersection of the points michael@0: // (including edges) of *this and aRect. If there are no points in that michael@0: // intersection, returns an empty rectangle with x/y set to the std::max of the x/y michael@0: // of *this and aRect. michael@0: Sub Intersect(const Sub& aRect) const michael@0: { michael@0: Sub result; michael@0: result.x = std::max(x, aRect.x); michael@0: result.y = std::max(y, aRect.y); michael@0: result.width = std::min(XMost(), aRect.XMost()) - result.x; michael@0: result.height = std::min(YMost(), aRect.YMost()) - result.y; michael@0: if (result.width < 0 || result.height < 0) { michael@0: result.SizeTo(0, 0); michael@0: } michael@0: return result; michael@0: } michael@0: // Sets *this to be the rectangle containing the intersection of the points michael@0: // (including edges) of *this and aRect. If there are no points in that michael@0: // intersection, sets *this to be an empty rectangle with x/y set to the std::max michael@0: // of the x/y of *this and aRect. michael@0: // michael@0: // 'this' can be the same object as either aRect1 or aRect2 michael@0: bool IntersectRect(const Sub& aRect1, const Sub& aRect2) michael@0: { michael@0: *static_cast(this) = aRect1.Intersect(aRect2); michael@0: return !IsEmpty(); michael@0: } michael@0: michael@0: // Returns the smallest rectangle that contains both the area of both michael@0: // this and aRect2. michael@0: // Thus, empty input rectangles are ignored. michael@0: // If both rectangles are empty, returns this. michael@0: Sub Union(const Sub& aRect) const michael@0: { michael@0: if (IsEmpty()) { michael@0: return aRect; michael@0: } else if (aRect.IsEmpty()) { michael@0: return *static_cast(this); michael@0: } else { michael@0: return UnionEdges(aRect); michael@0: } michael@0: } michael@0: // Returns the smallest rectangle that contains both the points (including michael@0: // edges) of both aRect1 and aRect2. michael@0: // Thus, empty input rectangles are allowed to affect the result. michael@0: Sub UnionEdges(const Sub& aRect) const michael@0: { michael@0: Sub result; michael@0: result.x = std::min(x, aRect.x); michael@0: result.y = std::min(y, aRect.y); michael@0: result.width = std::max(XMost(), aRect.XMost()) - result.x; michael@0: result.height = std::max(YMost(), aRect.YMost()) - result.y; michael@0: return result; michael@0: } michael@0: // Computes the smallest rectangle that contains both the area of both michael@0: // aRect1 and aRect2, and fills 'this' with the result. michael@0: // Thus, empty input rectangles are ignored. michael@0: // If both rectangles are empty, sets 'this' to aRect2. michael@0: // michael@0: // 'this' can be the same object as either aRect1 or aRect2 michael@0: void UnionRect(const Sub& aRect1, const Sub& aRect2) michael@0: { michael@0: *static_cast(this) = aRect1.Union(aRect2); michael@0: } michael@0: michael@0: // Computes the smallest rectangle that contains both the points (including michael@0: // edges) of both aRect1 and aRect2. michael@0: // Thus, empty input rectangles are allowed to affect the result. michael@0: // michael@0: // 'this' can be the same object as either aRect1 or aRect2 michael@0: void UnionRectEdges(const Sub& aRect1, const Sub& aRect2) michael@0: { michael@0: *static_cast(this) = aRect1.UnionEdges(aRect2); michael@0: } michael@0: michael@0: void SetRect(T aX, T aY, T aWidth, T aHeight) michael@0: { michael@0: x = aX; y = aY; width = aWidth; height = aHeight; michael@0: } michael@0: void SetRect(const Point& aPt, const SizeT& aSize) michael@0: { michael@0: SetRect(aPt.x, aPt.y, aSize.width, aSize.height); michael@0: } michael@0: void MoveTo(T aX, T aY) { x = aX; y = aY; } michael@0: void MoveTo(const Point& aPoint) { x = aPoint.x; y = aPoint.y; } michael@0: void MoveBy(T aDx, T aDy) { x += aDx; y += aDy; } michael@0: void MoveBy(const Point& aPoint) { x += aPoint.x; y += aPoint.y; } michael@0: void SizeTo(T aWidth, T aHeight) { width = aWidth; height = aHeight; } michael@0: void SizeTo(const SizeT& aSize) { width = aSize.width; height = aSize.height; } michael@0: michael@0: void Inflate(T aD) { Inflate(aD, aD); } michael@0: void Inflate(T aDx, T aDy) michael@0: { michael@0: x -= aDx; michael@0: y -= aDy; michael@0: width += 2 * aDx; michael@0: height += 2 * aDy; michael@0: } michael@0: void Inflate(const MarginT& aMargin) michael@0: { michael@0: x -= aMargin.left; michael@0: y -= aMargin.top; michael@0: width += aMargin.LeftRight(); michael@0: height += aMargin.TopBottom(); michael@0: } michael@0: void Inflate(const SizeT& aSize) { Inflate(aSize.width, aSize.height); } michael@0: michael@0: void Deflate(T aD) { Deflate(aD, aD); } michael@0: void Deflate(T aDx, T aDy) michael@0: { michael@0: x += aDx; michael@0: y += aDy; michael@0: width = std::max(T(0), width - 2 * aDx); michael@0: height = std::max(T(0), height - 2 * aDy); michael@0: } michael@0: void Deflate(const MarginT& aMargin) michael@0: { michael@0: x += aMargin.left; michael@0: y += aMargin.top; michael@0: width = std::max(T(0), width - aMargin.LeftRight()); michael@0: height = std::max(T(0), height - aMargin.TopBottom()); michael@0: } michael@0: void Deflate(const SizeT& aSize) { Deflate(aSize.width, aSize.height); } michael@0: michael@0: // Return true if the rectangles contain the same set of points, including michael@0: // points on the edges. michael@0: // Use when we care about the exact x/y/width/height values being michael@0: // equal (i.e. we care about differences in empty rectangles). michael@0: bool IsEqualEdges(const Sub& aRect) const michael@0: { michael@0: return x == aRect.x && y == aRect.y && michael@0: width == aRect.width && height == aRect.height; michael@0: } michael@0: // Return true if the rectangles contain the same area of the plane. michael@0: // Use when we do not care about differences in empty rectangles. michael@0: bool IsEqualInterior(const Sub& aRect) const michael@0: { michael@0: return IsEqualEdges(aRect) || (IsEmpty() && aRect.IsEmpty()); michael@0: } michael@0: michael@0: Sub operator+(const Point& aPoint) const michael@0: { michael@0: return Sub(x + aPoint.x, y + aPoint.y, width, height); michael@0: } michael@0: Sub operator-(const Point& aPoint) const michael@0: { michael@0: return Sub(x - aPoint.x, y - aPoint.y, width, height); michael@0: } michael@0: Sub& operator+=(const Point& aPoint) michael@0: { michael@0: MoveBy(aPoint); michael@0: return *static_cast(this); michael@0: } michael@0: Sub& operator-=(const Point& aPoint) michael@0: { michael@0: MoveBy(-aPoint); michael@0: return *static_cast(this); michael@0: } michael@0: michael@0: // Find difference as a Margin michael@0: MarginT operator-(const Sub& aRect) const michael@0: { michael@0: return MarginT(aRect.y - y, michael@0: XMost() - aRect.XMost(), michael@0: YMost() - aRect.YMost(), michael@0: aRect.x - x); michael@0: } michael@0: michael@0: // Helpers for accessing the vertices michael@0: Point TopLeft() const { return Point(x, y); } michael@0: Point TopRight() const { return Point(XMost(), y); } michael@0: Point BottomLeft() const { return Point(x, YMost()); } michael@0: Point BottomRight() const { return Point(XMost(), YMost()); } michael@0: Point Center() const { return Point(x, y) + Point(width, height)/2; } michael@0: SizeT Size() const { return SizeT(width, height); } michael@0: michael@0: // Helper methods for computing the extents michael@0: T X() const { return x; } michael@0: T Y() const { return y; } michael@0: T Width() const { return width; } michael@0: T Height() const { return height; } michael@0: T XMost() const { return x + width; } michael@0: T YMost() const { return y + height; } michael@0: michael@0: // Moves one edge of the rect without moving the opposite edge. michael@0: void SetLeftEdge(T aX) { michael@0: MOZ_ASSERT(aX <= XMost()); michael@0: width = XMost() - aX; michael@0: x = aX; michael@0: } michael@0: void SetRightEdge(T aXMost) { michael@0: MOZ_ASSERT(aXMost >= x); michael@0: width = aXMost - x; michael@0: } michael@0: void SetTopEdge(T aY) { michael@0: MOZ_ASSERT(aY <= YMost()); michael@0: height = YMost() - aY; michael@0: y = aY; michael@0: } michael@0: void SetBottomEdge(T aYMost) { michael@0: MOZ_ASSERT(aYMost >= y); michael@0: height = aYMost - y; michael@0: } michael@0: michael@0: // Round the rectangle edges to integer coordinates, such that the rounded michael@0: // rectangle has the same set of pixel centers as the original rectangle. michael@0: // Edges at offset 0.5 round up. michael@0: // Suitable for most places where integral device coordinates michael@0: // are needed, but note that any translation should be applied first to michael@0: // avoid pixel rounding errors. michael@0: // Note that this is *not* rounding to nearest integer if the values are negative. michael@0: // They are always rounding as floor(n + 0.5). michael@0: // See https://bugzilla.mozilla.org/show_bug.cgi?id=410748#c14 michael@0: // If you need similar method which is using NS_round(), you should create michael@0: // new |RoundAwayFromZero()| method. michael@0: void Round() michael@0: { michael@0: T x0 = static_cast(floor(T(X()) + 0.5)); michael@0: T y0 = static_cast(floor(T(Y()) + 0.5)); michael@0: T x1 = static_cast(floor(T(XMost()) + 0.5)); michael@0: T y1 = static_cast(floor(T(YMost()) + 0.5)); michael@0: michael@0: x = x0; michael@0: y = y0; michael@0: michael@0: width = x1 - x0; michael@0: height = y1 - y0; michael@0: } michael@0: michael@0: // Snap the rectangle edges to integer coordinates, such that the michael@0: // original rectangle contains the resulting rectangle. michael@0: void RoundIn() michael@0: { michael@0: T x0 = static_cast(ceil(T(X()))); michael@0: T y0 = static_cast(ceil(T(Y()))); michael@0: T x1 = static_cast(floor(T(XMost()))); michael@0: T y1 = static_cast(floor(T(YMost()))); michael@0: michael@0: x = x0; michael@0: y = y0; michael@0: michael@0: width = x1 - x0; michael@0: height = y1 - y0; michael@0: } michael@0: michael@0: // Snap the rectangle edges to integer coordinates, such that the michael@0: // resulting rectangle contains the original rectangle. michael@0: void RoundOut() michael@0: { michael@0: T x0 = static_cast(floor(T(X()))); michael@0: T y0 = static_cast(floor(T(Y()))); michael@0: T x1 = static_cast(ceil(T(XMost()))); michael@0: T y1 = static_cast(ceil(T(YMost()))); michael@0: michael@0: x = x0; michael@0: y = y0; michael@0: michael@0: width = x1 - x0; michael@0: height = y1 - y0; michael@0: } michael@0: michael@0: // Scale 'this' by aScale without doing any rounding. michael@0: void Scale(T aScale) { Scale(aScale, aScale); } michael@0: // Scale 'this' by aXScale and aYScale, without doing any rounding. michael@0: void Scale(T aXScale, T aYScale) michael@0: { michael@0: T right = XMost() * aXScale; michael@0: T bottom = YMost() * aYScale; michael@0: x = x * aXScale; michael@0: y = y * aYScale; michael@0: width = right - x; michael@0: height = bottom - y; michael@0: } michael@0: // Scale 'this' by aScale, converting coordinates to integers so that the result is michael@0: // the smallest integer-coordinate rectangle containing the unrounded result. michael@0: // Note: this can turn an empty rectangle into a non-empty rectangle michael@0: void ScaleRoundOut(double aScale) { ScaleRoundOut(aScale, aScale); } michael@0: // Scale 'this' by aXScale and aYScale, converting coordinates to integers so michael@0: // that the result is the smallest integer-coordinate rectangle containing the michael@0: // unrounded result. michael@0: // Note: this can turn an empty rectangle into a non-empty rectangle michael@0: void ScaleRoundOut(double aXScale, double aYScale) michael@0: { michael@0: T right = static_cast(ceil(double(XMost()) * aXScale)); michael@0: T bottom = static_cast(ceil(double(YMost()) * aYScale)); michael@0: x = static_cast(floor(double(x) * aXScale)); michael@0: y = static_cast(floor(double(y) * aYScale)); michael@0: width = right - x; michael@0: height = bottom - y; michael@0: } michael@0: // Scale 'this' by aScale, converting coordinates to integers so that the result is michael@0: // the largest integer-coordinate rectangle contained by the unrounded result. michael@0: void ScaleRoundIn(double aScale) { ScaleRoundIn(aScale, aScale); } michael@0: // Scale 'this' by aXScale and aYScale, converting coordinates to integers so michael@0: // that the result is the largest integer-coordinate rectangle contained by the michael@0: // unrounded result. michael@0: void ScaleRoundIn(double aXScale, double aYScale) michael@0: { michael@0: T right = static_cast(floor(double(XMost()) * aXScale)); michael@0: T bottom = static_cast(floor(double(YMost()) * aYScale)); michael@0: x = static_cast(ceil(double(x) * aXScale)); michael@0: y = static_cast(ceil(double(y) * aYScale)); michael@0: width = std::max(0, right - x); michael@0: height = std::max(0, bottom - y); michael@0: } michael@0: // Scale 'this' by 1/aScale, converting coordinates to integers so that the result is michael@0: // the smallest integer-coordinate rectangle containing the unrounded result. michael@0: // Note: this can turn an empty rectangle into a non-empty rectangle michael@0: void ScaleInverseRoundOut(double aScale) { ScaleInverseRoundOut(aScale, aScale); } michael@0: // Scale 'this' by 1/aXScale and 1/aYScale, converting coordinates to integers so michael@0: // that the result is the smallest integer-coordinate rectangle containing the michael@0: // unrounded result. michael@0: // Note: this can turn an empty rectangle into a non-empty rectangle michael@0: void ScaleInverseRoundOut(double aXScale, double aYScale) michael@0: { michael@0: T right = static_cast(ceil(double(XMost()) / aXScale)); michael@0: T bottom = static_cast(ceil(double(YMost()) / aYScale)); michael@0: x = static_cast(floor(double(x) / aXScale)); michael@0: y = static_cast(floor(double(y) / aYScale)); michael@0: width = right - x; michael@0: height = bottom - y; michael@0: } michael@0: // Scale 'this' by 1/aScale, converting coordinates to integers so that the result is michael@0: // the largest integer-coordinate rectangle contained by the unrounded result. michael@0: void ScaleInverseRoundIn(double aScale) { ScaleInverseRoundIn(aScale, aScale); } michael@0: // Scale 'this' by 1/aXScale and 1/aYScale, converting coordinates to integers so michael@0: // that the result is the largest integer-coordinate rectangle contained by the michael@0: // unrounded result. michael@0: void ScaleInverseRoundIn(double aXScale, double aYScale) michael@0: { michael@0: T right = static_cast(floor(double(XMost()) / aXScale)); michael@0: T bottom = static_cast(floor(double(YMost()) / aYScale)); michael@0: x = static_cast(ceil(double(x) / aXScale)); michael@0: y = static_cast(ceil(double(y) / aYScale)); michael@0: width = std::max(0, right - x); michael@0: height = std::max(0, bottom - y); michael@0: } michael@0: michael@0: /** michael@0: * Clamp aPoint to this rectangle. It is allowed to end up on any michael@0: * edge of the rectangle. michael@0: */ michael@0: Point ClampPoint(const Point& aPoint) const michael@0: { michael@0: return Point(std::max(x, std::min(XMost(), aPoint.x)), michael@0: std::max(y, std::min(YMost(), aPoint.y))); michael@0: } michael@0: michael@0: /** michael@0: * Clamp this rectangle to be inside aRect. The function returns a copy of michael@0: * this rect after it is forced inside the bounds of aRect. It will attempt to michael@0: * retain the size but will shrink the dimensions that don't fit. michael@0: */ michael@0: Sub ForceInside(const Sub& aRect) const michael@0: { michael@0: Sub rect(std::max(aRect.x, x), michael@0: std::max(aRect.y, y), michael@0: std::min(aRect.width, width), michael@0: std::min(aRect.height, height)); michael@0: rect.x = std::min(rect.XMost(), aRect.XMost()) - rect.width; michael@0: rect.y = std::min(rect.YMost(), aRect.YMost()) - rect.height; michael@0: return rect; michael@0: } michael@0: michael@0: private: michael@0: // Do not use the default operator== or operator!= ! michael@0: // Use IsEqualEdges or IsEqualInterior explicitly. michael@0: bool operator==(const Sub& aRect) const { return false; } michael@0: bool operator!=(const Sub& aRect) const { return false; } michael@0: }; michael@0: michael@0: } michael@0: } michael@0: michael@0: #endif /* MOZILLA_GFX_BASERECT_H_ */