mirror of
				https://github.com/mozilla/gecko-dev.git
				synced 2025-11-04 02:09:05 +02:00 
			
		
		
		
	The documents of Union and SaturatingUnion promise that |this| will be return when both rects are empty, but the current implementation returns aRect instead. This patch fixes the documents and added a test case. Differential Revision: https://phabricator.services.mozilla.com/D131568
		
			
				
	
	
		
			305 lines
		
	
	
	
		
			11 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			305 lines
		
	
	
	
		
			11 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 | 
						|
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
 | 
						|
/* This Source Code Form is subject to the terms of the Mozilla Public
 | 
						|
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 | 
						|
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | 
						|
 | 
						|
#ifndef MOZILLA_GFX_RECT_ABSOLUTE_H_
 | 
						|
#define MOZILLA_GFX_RECT_ABSOLUTE_H_
 | 
						|
 | 
						|
#include <algorithm>
 | 
						|
#include <cstdint>
 | 
						|
 | 
						|
#include "mozilla/Attributes.h"
 | 
						|
#include "Point.h"
 | 
						|
#include "Rect.h"
 | 
						|
#include "Types.h"
 | 
						|
 | 
						|
namespace mozilla {
 | 
						|
 | 
						|
template <typename>
 | 
						|
struct IsPixel;
 | 
						|
 | 
						|
namespace gfx {
 | 
						|
 | 
						|
/**
 | 
						|
 * A RectAbsolute is similar to a Rect (see BaseRect.h), but represented as
 | 
						|
 * (x1, y1, x2, y2) instead of (x, y, width, height).
 | 
						|
 *
 | 
						|
 * Unless otherwise indicated, methods on this class correspond
 | 
						|
 * to methods on BaseRect.
 | 
						|
 *
 | 
						|
 * The API is currently very bare-bones; it may be extended as needed.
 | 
						|
 *
 | 
						|
 * Do not use this class directly. Subclass it, pass that subclass as the
 | 
						|
 * Sub parameter, and only use that subclass.
 | 
						|
 */
 | 
						|
template <class T, class Sub, class Point, class Rect>
 | 
						|
