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