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_ */