tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

BaseRect.h (28064B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #ifndef MOZILLA_GFX_BASERECT_H_
      8 #define MOZILLA_GFX_BASERECT_H_
      9 
     10 #include <algorithm>
     11 #include <cmath>
     12 #include <ostream>
     13 #include <type_traits>
     14 
     15 #include "mozilla/Assertions.h"
     16 #include "mozilla/Saturate.h"
     17 #include "mozilla/gfx/ScaleFactors2D.h"
     18 #include "Types.h"
     19 
     20 namespace mozilla::gfx {
     21 
     22 /**
     23 * Rectangles have two interpretations: a set of (zero-size) points,
     24 * and a rectangular area of the plane. Most rectangle operations behave
     25 * the same no matter what interpretation is being used, but some operations
     26 * differ:
     27 * -- Equality tests behave differently. When a rectangle represents an area,
     28 * all zero-width and zero-height rectangles are equal to each other since they
     29 * represent the empty area. But when a rectangle represents a set of
     30 * mathematical points, zero-width and zero-height rectangles can be unequal.
     31 * -- The union operation can behave differently. When rectangles represent
     32 * areas, taking the union of a zero-width or zero-height rectangle with
     33 * another rectangle can just ignore the empty rectangle. But when rectangles
     34 * represent sets of mathematical points, we may need to extend the latter
     35 * rectangle to include the points of a zero-width or zero-height rectangle.
     36 *
     37 * To ensure that these interpretations are explicitly disambiguated, we
     38 * deny access to the == and != operators and require use of IsEqualEdges and
     39 * IsEqualInterior instead. Similarly we provide separate Union and UnionEdges
     40 * methods.
     41 *
     42 * Do not use this class directly. Subclass it, pass that subclass as the
     43 * Sub parameter, and only use that subclass.
     44 */
     45 template <class T, class Sub, class Point, class SizeT, class MarginT>
     46 struct BaseRect {
     47  T x, y, width, height;
     48 
     49  // Constructors
     50  BaseRect() : x(0), y(0), width(0), height(0) {}
     51  BaseRect(const Point& aOrigin, const SizeT& aSize)
     52      : x(aOrigin.x), y(aOrigin.y), width(aSize.width), height(aSize.height) {}
     53  BaseRect(T aX, T aY, T aWidth, T aHeight)
     54      : x(aX), y(aY), width(aWidth), height(aHeight) {}
     55 
     56  // Emptiness. An empty rect is one that has no area, i.e. its height or width
     57  // is <= 0.  Zero rect is the one with height and width set to zero.  Note
     58  // that SetEmpty() may change a rectangle that identified as IsEmpty().
     59  MOZ_ALWAYS_INLINE bool IsZeroArea() const {
     60    return height == 0 || width == 0;
     61  }
     62  MOZ_ALWAYS_INLINE bool IsEmpty() const { return height <= 0 || width <= 0; }
     63  void SetEmpty() { width = height = 0; }
     64 
     65  // "Finite" means not inf and not NaN
     66  bool IsFinite() const {
     67    using FloatType =
     68        std::conditional_t<std::is_same_v<T, float>, float, double>;
     69    return (std::isfinite(FloatType(x)) && std::isfinite(FloatType(y)) &&
     70            std::isfinite(FloatType(width)) &&
     71            std::isfinite(FloatType(height)));
     72  }
     73 
     74  // Returns true if this rectangle contains the interior of aRect. Always
     75  // returns true if aRect is empty, and always returns false is aRect is
     76  // nonempty but this rect is empty.
     77  bool Contains(const Sub& aRect) const {
     78    return aRect.IsEmpty() || (x <= aRect.x && aRect.XMost() <= XMost() &&
     79                               y <= aRect.y && aRect.YMost() <= YMost());
     80  }
     81  // Returns true if this rectangle contains the point. Points are considered
     82  // in the rectangle if they are on the left or top edge, but outside if they
     83  // are on the right or bottom edge.
     84  MOZ_ALWAYS_INLINE bool Contains(T aX, T aY) const {
     85    return x <= aX && aX < XMost() && y <= aY && aY < YMost();
     86  }
     87  MOZ_ALWAYS_INLINE bool ContainsX(T aX) const {
     88    return x <= aX && aX < XMost();
     89  }
     90  MOZ_ALWAYS_INLINE bool ContainsY(T aY) const {
     91    return y <= aY && aY < YMost();
     92  }
     93  // Returns true if this rectangle contains the point. Points are considered
     94  // in the rectangle if they are on the left or top edge, but outside if they
     95  // are on the right or bottom edge.
     96  bool Contains(const Point& aPoint) const {
     97    return Contains(aPoint.x, aPoint.y);
     98  }
     99 
    100  // Returns true if this rectangle contains the point, considering points on
    101  // all edges of the rectangle to be contained (as compared to Contains()
    102  // which only includes points on the top & left but not bottom & right edges).
    103  MOZ_ALWAYS_INLINE bool ContainsInclusively(const Point& aPoint) const {
    104    return x <= aPoint.x && aPoint.x <= XMost() && y <= aPoint.y &&
    105           aPoint.y <= YMost();
    106  }
    107 
    108  // Intersection. Returns TRUE if the receiver's area has non-empty
    109  // intersection with aRect's area, and FALSE otherwise.
    110  // Always returns false if aRect is empty or 'this' is empty.
    111  bool Intersects(const Sub& aRect) const {
    112    return !IsEmpty() && !aRect.IsEmpty() && x < aRect.XMost() &&
    113           aRect.x < XMost() && y < aRect.YMost() && aRect.y < YMost();
    114  }
    115  // Returns the rectangle containing the intersection of the points
    116  // (including edges) of *this and aRect. If there are no points in that
    117  // intersection, returns an empty rectangle with x/y set to the std::max of
    118  // the x/y of *this and aRect.
    119  //
    120  // Intersection with an empty Rect may not produce an empty Rect if overflow
    121  // occurs. e.g. {INT_MIN, 0, 0, 20} Intersect { 5000, 0, 500, 20 } gives:
    122  // the non-emtpy {5000, 0, 500, 20 } instead of {5000, 0, 0, 0}
    123  [[nodiscard]] Sub Intersect(const Sub& aRect) const {
    124    Sub result;
    125    result.x = std::max<T>(x, aRect.x);
    126    result.y = std::max<T>(y, aRect.y);
    127    result.width =
    128        std::min<T>(x - result.x + width, aRect.x - result.x + aRect.width);
    129    result.height =
    130        std::min<T>(y - result.y + height, aRect.y - result.y + aRect.height);
    131    // See bug 1457110, this function expects to -only- size to 0,0 if the
    132    // width/height is explicitly negative.
    133    if (result.width < 0 || result.height < 0) {
    134      result.SizeTo(0, 0);
    135    }
    136    return result;
    137  }
    138 
    139  // Gives the same results as Intersect() but handles integer overflow
    140  // better. This comes at a tiny cost in performance.
    141  // e.g. {INT_MIN, 0, 0, 20} Intersect { 5000, 0, 500, 20 } gives:
    142  // {5000, 0, 0, 0}
    143  [[nodiscard]] Sub SafeIntersect(const Sub& aRect) const {
    144    Sub result;
    145    result.x = std::max<T>(x, aRect.x);
    146    result.y = std::max<T>(y, aRect.y);
    147    T right = std::min<T>(x + width, aRect.x + aRect.width);
    148    T bottom = std::min<T>(y + height, aRect.y + aRect.height);
    149    // See bug 1457110, this function expects to -only- size to 0,0 if the
    150    // width/height is explicitly negative.
    151    if (right < result.x || bottom < result.y) {
    152      result.width = 0;
    153      result.height = 0;
    154    } else {
    155      result.width = right - result.x;
    156      result.height = bottom - result.y;
    157    }
    158    return result;
    159  }
    160 
    161  // Sets *this to be the rectangle containing the intersection of the points
    162  // (including edges) of *this and aRect. If there are no points in that
    163  // intersection, sets *this to be an empty rectangle with x/y set to the
    164  // std::max of the x/y of *this and aRect.
    165  //
    166  // 'this' can be the same object as either aRect1 or aRect2
    167  bool IntersectRect(const Sub& aRect1, const Sub& aRect2) {
    168    T newX = std::max<T>(aRect1.x, aRect2.x);
    169    T newY = std::max<T>(aRect1.y, aRect2.y);
    170    width = std::min<T>(aRect1.x - newX + aRect1.width,
    171                        aRect2.x - newX + aRect2.width);
    172    height = std::min<T>(aRect1.y - newY + aRect1.height,
    173                         aRect2.y - newY + aRect2.height);
    174    x = newX;
    175    y = newY;
    176    if (width <= 0 || height <= 0) {
    177      SizeTo(0, 0);
    178      return false;
    179    }
    180    return true;
    181  }
    182 
    183  // Returns the smallest rectangle that contains both the area of both
    184  // this and aRect. Thus, empty input rectangles are ignored.
    185  // Note: if both rectangles are empty, returns aRect.
    186  // WARNING! This is not safe against overflow, prefer using SafeUnion instead
    187  // when dealing with int-based rects.
    188  [[nodiscard]] Sub Union(const Sub& aRect) const {
    189    if (IsEmpty()) {
    190      return aRect;
    191    } else if (aRect.IsEmpty()) {
    192      return *static_cast<const Sub*>(this);
    193    } else {
    194      return UnionEdges(aRect);
    195    }
    196  }
    197  // Returns the smallest rectangle that contains both the points (including
    198  // edges) of both aRect1 and aRect2.
    199  // Thus, empty input rectangles are allowed to affect the result.
    200  // WARNING! This is not safe against overflow, prefer using SafeUnionEdges
    201  // instead when dealing with int-based rects.
    202  [[nodiscard]] Sub UnionEdges(const Sub& aRect) const {
    203    Sub result;
    204    result.x = std::min(x, aRect.x);
    205    result.y = std::min(y, aRect.y);
    206    result.width = std::max(XMost(), aRect.XMost()) - result.x;
    207    result.height = std::max(YMost(), aRect.YMost()) - result.y;
    208    return result;
    209  }
    210  // Computes the smallest rectangle that contains both the area of both
    211  // aRect1 and aRect2, and fills 'this' with the result.
    212  // Thus, empty input rectangles are ignored.
    213  // If both rectangles are empty, sets 'this' to aRect2.
    214  //
    215  // 'this' can be the same object as either aRect1 or aRect2
    216  void UnionRect(const Sub& aRect1, const Sub& aRect2) {
    217    *static_cast<Sub*>(this) = aRect1.Union(aRect2);
    218  }
    219 
    220  void OrWith(const Sub& aRect1) {
    221    UnionRect(*static_cast<Sub*>(this), aRect1);
    222  }
    223 
    224  // Computes the smallest rectangle that contains both the points (including
    225  // edges) of both aRect1 and aRect2.
    226  // Thus, empty input rectangles are allowed to affect the result.
    227  //
    228  // 'this' can be the same object as either aRect1 or aRect2
    229  void UnionRectEdges(const Sub& aRect1, const Sub& aRect2) {
    230    *static_cast<Sub*>(this) = aRect1.UnionEdges(aRect2);
    231  }
    232 
    233  // Expands the rect to include the point
    234  void ExpandToEnclose(const Point& aPoint) {
    235    if (aPoint.x < x) {
    236      width = XMost() - aPoint.x;
    237      x = aPoint.x;
    238    } else if (aPoint.x > XMost()) {
    239      width = aPoint.x - x;
    240    }
    241    if (aPoint.y < y) {
    242      height = YMost() - aPoint.y;
    243      y = aPoint.y;
    244    } else if (aPoint.y > YMost()) {
    245      height = aPoint.y - y;
    246    }
    247  }
    248 
    249  MOZ_ALWAYS_INLINE void SetRect(T aX, T aY, T aWidth, T aHeight) {
    250    x = aX;
    251    y = aY;
    252    width = aWidth;
    253    height = aHeight;
    254  }
    255  MOZ_ALWAYS_INLINE void SetRectX(T aX, T aWidth) {
    256    x = aX;
    257    width = aWidth;
    258  }
    259  MOZ_ALWAYS_INLINE void SetRectY(T aY, T aHeight) {
    260    y = aY;
    261    height = aHeight;
    262  }
    263  MOZ_ALWAYS_INLINE void SetBox(T aX, T aY, T aXMost, T aYMost) {
    264    x = aX;
    265    y = aY;
    266    width = aXMost - aX;
    267    height = aYMost - aY;
    268  }
    269  MOZ_ALWAYS_INLINE void SetNonEmptyBox(T aX, T aY, T aXMost, T aYMost) {
    270    x = aX;
    271    y = aY;
    272    width = std::max(0, aXMost - aX);
    273    height = std::max(0, aYMost - aY);
    274  }
    275  MOZ_ALWAYS_INLINE void SetBoxX(T aX, T aXMost) {
    276    x = aX;
    277    width = aXMost - aX;
    278  }
    279  MOZ_ALWAYS_INLINE void SetBoxY(T aY, T aYMost) {
    280    y = aY;
    281    height = aYMost - aY;
    282  }
    283  void SetRect(const Point& aPt, const SizeT& aSize) {
    284    SetRect(aPt.x, aPt.y, aSize.width, aSize.height);
    285  }
    286  MOZ_ALWAYS_INLINE void GetRect(T* aX, T* aY, T* aWidth, T* aHeight) const {
    287    *aX = x;
    288    *aY = y;
    289    *aWidth = width;
    290    *aHeight = height;
    291  }
    292 
    293  MOZ_ALWAYS_INLINE void MoveTo(T aX, T aY) {
    294    x = aX;
    295    y = aY;
    296  }
    297  MOZ_ALWAYS_INLINE void MoveToX(T aX) { x = aX; }
    298  MOZ_ALWAYS_INLINE void MoveToY(T aY) { y = aY; }
    299  MOZ_ALWAYS_INLINE void MoveTo(const Point& aPoint) {
    300    x = aPoint.x;
    301    y = aPoint.y;
    302  }
    303  MOZ_ALWAYS_INLINE void MoveBy(T aDx, T aDy) {
    304    x += aDx;
    305    y += aDy;
    306  }
    307  MOZ_ALWAYS_INLINE void MoveByX(T aDx) { x += aDx; }
    308  MOZ_ALWAYS_INLINE void MoveByY(T aDy) { y += aDy; }
    309  MOZ_ALWAYS_INLINE void MoveBy(const Point& aPoint) {
    310    x += aPoint.x;
    311    y += aPoint.y;
    312  }
    313  MOZ_ALWAYS_INLINE void SizeTo(T aWidth, T aHeight) {
    314    width = aWidth;
    315    height = aHeight;
    316  }
    317  MOZ_ALWAYS_INLINE void SizeTo(const SizeT& aSize) {
    318    width = aSize.width;
    319    height = aSize.height;
    320  }
    321 
    322  // Variant of MoveBy that ensures that even after translation by a point that
    323  // the rectangle coordinates will still fit within numeric limits. The origin
    324  // and size will be clipped within numeric limits to ensure this.
    325  void SafeMoveByX(T aDx) {
    326    T x2 = XMost();
    327    if (aDx >= T(0)) {
    328      T limit = std::numeric_limits<T>::max();
    329      x = limit - aDx < x ? limit : x + aDx;
    330      width = (limit - aDx < x2 ? limit : x2 + aDx) - x;
    331    } else {
    332      T limit = std::numeric_limits<T>::lowest();
    333      x = limit - aDx > x ? limit : x + aDx;
    334      width = (limit - aDx > x2 ? limit : x2 + aDx) - x;
    335    }
    336  }
    337  void SafeMoveByY(T aDy) {
    338    T y2 = YMost();
    339    if (aDy >= T(0)) {
    340      T limit = std::numeric_limits<T>::max();
    341      y = limit - aDy < y ? limit : y + aDy;
    342      height = (limit - aDy < y2 ? limit : y2 + aDy) - y;
    343    } else {
    344      T limit = std::numeric_limits<T>::lowest();
    345      y = limit - aDy > y ? limit : y + aDy;
    346      height = (limit - aDy > y2 ? limit : y2 + aDy) - y;
    347    }
    348  }
    349  void SafeMoveBy(T aDx, T aDy) {
    350    SafeMoveByX(aDx);
    351    SafeMoveByY(aDy);
    352  }
    353  void SafeMoveBy(const Point& aPoint) { SafeMoveBy(aPoint.x, aPoint.y); }
    354 
    355  void Inflate(T aD) { Inflate(aD, aD); }
    356  void Inflate(T aDx, T aDy) {
    357    x -= aDx;
    358    y -= aDy;
    359    width += 2 * aDx;
    360    height += 2 * aDy;
    361  }
    362  void Inflate(const MarginT& aMargin) {
    363    x -= aMargin.left;
    364    y -= aMargin.top;
    365    width += aMargin.LeftRight();
    366    height += aMargin.TopBottom();
    367  }
    368  void Inflate(const SizeT& aSize) { Inflate(aSize.width, aSize.height); }
    369 
    370  void Deflate(T aD) { Deflate(aD, aD); }
    371  void Deflate(T aDx, T aDy) {
    372    x += aDx;
    373    y += aDy;
    374    width = std::max(T(0), width - 2 * aDx);
    375    height = std::max(T(0), height - 2 * aDy);
    376  }
    377  void Deflate(const MarginT& aMargin) {
    378    x += aMargin.left;
    379    y += aMargin.top;
    380    width = std::max(T(0), width - aMargin.LeftRight());
    381    height = std::max(T(0), height - aMargin.TopBottom());
    382  }
    383  void Deflate(const SizeT& aSize) { Deflate(aSize.width, aSize.height); }
    384 
    385  // Return true if the rectangles contain the same set of points, including
    386  // points on the edges.
    387  // Use when we care about the exact x/y/width/height values being
    388  // equal (i.e. we care about differences in empty rectangles).
    389  bool IsEqualEdges(const Sub& aRect) const {
    390    return x == aRect.x && y == aRect.y && width == aRect.width &&
    391           height == aRect.height;
    392  }
    393  MOZ_ALWAYS_INLINE bool IsEqualRect(T aX, T aY, T aW, T aH) {
    394    return x == aX && y == aY && width == aW && height == aH;
    395  }
    396  MOZ_ALWAYS_INLINE bool IsEqualXY(T aX, T aY) { return x == aX && y == aY; }
    397 
    398  MOZ_ALWAYS_INLINE bool IsEqualSize(T aW, T aH) {
    399    return width == aW && height == aH;
    400  }
    401 
    402  // Return true if the rectangles contain the same area of the plane.
    403  // Use when we do not care about differences in empty rectangles.
    404  bool IsEqualInterior(const Sub& aRect) const {
    405    return IsEqualEdges(aRect) || (IsEmpty() && aRect.IsEmpty());
    406  }
    407 
    408  friend Sub operator+(Sub aSub, const Point& aPoint) {
    409    aSub += aPoint;
    410    return aSub;
    411  }
    412  friend Sub operator-(Sub aSub, const Point& aPoint) {
    413    aSub -= aPoint;
    414    return aSub;
    415  }
    416  friend Sub operator+(Sub aSub, const SizeT& aSize) {
    417    aSub += aSize;
    418    return aSub;
    419  }
    420  friend Sub operator-(Sub aSub, const SizeT& aSize) {
    421    aSub -= aSize;
    422    return aSub;
    423  }
    424  Sub& operator+=(const Point& aPoint) {
    425    MoveBy(aPoint);
    426    return *static_cast<Sub*>(this);
    427  }
    428  Sub& operator-=(const Point& aPoint) {
    429    MoveBy(-aPoint);
    430    return *static_cast<Sub*>(this);
    431  }
    432  Sub& operator+=(const SizeT& aSize) {
    433    width += aSize.width;
    434    height += aSize.height;
    435    return *static_cast<Sub*>(this);
    436  }
    437  Sub& operator-=(const SizeT& aSize) {
    438    width -= aSize.width;
    439    height -= aSize.height;
    440    return *static_cast<Sub*>(this);
    441  }
    442  // Find difference as a Margin
    443  MarginT operator-(const Sub& aRect) const {
    444    return MarginT(aRect.y - y, XMost() - aRect.XMost(),
    445                   YMost() - aRect.YMost(), aRect.x - x);
    446  }
    447 
    448  // Helpers for accessing the vertices
    449  Point TopLeft() const { return Point(x, y); }
    450  Point TopRight() const { return Point(XMost(), y); }
    451  Point BottomLeft() const { return Point(x, YMost()); }
    452  Point BottomRight() const { return Point(XMost(), YMost()); }
    453  Point AtCorner(Corner aCorner) const {
    454    switch (aCorner) {
    455      case eCornerTopLeft:
    456        return TopLeft();
    457      case eCornerTopRight:
    458        return TopRight();
    459      case eCornerBottomRight:
    460        return BottomRight();
    461      case eCornerBottomLeft:
    462        return BottomLeft();
    463    }
    464    MOZ_CRASH("GFX: Incomplete switch");
    465  }
    466  Point CCWCorner(mozilla::Side side) const {
    467    switch (side) {
    468      case eSideTop:
    469        return TopLeft();
    470      case eSideRight:
    471        return TopRight();
    472      case eSideBottom:
    473        return BottomRight();
    474      case eSideLeft:
    475        return BottomLeft();
    476    }
    477    MOZ_CRASH("GFX: Incomplete switch");
    478  }
    479  Point CWCorner(mozilla::Side side) const {
    480    switch (side) {
    481      case eSideTop:
    482        return TopRight();
    483      case eSideRight:
    484        return BottomRight();
    485      case eSideBottom:
    486        return BottomLeft();
    487      case eSideLeft:
    488        return TopLeft();
    489    }
    490    MOZ_CRASH("GFX: Incomplete switch");
    491  }
    492  Point Center() const { return Point(x, y) + Point(width, height) / 2; }
    493  SizeT Size() const { return SizeT(width, height); }
    494 
    495  T Area() const { return width * height; }
    496 
    497  // Helper methods for computing the extents
    498  MOZ_ALWAYS_INLINE T X() const { return x; }
    499  MOZ_ALWAYS_INLINE T Y() const { return y; }
    500  MOZ_ALWAYS_INLINE T Width() const { return width; }
    501  MOZ_ALWAYS_INLINE T Height() const { return height; }
    502 
    503  MOZ_ALWAYS_INLINE T XMost() const {
    504    if constexpr (std::is_integral<T>::value) {
    505      return (Saturate<T>(x) + width).value();
    506    } else {
    507      return x + width;
    508    }
    509  }
    510  MOZ_ALWAYS_INLINE T YMost() const {
    511    if constexpr (std::is_integral<T>::value) {
    512      return (Saturate<T>(y) + height).value();
    513    } else {
    514      return y + height;
    515    }
    516  }
    517 
    518  // Set width and height. SizeTo() sets them together.
    519  MOZ_ALWAYS_INLINE void SetWidth(T aWidth) { width = aWidth; }
    520  MOZ_ALWAYS_INLINE void SetHeight(T aHeight) { height = aHeight; }
    521 
    522  // Get the coordinate of the edge on the given side.
    523  T Edge(mozilla::Side aSide) const {
    524    switch (aSide) {
    525      case eSideTop:
    526        return Y();
    527      case eSideRight:
    528        return XMost();
    529      case eSideBottom:
    530        return YMost();
    531      case eSideLeft:
    532        return X();
    533    }
    534    MOZ_CRASH("GFX: Incomplete switch");
    535  }
    536 
    537  // Moves one edge of the rect without moving the opposite edge.
    538  void SetLeftEdge(T aX) {
    539    width = XMost() - aX;
    540    x = aX;
    541  }
    542  void SetRightEdge(T aXMost) { width = aXMost - x; }
    543  void SetTopEdge(T aY) {
    544    height = YMost() - aY;
    545    y = aY;
    546  }
    547  void SetBottomEdge(T aYMost) { height = aYMost - y; }
    548  void Swap() {
    549    std::swap(x, y);
    550    std::swap(width, height);
    551  }
    552 
    553  // Round the rectangle edges to integer coordinates, such that the rounded
    554  // rectangle has the same set of pixel centers as the original rectangle.
    555  // Edges at offset 0.5 round up.
    556  // Suitable for most places where integral device coordinates
    557  // are needed, but note that any translation should be applied first to
    558  // avoid pixel rounding errors.
    559  // Note that this is *not* rounding to nearest integer if the values are
    560  // negative. They are always rounding as floor(n + 0.5). See
    561  // https://bugzilla.mozilla.org/show_bug.cgi?id=410748#c14 If you need similar
    562  // method which is using NS_round(), you should create new
    563  // |RoundAwayFromZero()| method.
    564  void Round() {
    565    T x0 = static_cast<T>(std::floor(T(X()) + 0.5f));
    566    T y0 = static_cast<T>(std::floor(T(Y()) + 0.5f));
    567    T x1 = static_cast<T>(std::floor(T(XMost()) + 0.5f));
    568    T y1 = static_cast<T>(std::floor(T(YMost()) + 0.5f));
    569 
    570    x = x0;
    571    y = y0;
    572 
    573    width = x1 - x0;
    574    height = y1 - y0;
    575  }
    576 
    577  // Snap the rectangle edges to integer coordinates, such that the
    578  // original rectangle contains the resulting rectangle.
    579  void RoundIn() {
    580    T x0 = static_cast<T>(std::ceil(T(X())));
    581    T y0 = static_cast<T>(std::ceil(T(Y())));
    582    T x1 = static_cast<T>(std::floor(T(XMost())));
    583    T y1 = static_cast<T>(std::floor(T(YMost())));
    584 
    585    x = x0;
    586    y = y0;
    587 
    588    width = x1 - x0;
    589    height = y1 - y0;
    590  }
    591 
    592  // Snap the rectangle edges to integer coordinates, such that the
    593  // resulting rectangle contains the original rectangle.
    594  void RoundOut() {
    595    T x0 = static_cast<T>(std::floor(T(X())));
    596    T y0 = static_cast<T>(std::floor(T(Y())));
    597    T x1 = static_cast<T>(std::ceil(T(XMost())));
    598    T y1 = static_cast<T>(std::ceil(T(YMost())));
    599 
    600    x = x0;
    601    y = y0;
    602 
    603    width = x1 - x0;
    604    height = y1 - y0;
    605  }
    606 
    607  // Scale 'this' by aScale.xScale and aScale.yScale without doing any rounding.
    608  template <class Src, class Dst>
    609  void Scale(const BaseScaleFactors2D<Src, Dst, T>& aScale) {
    610    Scale(aScale.xScale, aScale.yScale);
    611  }
    612  // Scale 'this' by aScale without doing any rounding.
    613  void Scale(T aScale) { Scale(aScale, aScale); }
    614  // Scale 'this' by aXScale and aYScale, without doing any rounding.
    615  void Scale(T aXScale, T aYScale) {
    616    x = x * aXScale;
    617    y = y * aYScale;
    618    width = width * aXScale;
    619    height = height * aYScale;
    620  }
    621  // Scale 'this' by aScale, converting coordinates to integers so that the
    622  // result is the smallest integer-coordinate rectangle containing the
    623  // unrounded result. Note: this can turn an empty rectangle into a non-empty
    624  // rectangle
    625  void ScaleRoundOut(double aScale) { ScaleRoundOut(aScale, aScale); }
    626  // Scale 'this' by aXScale and aYScale, converting coordinates to integers so
    627  // that the result is the smallest integer-coordinate rectangle containing the
    628  // unrounded result.
    629  // Note: this can turn an empty rectangle into a non-empty rectangle
    630  void ScaleRoundOut(double aXScale, double aYScale) {
    631    T right = static_cast<T>(ceil(double(XMost()) * aXScale));
    632    T bottom = static_cast<T>(ceil(double(YMost()) * aYScale));
    633    x = static_cast<T>(floor(double(x) * aXScale));
    634    y = static_cast<T>(floor(double(y) * aYScale));
    635    width = right - x;
    636    height = bottom - y;
    637  }
    638  // Scale 'this' by aScale, converting coordinates to integers so that the
    639  // result is the largest integer-coordinate rectangle contained by the
    640  // unrounded result.
    641  void ScaleRoundIn(double aScale) { ScaleRoundIn(aScale, aScale); }
    642  // Scale 'this' by aXScale and aYScale, converting coordinates to integers so
    643  // that the result is the largest integer-coordinate rectangle contained by
    644  // the unrounded result.
    645  void ScaleRoundIn(double aXScale, double aYScale) {
    646    T right = static_cast<T>(floor(double(XMost()) * aXScale));
    647    T bottom = static_cast<T>(floor(double(YMost()) * aYScale));
    648    x = static_cast<T>(ceil(double(x) * aXScale));
    649    y = static_cast<T>(ceil(double(y) * aYScale));
    650    width = std::max<T>(0, right - x);
    651    height = std::max<T>(0, bottom - y);
    652  }
    653  // Scale 'this' by 1/aScale, converting coordinates to integers so that the
    654  // result is the smallest integer-coordinate rectangle containing the
    655  // unrounded result. Note: this can turn an empty rectangle into a non-empty
    656  // rectangle
    657  void ScaleInverseRoundOut(double aScale) {
    658    ScaleInverseRoundOut(aScale, aScale);
    659  }
    660  // Scale 'this' by 1/aXScale and 1/aYScale, converting coordinates to integers
    661  // so that the result is the smallest integer-coordinate rectangle containing
    662  // the unrounded result. Note: this can turn an empty rectangle into a
    663  // non-empty rectangle
    664  void ScaleInverseRoundOut(double aXScale, double aYScale) {
    665    T right = static_cast<T>(ceil(double(XMost()) / aXScale));
    666    T bottom = static_cast<T>(ceil(double(YMost()) / aYScale));
    667    x = static_cast<T>(floor(double(x) / aXScale));
    668    y = static_cast<T>(floor(double(y) / aYScale));
    669    width = right - x;
    670    height = bottom - y;
    671  }
    672  // Scale 'this' by 1/aScale, converting coordinates to integers so that the
    673  // result is the largest integer-coordinate rectangle contained by the
    674  // unrounded result.
    675  void ScaleInverseRoundIn(double aScale) {
    676    ScaleInverseRoundIn(aScale, aScale);
    677  }
    678  // Scale 'this' by 1/aXScale and 1/aYScale, converting coordinates to integers
    679  // so that the result is the largest integer-coordinate rectangle contained by
    680  // the unrounded result.
    681  void ScaleInverseRoundIn(double aXScale, double aYScale) {
    682    T right = static_cast<T>(floor(double(XMost()) / aXScale));
    683    T bottom = static_cast<T>(floor(double(YMost()) / aYScale));
    684    x = static_cast<T>(ceil(double(x) / aXScale));
    685    y = static_cast<T>(ceil(double(y) / aYScale));
    686    width = std::max<T>(0, right - x);
    687    height = std::max<T>(0, bottom - y);
    688  }
    689 
    690  /**
    691   * Clamp aPoint to this rectangle. It is allowed to end up on any
    692   * edge of the rectangle.
    693   * Return the rectangle as a point if the rectangle is empty.
    694   */
    695  [[nodiscard]] Point ClampPoint(const Point& aPoint) const {
    696    using Coord = decltype(aPoint.x);
    697    return {std::max(Coord(x), std::min(Coord(XMost()), aPoint.x)),
    698            std::max(Coord(y), std::min(Coord(YMost()), aPoint.y))};
    699  }
    700 
    701  /**
    702   * Translate this rectangle to be inside aRect. If it doesn't fit inside
    703   * aRect then the dimensions that don't fit will be shrunk so that they
    704   * do fit. The resulting rect is returned.
    705   */
    706  [[nodiscard]] Sub MoveInsideAndClamp(const Sub& aRect) const {
    707    Sub rect(std::max(aRect.x, x), std::max(aRect.y, y),
    708             std::min(aRect.width, width), std::min(aRect.height, height));
    709    rect.x = std::min(rect.XMost(), aRect.XMost()) - rect.width;
    710    rect.y = std::min(rect.YMost(), aRect.YMost()) - rect.height;
    711    return rect;
    712  }
    713 
    714  // Returns the largest rectangle that can be represented with 32-bit
    715  // signed integers, centered around a point at 0,0.  As BaseRect's represent
    716  // the dimensions as a top-left point with a width and height, the width
    717  // and height will be the largest positive 32-bit value.  The top-left
    718  // position coordinate is divided by two to center the rectangle around a
    719  // point at 0,0.
    720  static Sub MaxIntRect() {
    721    return Sub(static_cast<T>(-std::numeric_limits<int32_t>::max() * 0.5),
    722               static_cast<T>(-std::numeric_limits<int32_t>::max() * 0.5),
    723               static_cast<T>(std::numeric_limits<int32_t>::max()),
    724               static_cast<T>(std::numeric_limits<int32_t>::max()));
    725  };
    726 
    727  // Returns a point representing the distance, along each dimension, of the
    728  // given point from this rectangle. The distance along a dimension is defined
    729  // as zero if the point is within the bounds of the rectangle in that
    730  // dimension; otherwise, it's the distance to the closer endpoint of the
    731  // rectangle in that dimension.
    732  Point DistanceTo(const Point& aPoint) const {
    733    return {DistanceFromInterval(aPoint.x, x, XMost()),
    734            DistanceFromInterval(aPoint.y, y, YMost())};
    735  }
    736 
    737  friend std::ostream& operator<<(
    738      std::ostream& stream,
    739      const BaseRect<T, Sub, Point, SizeT, MarginT>& aRect) {
    740    return stream << "(x=" << aRect.x << ", y=" << aRect.y
    741                  << ", w=" << aRect.width << ", h=" << aRect.height << ')';
    742  }
    743 
    744 private:
    745  // Do not use the default operator== or operator!= !
    746  // Use IsEqualEdges or IsEqualInterior explicitly.
    747  bool operator==(const Sub& aRect) const { return false; }
    748  bool operator!=(const Sub& aRect) const { return false; }
    749 
    750  // Helper function for DistanceTo() that computes the distance of a
    751  // coordinate along one dimension from an interval in that dimension.
    752  static T DistanceFromInterval(T aCoord, T aIntervalStart, T aIntervalEnd) {
    753    if (aCoord < aIntervalStart) {
    754      return aIntervalStart - aCoord;
    755    }
    756    if (aCoord > aIntervalEnd) {
    757      return aCoord - aIntervalEnd;
    758    }
    759    return 0;
    760  }
    761 };
    762 
    763 }  // namespace mozilla::gfx
    764 
    765 #endif /* MOZILLA_GFX_BASERECT_H_ */