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_
13 #include <type_traits>
15 #include "mozilla/Assertions.h"
16 #include "mozilla/FloatingPoint.h"
17 #include "mozilla/gfx/ScaleFactors2D.h"
20 namespace mozilla::gfx
{
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
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
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
>
47 T x
, y
, width
, height
;
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 {
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
&&
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 {
125 result
.x
= std::max
<T
>(x
, aRect
.x
);
126 result
.y
= std::max
<T
>(y
, aRect
.y
);
128 std::min
<T
>(x
- result
.x
+ width
, aRect
.x
- result
.x
+ aRect
.width
);
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) {
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:
143 [[nodiscard
]] Sub
SafeIntersect(const Sub
& aRect
) const {
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
) {
155 result
.width
= right
- result
.x
;
156 result
.height
= bottom
- result
.y
;
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
);
176 if (width
<= 0 || height
<= 0) {
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 {
191 } else if (aRect
.IsEmpty()) {
192 return *static_cast<const Sub
*>(this);
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 {
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
;
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
) {
236 width
= XMost() - aPoint
.x
;
238 } else if (aPoint
.x
> XMost()) {
239 width
= aPoint
.x
- x
;
242 height
= YMost() - 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
) {
255 MOZ_ALWAYS_INLINE
void SetRectX(T aX
, T aWidth
) {
259 MOZ_ALWAYS_INLINE
void SetRectY(T aY
, T aHeight
) {
263 MOZ_ALWAYS_INLINE
void SetBox(T aX
, T aY
, T aXMost
, T aYMost
) {
267 height
= aYMost
- aY
;
269 MOZ_ALWAYS_INLINE
void SetNonEmptyBox(T aX
, T aY
, T aXMost
, T aYMost
) {
272 width
= std::max(0, aXMost
- aX
);
273 height
= std::max(0, aYMost
- aY
);
275 MOZ_ALWAYS_INLINE
void SetBoxX(T aX
, T aXMost
) {
279 MOZ_ALWAYS_INLINE
void SetBoxY(T aY
, T aYMost
) {
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 {
293 MOZ_ALWAYS_INLINE
void MoveTo(T aX
, T 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
) {
303 MOZ_ALWAYS_INLINE
void MoveBy(T aDx
, T 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
) {
313 MOZ_ALWAYS_INLINE
void SizeTo(T aWidth
, T aHeight
) {
317 MOZ_ALWAYS_INLINE
void SizeTo(const SizeT
& aSize
) {
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
) {
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
;
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
) {
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
;
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
) {
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
) {
362 void Inflate(const MarginT
& aMargin
) {
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
) {
374 width
= std::max(T(0), width
- 2 * aDx
);
375 height
= std::max(T(0), height
- 2 * aDy
);
377 void Deflate(const MarginT
& aMargin
) {
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
) {
412 friend Sub
operator-(Sub aSub
, const Point
& aPoint
) {
416 friend Sub
operator+(Sub aSub
, const SizeT
& aSize
) {
420 friend Sub
operator-(Sub aSub
, const SizeT
& aSize
) {
424 Sub
& operator+=(const Point
& aPoint
) {
426 return *static_cast<Sub
*>(this);
428 Sub
& operator-=(const Point
& 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 {
457 case eCornerTopRight
:
459 case eCornerBottomRight
:
460 return BottomRight();
461 case eCornerBottomLeft
:
464 MOZ_CRASH("GFX: Incomplete switch");
466 Point
CCWCorner(mozilla::Side side
) const {
473 return BottomRight();
477 MOZ_CRASH("GFX: Incomplete switch");
479 Point
CWCorner(mozilla::Side side
) const {
484 return BottomRight();
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 {
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
;
529 void SetRightEdge(T aXMost
) { width
= aXMost
- x
; }
530 void SetTopEdge(T aY
) {
531 height
= YMost() - aY
;
534 void SetBottomEdge(T aYMost
) { height
= aYMost
- 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.
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
));
564 // Snap the rectangle edges to integer coordinates, such that the
565 // original rectangle contains the resulting rectangle.
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())));
579 // Snap the rectangle edges to integer coordinates, such that the
580 // resulting rectangle contains the original rectangle.
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())));
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
) {
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
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
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
));
625 // Scale 'this' by aScale, converting coordinates to integers so that the
626 // result is the largest integer-coordinate rectangle contained by the
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
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
));
659 // Scale 'this' by 1/aScale, converting coordinates to integers so that the
660 // result is the largest integer-coordinate rectangle contained by the
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
;
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
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
<< ')';
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
;
749 } // namespace mozilla::gfx
751 #endif /* MOZILLA_GFX_BASERECT_H_ */