Bug 1866777 - Disable test_race_cache_with_network.js on windows opt for frequent...
[gecko.git] / gfx / 2d / BaseRect.h
blob70d82050b376f5dcb7140031dbe9eab64ee08f8d
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/. */
7 #ifndef MOZILLA_GFX_BASERECT_H_
8 #define MOZILLA_GFX_BASERECT_H_
10 #include <algorithm>
11 #include <cmath>
12 #include <ostream>
13 #include <type_traits>
15 #include "mozilla/Assertions.h"
16 #include "mozilla/FloatingPoint.h"
17 #include "mozilla/gfx/ScaleFactors2D.h"
18 #include "Types.h"
20 namespace mozilla::gfx {
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.
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.
42 * Do not use this class directly. Subclass it, pass that subclass as the
43 * Sub parameter, and only use that subclass.
45 template <class T, class Sub, class Point, class SizeT, class MarginT>
46 struct BaseRect {
47 T x, y, width, height;
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) {}
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;
62 MOZ_ALWAYS_INLINE bool IsEmpty() const { return height <= 0 || width <= 0; }
63 void SetEmpty() { width = height = 0; }
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)));
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());
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();
87 MOZ_ALWAYS_INLINE bool ContainsX(T aX) const {
88 return x <= aX && aX < XMost();
90 MOZ_ALWAYS_INLINE bool ContainsY(T aY) const {
91 return y <= aY && aY < YMost();
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);
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();
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();
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.
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);
136 return result;
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;
158 return result;
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.
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;
180 return true;
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);
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;
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.
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);
220 void OrWith(const Sub& aRect1) {
221 UnionRect(*static_cast<Sub*>(this), aRect1);
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.
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);
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;
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;
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;
255 MOZ_ALWAYS_INLINE void SetRectX(T aX, T aWidth) {
256 x = aX;
257 width = aWidth;
259 MOZ_ALWAYS_INLINE void SetRectY(T aY, T aHeight) {
260 y = aY;
261 height = aHeight;
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;
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);
275 MOZ_ALWAYS_INLINE void SetBoxX(T aX, T aXMost) {
276 x = aX;
277 width = aXMost - aX;
279 MOZ_ALWAYS_INLINE void SetBoxY(T aY, T aYMost) {
280 y = aY;
281 height = aYMost - aY;
283 void SetRect(const Point& aPt, const SizeT& aSize) {
284 SetRect(aPt.x, aPt.y, aSize.width, aSize.height);
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;
293 MOZ_ALWAYS_INLINE void MoveTo(T aX, T aY) {
294 x = aX;
295 y = aY;
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;
303 MOZ_ALWAYS_INLINE void MoveBy(T aDx, T aDy) {
304 x += aDx;
305 y += aDy;
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;
313 MOZ_ALWAYS_INLINE void SizeTo(T aWidth, T aHeight) {
314 width = aWidth;
315 height = aHeight;
317 MOZ_ALWAYS_INLINE void SizeTo(const SizeT& aSize) {
318 width = aSize.width;
319 height = aSize.height;
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>::min();
333 x = limit - aDx > x ? limit : x + aDx;
334 width = (limit - aDx > x2 ? limit : x2 + aDx) - x;
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>::min();
345 y = limit - aDy > y ? limit : y + aDy;
346 height = (limit - aDy > y2 ? limit : y2 + aDy) - y;
349 void SafeMoveBy(T aDx, T aDy) {
350 SafeMoveByX(aDx);
351 SafeMoveByY(aDy);
353 void SafeMoveBy(const Point& aPoint) { SafeMoveBy(aPoint.x, aPoint.y); }
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;
362 void Inflate(const MarginT& aMargin) {
363 x -= aMargin.left;
364 y -= aMargin.top;
365 width += aMargin.LeftRight();
366 height += aMargin.TopBottom();
368 void Inflate(const SizeT& aSize) { Inflate(aSize.width, aSize.height); }
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);
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());
383 void Deflate(const SizeT& aSize) { Deflate(aSize.width, aSize.height); }
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;
393 MOZ_ALWAYS_INLINE bool IsEqualRect(T aX, T aY, T aW, T aH) {
394 return x == aX && y == aY && width == aW && height == aH;
396 MOZ_ALWAYS_INLINE bool IsEqualXY(T aX, T aY) { return x == aX && y == aY; }
398 MOZ_ALWAYS_INLINE bool IsEqualSize(T aW, T aH) {
399 return width == aW && height == aH;
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());
408 friend Sub operator+(Sub aSub, const Point& aPoint) {
409 aSub += aPoint;
410 return aSub;
412 friend Sub operator-(Sub aSub, const Point& aPoint) {
413 aSub -= aPoint;
414 return aSub;
416 friend Sub operator+(Sub aSub, const SizeT& aSize) {
417 aSub += aSize;
418 return aSub;
420 friend Sub operator-(Sub aSub, const SizeT& aSize) {
421 aSub -= aSize;
422 return aSub;
424 Sub& operator+=(const Point& aPoint) {
425 MoveBy(aPoint);
426 return *static_cast<Sub*>(this);
428 Sub& operator-=(const Point& aPoint) {
429 MoveBy(-aPoint);
430 return *static_cast<Sub*>(this);
432 Sub& operator+=(const SizeT& aSize) {
433 width += aSize.width;
434 height += aSize.height;
435 return *static_cast<Sub*>(this);
437 Sub& operator-=(const SizeT& aSize) {
438 width -= aSize.width;
439 height -= aSize.height;
440 return *static_cast<Sub*>(this);
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);
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();
464 MOZ_CRASH("GFX: Incomplete switch");
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();
477 MOZ_CRASH("GFX: Incomplete switch");
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();
490 MOZ_CRASH("GFX: Incomplete switch");
492 Point Center() const { return Point(x, y) + Point(width, height) / 2; }
493 SizeT Size() const { return SizeT(width, height); }
495 T Area() const { return width * height; }
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 MOZ_ALWAYS_INLINE T XMost() const { return x + width; }
503 MOZ_ALWAYS_INLINE T YMost() const { return y + height; }
505 // Set width and height. SizeTo() sets them together.
506 MOZ_ALWAYS_INLINE void SetWidth(T aWidth) { width = aWidth; }
507 MOZ_ALWAYS_INLINE void SetHeight(T aHeight) { height = aHeight; }
509 // Get the coordinate of the edge on the given side.
510 T Edge(mozilla::Side aSide) const {
511 switch (aSide) {
512 case eSideTop:
513 return Y();
514 case eSideRight:
515 return XMost();
516 case eSideBottom:
517 return YMost();
518 case eSideLeft:
519 return X();
521 MOZ_CRASH("GFX: Incomplete switch");
524 // Moves one edge of the rect without moving the opposite edge.
525 void SetLeftEdge(T aX) {
526 width = XMost() - aX;
527 x = aX;
529 void SetRightEdge(T aXMost) { width = aXMost - x; }
530 void SetTopEdge(T aY) {
531 height = YMost() - aY;
532 y = aY;
534 void SetBottomEdge(T aYMost) { height = aYMost - y; }
535 void Swap() {
536 std::swap(x, y);
537 std::swap(width, height);
540 // Round the rectangle edges to integer coordinates, such that the rounded
541 // rectangle has the same set of pixel centers as the original rectangle.
542 // Edges at offset 0.5 round up.
543 // Suitable for most places where integral device coordinates
544 // are needed, but note that any translation should be applied first to
545 // avoid pixel rounding errors.
546 // Note that this is *not* rounding to nearest integer if the values are
547 // negative. They are always rounding as floor(n + 0.5). See
548 // https://bugzilla.mozilla.org/show_bug.cgi?id=410748#c14 If you need similar
549 // method which is using NS_round(), you should create new
550 // |RoundAwayFromZero()| method.
551 void Round() {
552 T x0 = static_cast<T>(std::floor(T(X()) + 0.5f));
553 T y0 = static_cast<T>(std::floor(T(Y()) + 0.5f));
554 T x1 = static_cast<T>(std::floor(T(XMost()) + 0.5f));
555 T y1 = static_cast<T>(std::floor(T(YMost()) + 0.5f));
557 x = x0;
558 y = y0;
560 width = x1 - x0;
561 height = y1 - y0;
564 // Snap the rectangle edges to integer coordinates, such that the
565 // original rectangle contains the resulting rectangle.
566 void RoundIn() {
567 T x0 = static_cast<T>(std::ceil(T(X())));
568 T y0 = static_cast<T>(std::ceil(T(Y())));
569 T x1 = static_cast<T>(std::floor(T(XMost())));
570 T y1 = static_cast<T>(std::floor(T(YMost())));
572 x = x0;
573 y = y0;
575 width = x1 - x0;
576 height = y1 - y0;
579 // Snap the rectangle edges to integer coordinates, such that the
580 // resulting rectangle contains the original rectangle.
581 void RoundOut() {
582 T x0 = static_cast<T>(std::floor(T(X())));
583 T y0 = static_cast<T>(std::floor(T(Y())));
584 T x1 = static_cast<T>(std::ceil(T(XMost())));
585 T y1 = static_cast<T>(std::ceil(T(YMost())));
587 x = x0;
588 y = y0;
590 width = x1 - x0;
591 height = y1 - y0;
594 // Scale 'this' by aScale.xScale and aScale.yScale without doing any rounding.
595 template <class Src, class Dst>
596 void Scale(const BaseScaleFactors2D<Src, Dst, T>& aScale) {
597 Scale(aScale.xScale, aScale.yScale);
599 // Scale 'this' by aScale without doing any rounding.
600 void Scale(T aScale) { Scale(aScale, aScale); }
601 // Scale 'this' by aXScale and aYScale, without doing any rounding.
602 void Scale(T aXScale, T aYScale) {
603 x = x * aXScale;
604 y = y * aYScale;
605 width = width * aXScale;
606 height = height * aYScale;
608 // Scale 'this' by aScale, converting coordinates to integers so that the
609 // result is the smallest integer-coordinate rectangle containing the
610 // unrounded result. Note: this can turn an empty rectangle into a non-empty
611 // rectangle
612 void ScaleRoundOut(double aScale) { ScaleRoundOut(aScale, aScale); }
613 // Scale 'this' by aXScale and aYScale, converting coordinates to integers so
614 // that the result is the smallest integer-coordinate rectangle containing the
615 // unrounded result.
616 // Note: this can turn an empty rectangle into a non-empty rectangle
617 void ScaleRoundOut(double aXScale, double aYScale) {
618 T right = static_cast<T>(ceil(double(XMost()) * aXScale));
619 T bottom = static_cast<T>(ceil(double(YMost()) * aYScale));
620 x = static_cast<T>(floor(double(x) * aXScale));
621 y = static_cast<T>(floor(double(y) * aYScale));
622 width = right - x;
623 height = bottom - y;
625 // Scale 'this' by aScale, converting coordinates to integers so that the
626 // result is the largest integer-coordinate rectangle contained by the
627 // unrounded result.
628 void ScaleRoundIn(double aScale) { ScaleRoundIn(aScale, aScale); }
629 // Scale 'this' by aXScale and aYScale, converting coordinates to integers so
630 // that the result is the largest integer-coordinate rectangle contained by
631 // the unrounded result.
632 void ScaleRoundIn(double aXScale, double aYScale) {
633 T right = static_cast<T>(floor(double(XMost()) * aXScale));
634 T bottom = static_cast<T>(floor(double(YMost()) * aYScale));
635 x = static_cast<T>(ceil(double(x) * aXScale));
636 y = static_cast<T>(ceil(double(y) * aYScale));
637 width = std::max<T>(0, right - x);
638 height = std::max<T>(0, bottom - y);
640 // Scale 'this' by 1/aScale, converting coordinates to integers so that the
641 // result is the smallest integer-coordinate rectangle containing the
642 // unrounded result. Note: this can turn an empty rectangle into a non-empty
643 // rectangle
644 void ScaleInverseRoundOut(double aScale) {
645 ScaleInverseRoundOut(aScale, aScale);
647 // Scale 'this' by 1/aXScale and 1/aYScale, converting coordinates to integers
648 // so that the result is the smallest integer-coordinate rectangle containing
649 // the unrounded result. Note: this can turn an empty rectangle into a
650 // non-empty rectangle
651 void ScaleInverseRoundOut(double aXScale, double aYScale) {
652 T right = static_cast<T>(ceil(double(XMost()) / aXScale));
653 T bottom = static_cast<T>(ceil(double(YMost()) / aYScale));
654 x = static_cast<T>(floor(double(x) / aXScale));
655 y = static_cast<T>(floor(double(y) / aYScale));
656 width = right - x;
657 height = bottom - y;
659 // Scale 'this' by 1/aScale, converting coordinates to integers so that the
660 // result is the largest integer-coordinate rectangle contained by the
661 // unrounded result.
662 void ScaleInverseRoundIn(double aScale) {
663 ScaleInverseRoundIn(aScale, aScale);
665 // Scale 'this' by 1/aXScale and 1/aYScale, converting coordinates to integers
666 // so that the result is the largest integer-coordinate rectangle contained by
667 // the unrounded result.
668 void ScaleInverseRoundIn(double aXScale, double aYScale) {
669 T right = static_cast<T>(floor(double(XMost()) / aXScale));
670 T bottom = static_cast<T>(floor(double(YMost()) / aYScale));
671 x = static_cast<T>(ceil(double(x) / aXScale));
672 y = static_cast<T>(ceil(double(y) / aYScale));
673 width = std::max<T>(0, right - x);
674 height = std::max<T>(0, bottom - y);
678 * Clamp aPoint to this rectangle. It is allowed to end up on any
679 * edge of the rectangle.
681 [[nodiscard]] Point ClampPoint(const Point& aPoint) const {
682 using Coord = decltype(aPoint.x);
683 return Point(std::max(Coord(x), std::min(Coord(XMost()), aPoint.x)),
684 std::max(Coord(y), std::min(Coord(YMost()), aPoint.y)));
688 * Translate this rectangle to be inside aRect. If it doesn't fit inside
689 * aRect then the dimensions that don't fit will be shrunk so that they
690 * do fit. The resulting rect is returned.
692 [[nodiscard]] Sub MoveInsideAndClamp(const Sub& aRect) const {
693 Sub rect(std::max(aRect.x, x), std::max(aRect.y, y),
694 std::min(aRect.width, width), std::min(aRect.height, height));
695 rect.x = std::min(rect.XMost(), aRect.XMost()) - rect.width;
696 rect.y = std::min(rect.YMost(), aRect.YMost()) - rect.height;
697 return rect;
700 // Returns the largest rectangle that can be represented with 32-bit
701 // signed integers, centered around a point at 0,0. As BaseRect's represent
702 // the dimensions as a top-left point with a width and height, the width
703 // and height will be the largest positive 32-bit value. The top-left
704 // position coordinate is divided by two to center the rectangle around a
705 // point at 0,0.
706 static Sub MaxIntRect() {
707 return Sub(static_cast<T>(-std::numeric_limits<int32_t>::max() * 0.5),
708 static_cast<T>(-std::numeric_limits<int32_t>::max() * 0.5),
709 static_cast<T>(std::numeric_limits<int32_t>::max()),
710 static_cast<T>(std::numeric_limits<int32_t>::max()));
713 // Returns a point representing the distance, along each dimension, of the
714 // given point from this rectangle. The distance along a dimension is defined
715 // as zero if the point is within the bounds of the rectangle in that
716 // dimension; otherwise, it's the distance to the closer endpoint of the
717 // rectangle in that dimension.
718 Point DistanceTo(const Point& aPoint) const {
719 return {DistanceFromInterval(aPoint.x, x, XMost()),
720 DistanceFromInterval(aPoint.y, y, YMost())};
723 friend std::ostream& operator<<(
724 std::ostream& stream,
725 const BaseRect<T, Sub, Point, SizeT, MarginT>& aRect) {
726 return stream << "(x=" << aRect.x << ", y=" << aRect.y
727 << ", w=" << aRect.width << ", h=" << aRect.height << ')';
730 private:
731 // Do not use the default operator== or operator!= !
732 // Use IsEqualEdges or IsEqualInterior explicitly.
733 bool operator==(const Sub& aRect) const { return false; }
734 bool operator!=(const Sub& aRect) const { return false; }
736 // Helper function for DistanceTo() that computes the distance of a
737 // coordinate along one dimension from an interval in that dimension.
738 static T DistanceFromInterval(T aCoord, T aIntervalStart, T aIntervalEnd) {
739 if (aCoord < aIntervalStart) {
740 return aIntervalStart - aCoord;
742 if (aCoord > aIntervalEnd) {
743 return aCoord - aIntervalEnd;
745 return 0;
749 } // namespace mozilla::gfx
751 #endif /* MOZILLA_GFX_BASERECT_H_ */