mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-23 04:41:11 +00:00
e7c70b157c
Differential Revision: https://phabricator.services.mozilla.com/D229057
753 lines
27 KiB
C++
753 lines
27 KiB
C++
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
|
/* 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 <algorithm>
|
|
#include <cmath>
|
|
#include <ostream>
|
|
#include <type_traits>
|
|
|
|
#include "mozilla/Assertions.h"
|
|
#include "mozilla/FloatingPoint.h"
|
|
#include "mozilla/gfx/ScaleFactors2D.h"
|
|
#include "Types.h"
|
|
|
|
namespace mozilla::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 <class T, class Sub, class Point, class SizeT, class MarginT>
|
|
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. Zero rect is the one with height and width set to zero. Note
|
|
// that SetEmpty() may change a rectangle that identified as IsEmpty().
|
|
MOZ_ALWAYS_INLINE bool IsZeroArea() const {
|
|
return height == 0 || width == 0;
|
|
}
|
|
MOZ_ALWAYS_INLINE bool IsEmpty() const { return height <= 0 || width <= 0; }
|
|
void SetEmpty() { width = height = 0; }
|
|
|
|
// "Finite" means not inf and not NaN
|
|
bool IsFinite() const {
|
|
using FloatType =
|
|
std::conditional_t<std::is_same_v<T, float>, float, double>;
|
|
return (std::isfinite(FloatType(x)) && std::isfinite(FloatType(y)) &&
|
|
std::isfinite(FloatType(width)) &&
|
|
std::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.
|
|
MOZ_ALWAYS_INLINE bool Contains(T aX, T aY) const {
|
|
return x <= aX && aX < XMost() && y <= aY && aY < YMost();
|
|
}
|
|
MOZ_ALWAYS_INLINE bool ContainsX(T aX) const {
|
|
return x <= aX && aX < XMost();
|
|
}
|
|
MOZ_ALWAYS_INLINE bool ContainsY(T aY) const {
|
|
return 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);
|
|
}
|
|
|
|
// Returns true if this rectangle contains the point, considering points on
|
|
// all edges of the rectangle to be contained (as compared to Contains()
|
|
// which only includes points on the top & left but not bottom & right edges).
|
|
MOZ_ALWAYS_INLINE bool ContainsInclusively(const Point& aPoint) const {
|
|
return x <= aPoint.x && aPoint.x <= XMost() && y <= aPoint.y &&
|
|
aPoint.y <= YMost();
|
|
}
|
|
|
|
// 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.
|
|
//
|
|
// Intersection with an empty Rect may not produce an empty Rect if overflow
|
|
// occurs. e.g. {INT_MIN, 0, 0, 20} Intersect { 5000, 0, 500, 20 } gives:
|
|
// the non-emtpy {5000, 0, 500, 20 } instead of {5000, 0, 0, 0}
|
|
[[nodiscard]] Sub Intersect(const Sub& aRect) const {
|
|
Sub result;
|
|
result.x = std::max<T>(x, aRect.x);
|
|
result.y = std::max<T>(y, aRect.y);
|
|
result.width =
|
|
std::min<T>(x - result.x + width, aRect.x - result.x + aRect.width);
|
|
result.height =
|
|
std::min<T>(y - result.y + height, aRect.y - result.y + aRect.height);
|
|
// See bug 1457110, this function expects to -only- size to 0,0 if the
|
|
// width/height is explicitly negative.
|
|
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}
|
|
[[nodiscard]] Sub SafeIntersect(const Sub& aRect) const {
|
|
Sub result;
|
|
result.x = std::max<T>(x, aRect.x);
|
|
result.y = std::max<T>(y, aRect.y);
|
|
T right = std::min<T>(x + width, aRect.x + aRect.width);
|
|
T bottom = std::min<T>(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
|
|
bool IntersectRect(const Sub& aRect1, const Sub& aRect2) {
|
|
T newX = std::max<T>(aRect1.x, aRect2.x);
|
|
T newY = std::max<T>(aRect1.y, aRect2.y);
|
|
width = std::min<T>(aRect1.x - newX + aRect1.width,
|
|
aRect2.x - newX + aRect2.width);
|
|
height = std::min<T>(aRect1.y - newY + aRect1.height,
|
|
aRect2.y - newY + aRect2.height);
|
|
x = newX;
|
|
y = newY;
|
|
if (width <= 0 || height <= 0) {
|
|
SizeTo(0, 0);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Returns the smallest rectangle that contains both the area of both
|
|
// this and aRect. Thus, empty input rectangles are ignored.
|
|
// Note: if both rectangles are empty, returns aRect.
|
|
// WARNING! This is not safe against overflow, prefer using SafeUnion instead
|
|
// when dealing with int-based rects.
|
|
[[nodiscard]] Sub Union(const Sub& aRect) const {
|
|
if (IsEmpty()) {
|
|
return aRect;
|
|
} else if (aRect.IsEmpty()) {
|
|
return *static_cast<const Sub*>(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.
|
|
// WARNING! This is not safe against overflow, prefer using SafeUnionEdges
|
|
// instead when dealing with int-based rects.
|
|
[[nodiscard]] 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<Sub*>(this) = aRect1.Union(aRect2);
|
|
}
|
|
|
|
void OrWith(const Sub& aRect1) {
|
|
UnionRect(*static_cast<Sub*>(this), aRect1);
|
|
}
|
|
|
|
// 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<Sub*>(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;
|
|
}
|
|
}
|
|
|
|
MOZ_ALWAYS_INLINE void SetRect(T aX, T aY, T aWidth, T aHeight) {
|
|
x = aX;
|
|
y = aY;
|
|
width = aWidth;
|
|
height = aHeight;
|
|
}
|
|
MOZ_ALWAYS_INLINE void SetRectX(T aX, T aWidth) {
|
|
x = aX;
|
|
width = aWidth;
|
|
}
|
|
MOZ_ALWAYS_INLINE void SetRectY(T aY, T aHeight) {
|
|
y = aY;
|
|
height = aHeight;
|
|
}
|
|
MOZ_ALWAYS_INLINE void SetBox(T aX, T aY, T aXMost, T aYMost) {
|
|
x = aX;
|
|
y = aY;
|
|
width = aXMost - aX;
|
|
height = aYMost - aY;
|
|
}
|
|
MOZ_ALWAYS_INLINE void SetNonEmptyBox(T aX, T aY, T aXMost, T aYMost) {
|
|
x = aX;
|
|
y = aY;
|
|
width = std::max(0, aXMost - aX);
|
|
height = std::max(0, aYMost - aY);
|
|
}
|
|
MOZ_ALWAYS_INLINE void SetBoxX(T aX, T aXMost) {
|
|
x = aX;
|
|
width = aXMost - aX;
|
|
}
|
|
MOZ_ALWAYS_INLINE void SetBoxY(T aY, T aYMost) {
|
|
y = aY;
|
|
height = aYMost - aY;
|
|
}
|
|
void SetRect(const Point& aPt, const SizeT& aSize) {
|
|
SetRect(aPt.x, aPt.y, aSize.width, aSize.height);
|
|
}
|
|
MOZ_ALWAYS_INLINE void GetRect(T* aX, T* aY, T* aWidth, T* aHeight) const {
|
|
*aX = x;
|
|
*aY = y;
|
|
*aWidth = width;
|
|
*aHeight = height;
|
|
}
|
|
|
|
MOZ_ALWAYS_INLINE void MoveTo(T aX, T aY) {
|
|
x = aX;
|
|
y = aY;
|
|
}
|
|
MOZ_ALWAYS_INLINE void MoveToX(T aX) { x = aX; }
|
|
MOZ_ALWAYS_INLINE void MoveToY(T aY) { y = aY; }
|
|
MOZ_ALWAYS_INLINE void MoveTo(const Point& aPoint) {
|
|
x = aPoint.x;
|
|
y = aPoint.y;
|
|
}
|
|
MOZ_ALWAYS_INLINE void MoveBy(T aDx, T aDy) {
|
|
x += aDx;
|
|
y += aDy;
|
|
}
|
|
MOZ_ALWAYS_INLINE void MoveByX(T aDx) { x += aDx; }
|
|
MOZ_ALWAYS_INLINE void MoveByY(T aDy) { y += aDy; }
|
|
MOZ_ALWAYS_INLINE void MoveBy(const Point& aPoint) {
|
|
x += aPoint.x;
|
|
y += aPoint.y;
|
|
}
|
|
MOZ_ALWAYS_INLINE void SizeTo(T aWidth, T aHeight) {
|
|
width = aWidth;
|
|
height = aHeight;
|
|
}
|
|
MOZ_ALWAYS_INLINE 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<T>::max();
|
|
x = limit - aDx < x ? limit : x + aDx;
|
|
width = (limit - aDx < x2 ? limit : x2 + aDx) - x;
|
|
} else {
|
|
T limit = std::numeric_limits<T>::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<T>::max();
|
|
y = limit - aDy < y ? limit : y + aDy;
|
|
height = (limit - aDy < y2 ? limit : y2 + aDy) - y;
|
|
} else {
|
|
T limit = std::numeric_limits<T>::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;
|
|
}
|
|
MOZ_ALWAYS_INLINE bool IsEqualRect(T aX, T aY, T aW, T aH) {
|
|
return x == aX && y == aY && width == aW && height == aH;
|
|
}
|
|
MOZ_ALWAYS_INLINE bool IsEqualXY(T aX, T aY) { return x == aX && y == aY; }
|
|
|
|
MOZ_ALWAYS_INLINE bool IsEqualSize(T aW, T aH) {
|
|
return width == aW && height == aH;
|
|
}
|
|
|
|
// 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<Sub*>(this);
|
|
}
|
|
Sub& operator-=(const Point& aPoint) {
|
|
MoveBy(-aPoint);
|
|
return *static_cast<Sub*>(this);
|
|
}
|
|
Sub& operator+=(const SizeT& aSize) {
|
|
width += aSize.width;
|
|
height += aSize.height;
|
|
return *static_cast<Sub*>(this);
|
|
}
|
|
Sub& operator-=(const SizeT& aSize) {
|
|
width -= aSize.width;
|
|
height -= aSize.height;
|
|
return *static_cast<Sub*>(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(Corner aCorner) const {
|
|
switch (aCorner) {
|
|
case eCornerTopLeft:
|
|
return TopLeft();
|
|
case eCornerTopRight:
|
|
return TopRight();
|
|
case eCornerBottomRight:
|
|
return BottomRight();
|
|
case eCornerBottomLeft:
|
|
return BottomLeft();
|
|
}
|
|
MOZ_CRASH("GFX: Incomplete switch");
|
|
}
|
|
Point CCWCorner(mozilla::Side side) const {
|
|
switch (side) {
|
|
case eSideTop:
|
|
return TopLeft();
|
|
case eSideRight:
|
|
return TopRight();
|
|
case eSideBottom:
|
|
return BottomRight();
|
|
case eSideLeft:
|
|
return BottomLeft();
|
|
}
|
|
MOZ_CRASH("GFX: Incomplete switch");
|
|
}
|
|
Point CWCorner(mozilla::Side side) const {
|
|
switch (side) {
|
|
case eSideTop:
|
|
return TopRight();
|
|
case eSideRight:
|
|
return BottomRight();
|
|
case eSideBottom:
|
|
return BottomLeft();
|
|
case eSideLeft:
|
|
return TopLeft();
|
|
}
|
|
MOZ_CRASH("GFX: Incomplete switch");
|
|
}
|
|
Point Center() const { return Point(x, y) + Point(width, height) / 2; }
|
|
SizeT Size() const { return SizeT(width, height); }
|
|
|
|
T Area() const { return width * height; }
|
|
|
|
// Helper methods for computing the extents
|
|
MOZ_ALWAYS_INLINE T X() const { return x; }
|
|
MOZ_ALWAYS_INLINE T Y() const { return y; }
|
|
MOZ_ALWAYS_INLINE T Width() const { return width; }
|
|
MOZ_ALWAYS_INLINE T Height() const { return height; }
|
|
MOZ_ALWAYS_INLINE T XMost() const { return x + width; }
|
|
MOZ_ALWAYS_INLINE T YMost() const { return y + height; }
|
|
|
|
// Set width and height. SizeTo() sets them together.
|
|
MOZ_ALWAYS_INLINE void SetWidth(T aWidth) { width = aWidth; }
|
|
MOZ_ALWAYS_INLINE void SetHeight(T aHeight) { height = aHeight; }
|
|
|
|
// Get the coordinate of the edge on the given side.
|
|
T Edge(mozilla::Side aSide) const {
|
|
switch (aSide) {
|
|
case eSideTop:
|
|
return Y();
|
|
case eSideRight:
|
|
return XMost();
|
|
case eSideBottom:
|
|
return YMost();
|
|
case eSideLeft:
|
|
return X();
|
|
}
|
|
MOZ_CRASH("GFX: Incomplete switch");
|
|
}
|
|
|
|
// Moves one edge of the rect without moving the opposite edge.
|
|
void SetLeftEdge(T aX) {
|
|
width = XMost() - aX;
|
|
x = aX;
|
|
}
|
|
void SetRightEdge(T aXMost) { width = aXMost - x; }
|
|
void SetTopEdge(T aY) {
|
|
height = YMost() - aY;
|
|
y = aY;
|
|
}
|
|
void SetBottomEdge(T aYMost) { height = aYMost - y; }
|
|
void Swap() {
|
|
std::swap(x, y);
|
|
std::swap(width, height);
|
|
}
|
|
|
|
// 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<T>(std::floor(T(X()) + 0.5f));
|
|
T y0 = static_cast<T>(std::floor(T(Y()) + 0.5f));
|
|
T x1 = static_cast<T>(std::floor(T(XMost()) + 0.5f));
|
|
T y1 = static_cast<T>(std::floor(T(YMost()) + 0.5f));
|
|
|
|
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<T>(std::ceil(T(X())));
|
|
T y0 = static_cast<T>(std::ceil(T(Y())));
|
|
T x1 = static_cast<T>(std::floor(T(XMost())));
|
|
T y1 = static_cast<T>(std::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<T>(std::floor(T(X())));
|
|
T y0 = static_cast<T>(std::floor(T(Y())));
|
|
T x1 = static_cast<T>(std::ceil(T(XMost())));
|
|
T y1 = static_cast<T>(std::ceil(T(YMost())));
|
|
|
|
x = x0;
|
|
y = y0;
|
|
|
|
width = x1 - x0;
|
|
height = y1 - y0;
|
|
}
|
|
|
|
// Scale 'this' by aScale.xScale and aScale.yScale without doing any rounding.
|
|
template <class Src, class Dst>
|
|
void Scale(const BaseScaleFactors2D<Src, Dst, T>& aScale) {
|
|
Scale(aScale.xScale, aScale.yScale);
|
|
}
|
|
// 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) {
|
|
x = x * aXScale;
|
|
y = y * aYScale;
|
|
width = width * aXScale;
|
|
height = height * aYScale;
|
|
}
|
|
// 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<T>(ceil(double(XMost()) * aXScale));
|
|
T bottom = static_cast<T>(ceil(double(YMost()) * aYScale));
|
|
x = static_cast<T>(floor(double(x) * aXScale));
|
|
y = static_cast<T>(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<T>(floor(double(XMost()) * aXScale));
|
|
T bottom = static_cast<T>(floor(double(YMost()) * aYScale));
|
|
x = static_cast<T>(ceil(double(x) * aXScale));
|
|
y = static_cast<T>(ceil(double(y) * aYScale));
|
|
width = std::max<T>(0, right - x);
|
|
height = std::max<T>(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<T>(ceil(double(XMost()) / aXScale));
|
|
T bottom = static_cast<T>(ceil(double(YMost()) / aYScale));
|
|
x = static_cast<T>(floor(double(x) / aXScale));
|
|
y = static_cast<T>(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<T>(floor(double(XMost()) / aXScale));
|
|
T bottom = static_cast<T>(floor(double(YMost()) / aYScale));
|
|
x = static_cast<T>(ceil(double(x) / aXScale));
|
|
y = static_cast<T>(ceil(double(y) / aYScale));
|
|
width = std::max<T>(0, right - x);
|
|
height = std::max<T>(0, bottom - y);
|
|
}
|
|
|
|
/**
|
|
* Clamp aPoint to this rectangle. It is allowed to end up on any
|
|
* edge of the rectangle.
|
|
* Return the rectangle as a point if the rectangle is empty.
|
|
*/
|
|
[[nodiscard]] Point ClampPoint(const Point& aPoint) const {
|
|
using Coord = decltype(aPoint.x);
|
|
return {std::max(Coord(x), std::min(Coord(XMost()), aPoint.x)),
|
|
std::max(Coord(y), std::min(Coord(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.
|
|
*/
|
|
[[nodiscard]] 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;
|
|
}
|
|
|
|
// Returns the largest rectangle that can be represented with 32-bit
|
|
// signed integers, centered around a point at 0,0. As BaseRect's represent
|
|
// the dimensions as a top-left point with a width and height, the width
|
|
// and height will be the largest positive 32-bit value. The top-left
|
|
// position coordinate is divided by two to center the rectangle around a
|
|
// point at 0,0.
|
|
static Sub MaxIntRect() {
|
|
return Sub(static_cast<T>(-std::numeric_limits<int32_t>::max() * 0.5),
|
|
static_cast<T>(-std::numeric_limits<int32_t>::max() * 0.5),
|
|
static_cast<T>(std::numeric_limits<int32_t>::max()),
|
|
static_cast<T>(std::numeric_limits<int32_t>::max()));
|
|
};
|
|
|
|
// Returns a point representing the distance, along each dimension, of the
|
|
// given point from this rectangle. The distance along a dimension is defined
|
|
// as zero if the point is within the bounds of the rectangle in that
|
|
// dimension; otherwise, it's the distance to the closer endpoint of the
|
|
// rectangle in that dimension.
|
|
Point DistanceTo(const Point& aPoint) const {
|
|
return {DistanceFromInterval(aPoint.x, x, XMost()),
|
|
DistanceFromInterval(aPoint.y, y, YMost())};
|
|
}
|
|
|
|
friend std::ostream& operator<<(
|
|
std::ostream& stream,
|
|
const BaseRect<T, Sub, Point, SizeT, MarginT>& aRect) {
|
|
return stream << "(x=" << aRect.x << ", y=" << aRect.y
|
|
<< ", w=" << aRect.width << ", h=" << 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; }
|
|
|
|
// Helper function for DistanceTo() that computes the distance of a
|
|
// coordinate along one dimension from an interval in that dimension.
|
|
static T DistanceFromInterval(T aCoord, T aIntervalStart, T aIntervalEnd) {
|
|
if (aCoord < aIntervalStart) {
|
|
return aIntervalStart - aCoord;
|
|
}
|
|
if (aCoord > aIntervalEnd) {
|
|
return aCoord - aIntervalEnd;
|
|
}
|
|
return 0;
|
|
}
|
|
};
|
|
|
|
} // namespace mozilla::gfx
|
|
|
|
#endif /* MOZILLA_GFX_BASERECT_H_ */
|