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_RECT_H_
8 #define MOZILLA_GFX_RECT_H_
9
10 #include "BaseRect.h"
11 #include "BaseMargin.h"
12 #include "NumericTools.h"
13 #include "Point.h"
14 #include "Tools.h"
15 #include "mozilla/Maybe.h"
16
17 #include <cmath>
18
19 namespace mozilla {
20
21 template <typename>
22 struct IsPixel;
23
24 namespace gfx {
25
26 template <class units, class F>
27 struct RectTyped;
28
29 template <class units>
30 struct IntMarginTyped : public BaseMargin<int32_t, IntMarginTyped<units> >,
31 public units {
32 static_assert(IsPixel<units>::value,
33 "'units' must be a coordinate system tag");
34
35 typedef BaseMargin<int32_t, IntMarginTyped<units> > Super;
36
IntMarginTypedIntMarginTyped37 IntMarginTyped() : Super() {}
IntMarginTypedIntMarginTyped38 IntMarginTyped(int32_t aTop, int32_t aRight, int32_t aBottom, int32_t aLeft)
39 : Super(aTop, aRight, aBottom, aLeft) {}
40
41 // XXX When all of the code is ported, the following functions to convert
42 // to and from unknown types should be removed.
43
FromUnknownMarginIntMarginTyped44 static IntMarginTyped<units> FromUnknownMargin(
45 const IntMarginTyped<UnknownUnits>& aMargin) {
46 return IntMarginTyped<units>(aMargin.top, aMargin.right, aMargin.bottom,
47 aMargin.left);
48 }
49
ToUnknownMarginIntMarginTyped50 IntMarginTyped<UnknownUnits> ToUnknownMargin() const {
51 return IntMarginTyped<UnknownUnits>(this->top, this->right, this->bottom,
52 this->left);
53 }
54 };
55 typedef IntMarginTyped<UnknownUnits> IntMargin;
56
57 template <class units, class F = Float>
58 struct MarginTyped : public BaseMargin<F, MarginTyped<units, F> >,
59 public units {
60 static_assert(IsPixel<units>::value,
61 "'units' must be a coordinate system tag");
62
63 typedef BaseMargin<F, MarginTyped<units, F> > Super;
64
MarginTypedMarginTyped65 MarginTyped() : Super() {}
MarginTypedMarginTyped66 MarginTyped(F aTop, F aRight, F aBottom, F aLeft)
67 : Super(aTop, aRight, aBottom, aLeft) {}
MarginTypedMarginTyped68 explicit MarginTyped(const IntMarginTyped<units>& aMargin)
69 : Super(F(aMargin.top), F(aMargin.right), F(aMargin.bottom),
70 F(aMargin.left)) {}
71
WithinEpsilonOfMarginTyped72 bool WithinEpsilonOf(const MarginTyped& aOther, F aEpsilon) const {
73 return fabs(this->left - aOther.left) < aEpsilon &&
74 fabs(this->top - aOther.top) < aEpsilon &&
75 fabs(this->right - aOther.right) < aEpsilon &&
76 fabs(this->bottom - aOther.bottom) < aEpsilon;
77 }
78 };
79 typedef MarginTyped<UnknownUnits> Margin;
80 typedef MarginTyped<UnknownUnits, double> MarginDouble;
81
82 template <class units>
RoundedToInt(const MarginTyped<units> & aMargin)83 IntMarginTyped<units> RoundedToInt(const MarginTyped<units>& aMargin) {
84 return IntMarginTyped<units>(int32_t(floorf(aMargin.top + 0.5f)),
85 int32_t(floorf(aMargin.right + 0.5f)),
86 int32_t(floorf(aMargin.bottom + 0.5f)),
87 int32_t(floorf(aMargin.left + 0.5f)));
88 }
89
90 template <class units>
91 struct IntRectTyped
92 : public BaseRect<int32_t, IntRectTyped<units>, IntPointTyped<units>,
93 IntSizeTyped<units>, IntMarginTyped<units> >,
94 public units {
95 static_assert(IsPixel<units>::value,
96 "'units' must be a coordinate system tag");
97
98 typedef BaseRect<int32_t, IntRectTyped<units>, IntPointTyped<units>,
99 IntSizeTyped<units>, IntMarginTyped<units> >
100 Super;
101 typedef IntRectTyped<units> Self;
102 typedef IntParam<int32_t> ToInt;
103
IntRectTypedIntRectTyped104 IntRectTyped() : Super() {}
IntRectTypedIntRectTyped105 IntRectTyped(const IntPointTyped<units>& aPos,
106 const IntSizeTyped<units>& aSize)
107 : Super(aPos, aSize) {}
108
IntRectTypedIntRectTyped109 IntRectTyped(ToInt aX, ToInt aY, ToInt aWidth, ToInt aHeight)
110 : Super(aX.value, aY.value, aWidth.value, aHeight.value) {}
111
RoundInIntRectTyped112 static IntRectTyped<units> RoundIn(float aX, float aY, float aW, float aH) {
113 return IntRectTyped<units>::RoundIn(
114 RectTyped<units, float>(aX, aY, aW, aH));
115 }
116
RoundOutIntRectTyped117 static IntRectTyped<units> RoundOut(float aX, float aY, float aW, float aH) {
118 return IntRectTyped<units>::RoundOut(
119 RectTyped<units, float>(aX, aY, aW, aH));
120 }
121
RoundIntRectTyped122 static IntRectTyped<units> Round(float aX, float aY, float aW, float aH) {
123 return IntRectTyped<units>::Round(RectTyped<units, float>(aX, aY, aW, aH));
124 }
125
TruncateIntRectTyped126 static IntRectTyped<units> Truncate(float aX, float aY, float aW, float aH) {
127 return IntRectTyped<units>(IntPointTyped<units>::Truncate(aX, aY),
128 IntSizeTyped<units>::Truncate(aW, aH));
129 }
130
RoundInIntRectTyped131 static IntRectTyped<units> RoundIn(const RectTyped<units, float>& aRect) {
132 auto tmp(aRect);
133 tmp.RoundIn();
134 return IntRectTyped(int32_t(tmp.X()), int32_t(tmp.Y()),
135 int32_t(tmp.Width()), int32_t(tmp.Height()));
136 }
137
RoundOutIntRectTyped138 static IntRectTyped<units> RoundOut(const RectTyped<units, float>& aRect) {
139 auto tmp(aRect);
140 tmp.RoundOut();
141 return IntRectTyped(int32_t(tmp.X()), int32_t(tmp.Y()),
142 int32_t(tmp.Width()), int32_t(tmp.Height()));
143 }
144
RoundIntRectTyped145 static IntRectTyped<units> Round(const RectTyped<units, float>& aRect) {
146 auto tmp(aRect);
147 tmp.Round();
148 return IntRectTyped(int32_t(tmp.X()), int32_t(tmp.Y()),
149 int32_t(tmp.Width()), int32_t(tmp.Height()));
150 }
151
TruncateIntRectTyped152 static IntRectTyped<units> Truncate(const RectTyped<units, float>& aRect) {
153 return IntRectTyped::Truncate(aRect.X(), aRect.Y(), aRect.Width(),
154 aRect.Height());
155 }
156
157 // Rounding isn't meaningful on an integer rectangle.
RoundIntRectTyped158 void Round() {}
RoundInIntRectTyped159 void RoundIn() {}
RoundOutIntRectTyped160 void RoundOut() {}
161
162 // XXX When all of the code is ported, the following functions to convert
163 // to and from unknown types should be removed.
164
FromUnknownRectIntRectTyped165 static IntRectTyped<units> FromUnknownRect(
166 const IntRectTyped<UnknownUnits>& rect) {
167 return IntRectTyped<units>(rect.X(), rect.Y(), rect.Width(), rect.Height());
168 }
169
ToUnknownRectIntRectTyped170 IntRectTyped<UnknownUnits> ToUnknownRect() const {
171 return IntRectTyped<UnknownUnits>(this->X(), this->Y(), this->Width(),
172 this->Height());
173 }
174
OverflowsIntRectTyped175 bool Overflows() const {
176 CheckedInt<int32_t> xMost = this->X();
177 xMost += this->Width();
178 CheckedInt<int32_t> yMost = this->Y();
179 yMost += this->Height();
180 return !xMost.isValid() || !yMost.isValid();
181 }
182
183 // Same as Union(), but in the cases where aRect is non-empty, the union is
184 // done while guarding against overflow. If an overflow is detected, Nothing
185 // is returned.
SafeUnionIntRectTyped186 [[nodiscard]] Maybe<Self> SafeUnion(const Self& aRect) const {
187 if (this->IsEmpty()) {
188 return aRect.Overflows() ? Nothing() : Some(aRect);
189 } else if (aRect.IsEmpty()) {
190 return Some(*static_cast<const Self*>(this));
191 } else {
192 return this->SafeUnionEdges(aRect);
193 }
194 }
195
196 // Same as UnionEdges, but guards against overflow. If an overflow is
197 // detected, Nothing is returned.
SafeUnionEdgesIntRectTyped198 [[nodiscard]] Maybe<Self> SafeUnionEdges(const Self& aRect) const {
199 if (this->Overflows() || aRect.Overflows()) {
200 return Nothing();
201 }
202 // If neither |this| nor |aRect| overflow, then their XMost/YMost values
203 // should be safe to use.
204 CheckedInt<int32_t> newX = std::min(this->x, aRect.x);
205 CheckedInt<int32_t> newY = std::min(this->y, aRect.y);
206 CheckedInt<int32_t> newXMost = std::max(this->XMost(), aRect.XMost());
207 CheckedInt<int32_t> newYMost = std::max(this->YMost(), aRect.YMost());
208 CheckedInt<int32_t> newW = newXMost - newX;
209 CheckedInt<int32_t> newH = newYMost - newY;
210 if (!newW.isValid() || !newH.isValid()) {
211 return Nothing();
212 }
213 return Some(Self(newX.value(), newY.value(), newW.value(), newH.value()));
214 }
215
216 // This is here only to keep IPDL-generated code happy. DO NOT USE.
217 bool operator==(const IntRectTyped<units>& aRect) const {
218 return IntRectTyped<units>::IsEqualEdges(aRect);
219 }
220
InflateToMultipleIntRectTyped221 void InflateToMultiple(const IntSizeTyped<units>& aTileSize) {
222 if (this->IsEmpty()) {
223 return;
224 }
225
226 int32_t yMost = this->YMost();
227 int32_t xMost = this->XMost();
228
229 this->x = mozilla::RoundDownToMultiple(this->x, aTileSize.width);
230 this->y = mozilla::RoundDownToMultiple(this->y, aTileSize.height);
231 xMost = mozilla::RoundUpToMultiple(xMost, aTileSize.width);
232 yMost = mozilla::RoundUpToMultiple(yMost, aTileSize.height);
233
234 this->SetWidth(xMost - this->x);
235 this->SetHeight(yMost - this->y);
236 }
237 };
238 typedef IntRectTyped<UnknownUnits> IntRect;
239
240 template <class units, class F = Float>
241 struct RectTyped : public BaseRect<F, RectTyped<units, F>, PointTyped<units, F>,
242 SizeTyped<units, F>, MarginTyped<units, F> >,
243 public units {
244 static_assert(IsPixel<units>::value,
245 "'units' must be a coordinate system tag");
246
247 typedef BaseRect<F, RectTyped<units, F>, PointTyped<units, F>,
248 SizeTyped<units, F>, MarginTyped<units, F> >
249 Super;
250
RectTypedRectTyped251 RectTyped() : Super() {}
RectTypedRectTyped252 RectTyped(const PointTyped<units, F>& aPos, const SizeTyped<units, F>& aSize)
253 : Super(aPos, aSize) {}
RectTypedRectTyped254 RectTyped(F _x, F _y, F _width, F _height) : Super(_x, _y, _width, _height) {}
RectTypedRectTyped255 explicit RectTyped(const IntRectTyped<units>& rect)
256 : Super(F(rect.X()), F(rect.Y()), F(rect.Width()), F(rect.Height())) {}
257
NudgeToIntegersRectTyped258 void NudgeToIntegers() {
259 NudgeToInteger(&(this->x));
260 NudgeToInteger(&(this->y));
261 NudgeToInteger(&(this->width));
262 NudgeToInteger(&(this->height));
263 }
264
ToIntRectRectTyped265 bool ToIntRect(IntRectTyped<units>* aOut) const {
266 *aOut =
267 IntRectTyped<units>(int32_t(this->X()), int32_t(this->Y()),
268 int32_t(this->Width()), int32_t(this->Height()));
269 return RectTyped<units, F>(F(aOut->X()), F(aOut->Y()), F(aOut->Width()),
270 F(aOut->Height()))
271 .IsEqualEdges(*this);
272 }
273
274 // XXX When all of the code is ported, the following functions to convert to
275 // and from unknown types should be removed.
276
FromUnknownRectRectTyped277 static RectTyped<units, F> FromUnknownRect(
278 const RectTyped<UnknownUnits, F>& rect) {
279 return RectTyped<units, F>(rect.X(), rect.Y(), rect.Width(), rect.Height());
280 }
281
ToUnknownRectRectTyped282 RectTyped<UnknownUnits, F> ToUnknownRect() const {
283 return RectTyped<UnknownUnits, F>(this->X(), this->Y(), this->Width(),
284 this->Height());
285 }
286
287 // This is here only to keep IPDL-generated code happy. DO NOT USE.
288 bool operator==(const RectTyped<units, F>& aRect) const {
289 return RectTyped<units, F>::IsEqualEdges(aRect);
290 }
291
WithinEpsilonOfRectTyped292 bool WithinEpsilonOf(const RectTyped& aOther, F aEpsilon) const {
293 return fabs(this->x - aOther.x) < aEpsilon &&
294 fabs(this->y - aOther.y) < aEpsilon &&
295 fabs(this->width - aOther.width) < aEpsilon &&
296 fabs(this->height - aOther.height) < aEpsilon;
297 }
298 };
299 typedef RectTyped<UnknownUnits> Rect;
300 typedef RectTyped<UnknownUnits, double> RectDouble;
301
302 template <class units>
RoundedToInt(const RectTyped<units> & aRect)303 IntRectTyped<units> RoundedToInt(const RectTyped<units>& aRect) {
304 RectTyped<units> copy(aRect);
305 copy.Round();
306 return IntRectTyped<units>(int32_t(copy.X()), int32_t(copy.Y()),
307 int32_t(copy.Width()), int32_t(copy.Height()));
308 }
309
310 template <class units>
RectIsInt32Safe(const RectTyped<units> & aRect)311 bool RectIsInt32Safe(const RectTyped<units>& aRect) {
312 float min = (float)std::numeric_limits<std::int32_t>::min();
313 float max = (float)std::numeric_limits<std::int32_t>::max();
314 return aRect.x > min && aRect.y > min && aRect.width < max &&
315 aRect.height < max && aRect.XMost() < max && aRect.YMost() < max;
316 }
317
318 template <class units>
RoundedIn(const RectTyped<units> & aRect)319 IntRectTyped<units> RoundedIn(const RectTyped<units>& aRect) {
320 return IntRectTyped<units>::RoundIn(aRect);
321 }
322
323 template <class units>
RoundedOut(const RectTyped<units> & aRect)324 IntRectTyped<units> RoundedOut(const RectTyped<units>& aRect) {
325 return IntRectTyped<units>::RoundOut(aRect);
326 }
327
328 template <class units>
TruncatedToInt(const RectTyped<units> & aRect)329 IntRectTyped<units> TruncatedToInt(const RectTyped<units>& aRect) {
330 return IntRectTyped<units>::Truncate(aRect);
331 }
332
333 template <class units>
IntRectToRect(const IntRectTyped<units> & aRect)334 RectTyped<units> IntRectToRect(const IntRectTyped<units>& aRect) {
335 return RectTyped<units>(aRect.X(), aRect.Y(), aRect.Width(), aRect.Height());
336 }
337
338 // Convenience functions for intersecting and unioning two rectangles wrapped in
339 // Maybes.
340 template <typename Rect>
IntersectMaybeRects(const Maybe<Rect> & a,const Maybe<Rect> & b)341 Maybe<Rect> IntersectMaybeRects(const Maybe<Rect>& a, const Maybe<Rect>& b) {
342 if (!a) {
343 return b;
344 } else if (!b) {
345 return a;
346 } else {
347 return Some(a->Intersect(*b));
348 }
349 }
350 template <typename Rect>
UnionMaybeRects(const Maybe<Rect> & a,const Maybe<Rect> & b)351 Maybe<Rect> UnionMaybeRects(const Maybe<Rect>& a, const Maybe<Rect>& b) {
352 if (!a) {
353 return b;
354 } else if (!b) {
355 return a;
356 } else {
357 return Some(a->Union(*b));
358 }
359 }
360
361 struct RectCornerRadii final {
362 Size radii[eCornerCount];
363
364 RectCornerRadii() = default;
365
RectCornerRadiifinal366 explicit RectCornerRadii(Float radius) {
367 for (const auto i : mozilla::AllPhysicalCorners()) {
368 radii[i].SizeTo(radius, radius);
369 }
370 }
371
RectCornerRadiifinal372 RectCornerRadii(Float radiusX, Float radiusY) {
373 for (const auto i : mozilla::AllPhysicalCorners()) {
374 radii[i].SizeTo(radiusX, radiusY);
375 }
376 }
377
RectCornerRadiifinal378 RectCornerRadii(Float tl, Float tr, Float br, Float bl) {
379 radii[eCornerTopLeft].SizeTo(tl, tl);
380 radii[eCornerTopRight].SizeTo(tr, tr);
381 radii[eCornerBottomRight].SizeTo(br, br);
382 radii[eCornerBottomLeft].SizeTo(bl, bl);
383 }
384
RectCornerRadiifinal385 RectCornerRadii(const Size& tl, const Size& tr, const Size& br,
386 const Size& bl) {
387 radii[eCornerTopLeft] = tl;
388 radii[eCornerTopRight] = tr;
389 radii[eCornerBottomRight] = br;
390 radii[eCornerBottomLeft] = bl;
391 }
392
393 const Size& operator[](size_t aCorner) const { return radii[aCorner]; }
394
395 Size& operator[](size_t aCorner) { return radii[aCorner]; }
396
397 bool operator==(const RectCornerRadii& aOther) const {
398 return TopLeft() == aOther.TopLeft() && TopRight() == aOther.TopRight() &&
399 BottomRight() == aOther.BottomRight() &&
400 BottomLeft() == aOther.BottomLeft();
401 }
402
AreRadiiSamefinal403 bool AreRadiiSame() const {
404 return TopLeft() == TopRight() && TopLeft() == BottomRight() &&
405 TopLeft() == BottomLeft();
406 }
407
Scalefinal408 void Scale(Float aXScale, Float aYScale) {
409 for (const auto i : mozilla::AllPhysicalCorners()) {
410 radii[i].Scale(aXScale, aYScale);
411 }
412 }
413
TopLeftfinal414 const Size TopLeft() const { return radii[eCornerTopLeft]; }
TopLeftfinal415 Size& TopLeft() { return radii[eCornerTopLeft]; }
416
TopRightfinal417 const Size TopRight() const { return radii[eCornerTopRight]; }
TopRightfinal418 Size& TopRight() { return radii[eCornerTopRight]; }
419
BottomRightfinal420 const Size BottomRight() const { return radii[eCornerBottomRight]; }
BottomRightfinal421 Size& BottomRight() { return radii[eCornerBottomRight]; }
422
BottomLeftfinal423 const Size BottomLeft() const { return radii[eCornerBottomLeft]; }
BottomLeftfinal424 Size& BottomLeft() { return radii[eCornerBottomLeft]; }
425
IsEmptyfinal426 bool IsEmpty() const {
427 return TopLeft().IsEmpty() && TopRight().IsEmpty() &&
428 BottomRight().IsEmpty() && BottomLeft().IsEmpty();
429 }
430 };
431
432 /* A rounded rectangle abstraction.
433 *
434 * This can represent a rectangle with a different pair of radii on each corner.
435 *
436 * Note: CoreGraphics and Direct2D only support rounded rectangle with the same
437 * radii on all corners. However, supporting CSS's border-radius requires the
438 * extra flexibility. */
439 struct RoundedRect {
440 typedef mozilla::gfx::RectCornerRadii RectCornerRadii;
441
RoundedRectRoundedRect442 RoundedRect(const Rect& aRect, const RectCornerRadii& aCorners)
443 : rect(aRect), corners(aCorners) {}
444
DeflateRoundedRect445 void Deflate(Float aTopWidth, Float aBottomWidth, Float aLeftWidth,
446 Float aRightWidth) {
447 // deflate the internal rect
448 rect.SetRect(rect.X() + aLeftWidth, rect.Y() + aTopWidth,
449 std::max(0.f, rect.Width() - aLeftWidth - aRightWidth),
450 std::max(0.f, rect.Height() - aTopWidth - aBottomWidth));
451
452 corners.radii[mozilla::eCornerTopLeft].width = std::max(
453 0.f, corners.radii[mozilla::eCornerTopLeft].width - aLeftWidth);
454 corners.radii[mozilla::eCornerTopLeft].height = std::max(
455 0.f, corners.radii[mozilla::eCornerTopLeft].height - aTopWidth);
456
457 corners.radii[mozilla::eCornerTopRight].width = std::max(
458 0.f, corners.radii[mozilla::eCornerTopRight].width - aRightWidth);
459 corners.radii[mozilla::eCornerTopRight].height = std::max(
460 0.f, corners.radii[mozilla::eCornerTopRight].height - aTopWidth);
461
462 corners.radii[mozilla::eCornerBottomLeft].width = std::max(
463 0.f, corners.radii[mozilla::eCornerBottomLeft].width - aLeftWidth);
464 corners.radii[mozilla::eCornerBottomLeft].height = std::max(
465 0.f, corners.radii[mozilla::eCornerBottomLeft].height - aBottomWidth);
466
467 corners.radii[mozilla::eCornerBottomRight].width = std::max(
468 0.f, corners.radii[mozilla::eCornerBottomRight].width - aRightWidth);
469 corners.radii[mozilla::eCornerBottomRight].height = std::max(
470 0.f, corners.radii[mozilla::eCornerBottomRight].height - aBottomWidth);
471 }
472 Rect rect;
473 RectCornerRadii corners;
474 };
475
476 } // namespace gfx
477 } // namespace mozilla
478
479 #endif /* MOZILLA_GFX_RECT_H_ */
480