struct BaseRectAbsolute {
 | 
						|
 protected:
 | 
						|
  T left, top, right, bottom;
 | 
						|
 | 
						|
 public:
 | 
						|
  BaseRectAbsolute() : left(0), top(0), right(0), bottom(0) {}
 | 
						|
  BaseRectAbsolute(T aLeft, T aTop, T aRight, T aBottom)
 | 
						|
      : left(aLeft), top(aTop), right(aRight), bottom(aBottom) {}
 | 
						|
 | 
						|
  MOZ_ALWAYS_INLINE T X() const { return left; }
 | 
						|
  MOZ_ALWAYS_INLINE T Y() const { return top; }
 | 
						|
  MOZ_ALWAYS_INLINE T Width() const { return right - left; }
 | 
						|
  MOZ_ALWAYS_INLINE T Height() const { return bottom - top; }
 | 
						|
  MOZ_ALWAYS_INLINE T XMost() const { return right; }
 | 
						|
  MOZ_ALWAYS_INLINE T YMost() const { return bottom; }
 | 
						|
  MOZ_ALWAYS_INLINE const T& Left() const { return left; }
 | 
						|
  MOZ_ALWAYS_INLINE const T& Right() const { return right; }
 | 
						|
  MOZ_ALWAYS_INLINE const T& Top() const { return top; }
 | 
						|
  MOZ_ALWAYS_INLINE const T& Bottom() const { return bottom; }
 | 
						|
  MOZ_ALWAYS_INLINE T& Left() { return left; }
 | 
						|
  MOZ_ALWAYS_INLINE T& Right() { return right; }
 | 
						|
  MOZ_ALWAYS_INLINE T& Top() { return top; }
 | 
						|
  MOZ_ALWAYS_INLINE T& Bottom() { return bottom; }
 | 
						|
  T Area() const { return Width() * Height(); }
 | 
						|
 | 
						|
  void Inflate(T aD) { Inflate(aD, aD); }
 | 
						|
  void Inflate(T aDx, T aDy) {
 | 
						|
    left -= aDx;
 | 
						|
    top -= aDy;
 | 
						|
    right += aDx;
 | 
						|
    bottom += aDy;
 | 
						|
  }
 | 
						|
 | 
						|
  MOZ_ALWAYS_INLINE void SetBox(T aLeft, T aTop, T aRight, T aBottom) {
 | 
						|
    left = aLeft;
 | 
						|
    top = aTop;
 | 
						|
    right = aRight;
 | 
						|
    bottom = aBottom;
 | 
						|
  }
 | 
						|
  void SetLeftEdge(T aLeft) { left = aLeft; }
 | 
						|
  void SetRightEdge(T aRight) { right = aRight; }
 | 
						|
  void SetTopEdge(T aTop) { top = aTop; }
 | 
						|
  void SetBottomEdge(T aBottom) { bottom = aBottom; }
 | 
						|
 | 
						|
  static Sub FromRect(const Rect& aRect) {
 | 
						|
    if (aRect.Overflows()) {
 | 
						|
      return Sub();
 | 
						|
    }
 | 
						|
    return Sub(aRect.x, aRect.y, aRect.XMost(), aRect.YMost());
 | 
						|
  }
 | 
						|
 | 
						|
  [[nodiscard]] Sub Intersect(const Sub& aOther) const {
 | 
						|
    Sub result;
 | 
						|
    result.left = std::max<T>(left, aOther.left);
 | 
						|
    result.top = std::max<T>(top, aOther.top);
 | 
						|
    result.right = std::min<T>(right, aOther.right);
 | 
						|
    result.bottom = std::min<T>(bottom, aOther.bottom);
 | 
						|
    if (result.right < result.left || result.bottom < result.top) {
 | 
						|
      result.SizeTo(0, 0);
 | 
						|
    }
 | 
						|
    return result;
 | 
						|
  }
 | 
						|
 | 
						|
  bool IsEmpty() const { return right <= left || bottom <= top; }
 | 
						|
 | 
						|
  bool IsEqualEdges(const Sub& aOther) const {
 | 
						|
    return left == aOther.left && top == aOther.top && right == aOther.right &&
 | 
						|
           bottom == aOther.bottom;
 | 
						|
  }
 | 
						|
 | 
						|
  bool IsEqualInterior(const Sub& aRect) const {
 | 
						|
    return IsEqualEdges(aRect) || (IsEmpty() && aRect.IsEmpty());
 | 
						|
  }
 | 
						|
 | 
						|
  MOZ_ALWAYS_INLINE void MoveBy(T aDx, T aDy) {
 | 
						|
    left += aDx;
 | 
						|
    right += aDx;
 | 
						|
    top += aDy;
 | 
						|
    bottom += aDy;
 | 
						|
  }
 | 
						|
  MOZ_ALWAYS_INLINE void MoveBy(const Point& aPoint) {
 | 
						|
    left += aPoint.x;
 | 
						|
    right += aPoint.x;
 | 
						|
    top += aPoint.y;
 | 
						|
    bottom += aPoint.y;
 | 
						|
  }
 | 
						|
  MOZ_ALWAYS_INLINE void SizeTo(T aWidth, T aHeight) {
 | 
						|
    right = left + aWidth;
 | 
						|
    bottom = top + aHeight;
 | 
						|
  }
 | 
						|
 | 
						|
  bool Contains(const Sub& aRect) const {
 | 
						|
    return aRect.IsEmpty() || (left <= aRect.left && aRect.right <= right &&
 | 
						|
                               top <= aRect.top && aRect.bottom <= bottom);
 | 
						|
  }
 | 
						|
  bool Contains(T aX, T aY) const {
 | 
						|
    return (left <= aX && aX < right && top <= aY && aY < bottom);
 | 
						|
  }
 | 
						|
 | 
						|
  bool Intersects(const Sub& aRect) const {
 | 
						|
    return !IsEmpty() && !aRect.IsEmpty() && left < aRect.right &&
 | 
						|
           aRect.left < right && top < aRect.bottom && aRect.top < bottom;
 | 
						|
  }
 | 
						|
 | 
						|
  void SetEmpty() { left = right = top = bottom = 0; }
 | 
						|
 | 
						|
  // Returns the smallest rectangle that contains both the area of both
 | 
						|
  // this and aRect. Thus, empty input rectangles are ignored.
 | 
						|
  // Note: if both rectangles are empty, returns aRect.
 | 
						|
  // WARNING! This is not safe against overflow, prefer using SafeUnion instead
 | 
						|
  // when dealing with int-based rects.
 | 
						|
  [[nodiscard]] Sub Union(const Sub& aRect) const {
 | 
						|
    if (IsEmpty()) {
 | 
						|
      return aRect;
 | 
						|
    } else if (aRect.IsEmpty()) {
 | 
						|
      return *static_cast<const Sub*>(this);
 | 
						|
    } else {
 | 
						|
      return UnionEdges(aRect);
 | 
						|
    }
 | 
						|
  }
 | 
						|
  // Returns the smallest rectangle that contains both the points (including
 | 
						|
  // edges) of both aRect1 and aRect2.
 | 
						|
  // Thus, empty input rectangles are allowed to affect the result.
 | 
						|
  // WARNING! This is not safe against overflow, prefer using SafeUnionEdges
 | 
						|
  // instead when dealing with int-based rects.
 | 
						|
  [[nodiscard]] Sub UnionEdges(const Sub& aRect) const {
 | 
						|
    Sub result;
 | 
						|
    result.left = std::min(left, aRect.left);
 | 
						|
    result.top = std::min(top, aRect.top);
 | 
						|
    result.right = std::max(XMost(), aRect.XMost());
 | 
						|
    result.bottom = std::max(YMost(), aRect.YMost());
 | 
						|
    return result;
 | 
						|
  }
 | 
						|
 | 
						|
  // Scale 'this' by aScale without doing any rounding.
 | 
						|
  void Scale(T aScale) { Scale(aScale, aScale); }
 | 
						|
  // Scale 'this' by aXScale and aYScale, without doing any rounding.
 | 
						|
  void Scale(T aXScale, T aYScale) {
 | 
						|
    right = XMost() * aXScale;
 | 
						|
    bottom = YMost() * aYScale;
 | 
						|
    left = left * aXScale;
 | 
						|
    top = top * aYScale;
 | 
						|
  }
 | 
						|
  // Scale 'this' by aScale, converting coordinates to integers so that the
 | 
						|
  // result is the smallest integer-coordinate rectangle containing the
 | 
						|
  // unrounded result. Note: this can turn an empty rectangle into a non-empty
 | 
						|
  // rectangle
 | 
						|
  void ScaleRoundOut(double aScale) { ScaleRoundOut(aScale, aScale); }
 | 
						|
  // Scale 'this' by aXScale and aYScale, converting coordinates to integers so
 | 
						|
  // that the result is the smallest integer-coordinate rectangle containing the
 | 
						|
  // unrounded result.
 | 
						|
  // Note: this can turn an empty rectangle into a non-empty rectangle
 | 
						|
  void ScaleRoundOut(double aXScale, double aYScale) {
 | 
						|
    right = static_cast<T>(ceil(double(XMost()) * aXScale));
 | 
						|
    bottom = static_cast<T>(ceil(double(YMost()) * aYScale));
 | 
						|
    left = static_cast<T>(floor(double(left) * aXScale));
 | 
						|
    top = static_cast<T>(floor(double(top) * aYScale));
 | 
						|
  }
 | 
						|
  // Scale 'this' by aScale, converting coordinates to integers so that the
 | 
						|
  // result is the largest integer-coordinate rectangle contained by the
 | 
						|
  // unrounded result.
 | 
						|
  void ScaleRoundIn(double aScale) { ScaleRoundIn(aScale, aScale); }
 | 
						|
  // Scale 'this' by aXScale and aYScale, converting coordinates to integers so
 | 
						|
  // that the result is the largest integer-coordinate rectangle contained by
 | 
						|
  // the unrounded result.
 | 
						|
  void ScaleRoundIn(double aXScale, double aYScale) {
 | 
						|
    right = static_cast<T>(floor(double(XMost()) * aXScale));
 | 
						|
    bottom = static_cast<T>(floor(double(YMost()) * aYScale));
 | 
						|
    left = static_cast<T>(ceil(double(left) * aXScale));
 | 
						|
    top = static_cast<T>(ceil(double(top) * aYScale));
 | 
						|
  }
 | 
						|
  // Scale 'this' by 1/aScale, converting coordinates to integers so that the
 | 
						|
  // result is the smallest integer-coordinate rectangle containing the
 | 
						|
  // unrounded result. Note: this can turn an empty rectangle into a non-empty
 | 
						|
  // rectangle
 | 
						|
  void ScaleInverseRoundOut(double aScale) {
 | 
						|
    ScaleInverseRoundOut(aScale, aScale);
 | 
						|
  }
 | 
						|
  // Scale 'this' by 1/aXScale and 1/aYScale, converting coordinates to integers
 | 
						|
  // so that the result is the smallest integer-coordinate rectangle containing
 | 
						|
  // the unrounded result. Note: this can turn an empty rectangle into a
 | 
						|
  // non-empty rectangle
 | 
						|
  void ScaleInverseRoundOut(double aXScale, double aYScale) {
 | 
						|
    right = static_cast<T>(ceil(double(XMost()) / aXScale));
 | 
						|
    bottom = static_cast<T>(ceil(double(YMost()) / aYScale));
 | 
						|
    left = static_cast<T>(floor(double(left) / aXScale));
 | 
						|
    top = static_cast<T>(floor(double(top) / aYScale));
 | 
						|
  }
 | 
						|
  // Scale 'this' by 1/aScale, converting coordinates to integers so that the
 | 
						|
  // result is the largest integer-coordinate rectangle contained by the
 | 
						|
  // unrounded result.
 | 
						|
  void ScaleInverseRoundIn(double aScale) {
 | 
						|
    ScaleInverseRoundIn(aScale, aScale);
 | 
						|
  }
 | 
						|
  // Scale 'this' by 1/aXScale and 1/aYScale, converting coordinates to integers
 | 
						|
  // so that the result is the largest integer-coordinate rectangle contained by
 | 
						|
  // the unrounded result.
 | 
						|
  void ScaleInverseRoundIn(double aXScale, double aYScale) {
 | 
						|
    right = static_cast<T>(floor(double(XMost()) / aXScale));
 | 
						|
    bottom = static_cast<T>(floor(double(YMost()) / aYScale));
 | 
						|
    left = static_cast<T>(ceil(double(left) / aXScale));
 | 
						|
    top = static_cast<T>(ceil(double(top) / aYScale));
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Translate this rectangle to be inside aRect. If it doesn't fit inside
 | 
						|
   * aRect then the dimensions that don't fit will be shrunk so that they
 | 
						|
   * do fit. The resulting rect is returned.
 | 
						|
   */
 | 
						|
  [[nodiscard]] Sub MoveInsideAndClamp(const Sub& aRect) const {
 | 
						|
    T newLeft = std::max(aRect.left, left);
 | 
						|
    T newTop = std::max(aRect.top, top);
 | 
						|
    T width = std::min(aRect.Width(), Width());
 | 
						|
    T height = std::min(aRect.Height(), Height());
 | 
						|
    Sub rect(newLeft, newTop, newLeft + width, newTop + height);
 | 
						|
    newLeft = std::min(rect.right, aRect.right) - width;
 | 
						|
    newTop = std::min(rect.bottom, aRect.bottom) - height;
 | 
						|
    rect.MoveBy(newLeft - rect.left, newTop - rect.top);
 | 
						|
    return rect;
 | 
						|
  }
 | 
						|
 | 
						|
  friend std::ostream& operator<<(
 | 
						|
      std::ostream& stream,
 | 
						|
      const BaseRectAbsolute<T, Sub, Point, Rect>& aRect) {
 | 
						|
    return stream << "(l=" << aRect.left << ", t=" << aRect.top
 | 
						|
                  << ", r=" << aRect.right << ", b=" << aRect.bottom << ')';
 | 
						|
  }
 | 
						|
};
 | 
						|
 | 
						|
template <class Units>
 | 
						|
struct IntRectAbsoluteTyped
 | 
						|
    : public BaseRectAbsolute<int32_t, IntRectAbsoluteTyped<Units>,
 | 
						|
                              IntPointTyped<Units>, IntRectTyped<Units>>,
 | 
						|
      public Units {
 | 
						|
  static_assert(IsPixel<Units>::value,
 | 
						|
                "'units' must be a coordinate system tag");
 | 
						|
  typedef BaseRectAbsolute<int32_t, IntRectAbsoluteTyped<Units>,
 | 
						|
                           IntPointTyped<Units>, IntRectTyped<Units>>
 | 
						|
      Super;
 | 
						|
  typedef IntParam<int32_t> ToInt;
 | 
						|
 | 
						|
  IntRectAbsoluteTyped() : Super() {}
 | 
						|
  IntRectAbsoluteTyped(ToInt aLeft, ToInt aTop, ToInt aRight, ToInt aBottom)
 | 
						|
      : Super(aLeft.value, aTop.value, aRight.value, aBottom.value) {}
 | 
						|
};
 | 
						|
 | 
						|
template <class Units>
 | 
						|
struct RectAbsoluteTyped
 | 
						|
    : public BaseRectAbsolute<Float, RectAbsoluteTyped<Units>,
 | 
						|
                              PointTyped<Units>, RectTyped<Units>>,
 | 
						|
      public Units {
 | 
						|
  static_assert(IsPixel<Units>::value,
 | 
						|
                "'units' must be a coordinate system tag");
 | 
						|
  typedef BaseRectAbsolute<Float, RectAbsoluteTyped<Units>, PointTyped<Units>,
 | 
						|
                           RectTyped<Units>>
 | 
						|
      Super;
 | 
						|
 | 
						|
  RectAbsoluteTyped() : Super() {}
 | 
						|
  RectAbsoluteTyped(Float aLeft, Float aTop, Float aRight, Float aBottom)
 | 
						|
      : Super(aLeft, aTop, aRight, aBottom) {}
 | 
						|
};
 | 
						|
 | 
						|
typedef IntRectAbsoluteTyped<UnknownUnits> IntRectAbsolute;
 | 
						|
 | 
						|
}  // namespace gfx
 | 
						|
}  // namespace mozilla
 | 
						|
 | 
						|
#endif /* MOZILLA_GFX_RECT_ABSOLUTE_H_ */
 |