Blob Blame History Raw
/* -*- 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 nsRegion_h__
#define nsRegion_h__

#include <stddef.h>                 // for size_t
#include <stdint.h>                 // for uint32_t, uint64_t
#include <sys/types.h>              // for int32_t
#include <ostream>                  // for std::ostream
#include "nsCoord.h"                // for nscoord
#include "nsError.h"                // for nsresult
#include "nsPoint.h"                // for nsIntPoint, nsPoint
#include "nsRect.h"                 // for mozilla::gfx::IntRect, nsRect
#include "nsMargin.h"               // for nsIntMargin
#include "nsRegionFwd.h"            // for nsIntRegion
#include "nsString.h"               // for nsCString
#include "xpcom-config.h"           // for CPP_THROW_NEW
#include "mozilla/ArrayView.h"      // for ArrayView
#include "mozilla/Move.h"           // for mozilla::Move
#include "mozilla/gfx/MatrixFwd.h"  // for mozilla::gfx::Matrix4x4

#include "pixman.h"

/* For information on the internal representation look at pixman-region.c
 *
 * This replaces an older homebrew implementation of nsRegion. The
 * representation used here may use more rectangles than nsRegion however, the
 * representation is canonical.  This means that there's no need for an
 * Optimize() method because for a paticular region there is only one
 * representation. This means that nsIntRegion will have more predictable
 * performance characteristics than the old nsRegion and should not become
 * degenerate.
 *
 * The pixman region code originates from X11 which has spread to a variety of
 * projects including Qt, Gtk, Wine. It should perform reasonably well.
 */

enum class VisitSide { TOP, BOTTOM, LEFT, RIGHT };

class nsRegion {
 public:
  typedef nsRect RectType;
  typedef nsPoint PointType;
  typedef nsMargin MarginType;

  nsRegion() { pixman_region32_init(&mImpl); }
  MOZ_IMPLICIT nsRegion(const nsRect& aRect) {
    pixman_region32_init_rect(&mImpl, aRect.X(), aRect.Y(), aRect.Width(),
                              aRect.Height());
  }
  explicit nsRegion(mozilla::gfx::ArrayView<pixman_box32_t> aRects) {
    pixman_region32_init_rects(&mImpl, aRects.Data(), aRects.Length());
  }
  nsRegion(const nsRegion& aRegion) {
    pixman_region32_init(&mImpl);
    pixman_region32_copy(&mImpl, aRegion.Impl());
  }
  nsRegion(nsRegion&& aRegion) {
    mImpl = aRegion.mImpl;
    pixman_region32_init(&aRegion.mImpl);
  }
  nsRegion& operator=(nsRegion&& aRegion) {
    pixman_region32_fini(&mImpl);
    mImpl = aRegion.mImpl;
    pixman_region32_init(&aRegion.mImpl);
    return *this;
  }
  ~nsRegion() { pixman_region32_fini(&mImpl); }
  nsRegion& operator=(const nsRect& aRect) {
    Copy(aRect);
    return *this;
  }
  nsRegion& operator=(const nsRegion& aRegion) {
    Copy(aRegion);
    return *this;
  }
  bool operator==(const nsRegion& aRgn) const { return IsEqual(aRgn); }
  bool operator!=(const nsRegion& aRgn) const { return !(*this == aRgn); }

  friend std::ostream& operator<<(std::ostream& stream, const nsRegion& m);

  void Swap(nsRegion* aOther) {
    pixman_region32_t tmp = mImpl;
    mImpl = aOther->mImpl;
    aOther->mImpl = tmp;
  }

  void AndWith(const nsRegion& aOther) { And(*this, aOther); }
  void AndWith(const nsRect& aOther) { And(*this, aOther); }
  nsRegion& And(const nsRegion& aRgn1, const nsRegion& aRgn2) {
    pixman_region32_intersect(&mImpl, aRgn1.Impl(), aRgn2.Impl());
    return *this;
  }
  nsRegion& And(const nsRect& aRect, const nsRegion& aRegion) {
    return And(aRegion, aRect);
  }
  nsRegion& And(const nsRegion& aRegion, const nsRect& aRect) {
    pixman_region32_intersect_rect(&mImpl, aRegion.Impl(), aRect.X(), aRect.Y(),
                                   aRect.Width(), aRect.Height());
    return *this;
  }
  nsRegion& And(const nsRect& aRect1, const nsRect& aRect2) {
    nsRect TmpRect;

    TmpRect.IntersectRect(aRect1, aRect2);
    return Copy(TmpRect);
  }

  nsRegion& OrWith(const nsRegion& aOther) { return Or(*this, aOther); }
  nsRegion& OrWith(const nsRect& aOther) { return Or(*this, aOther); }
  nsRegion& Or(const nsRegion& aRgn1, const nsRegion& aRgn2) {
    pixman_region32_union(&mImpl, aRgn1.Impl(), aRgn2.Impl());
    return *this;
  }
  nsRegion& Or(const nsRegion& aRegion, const nsRect& aRect) {
    pixman_region32_union_rect(&mImpl, aRegion.Impl(), aRect.X(), aRect.Y(),
                               aRect.Width(), aRect.Height());
    return *this;
  }
  nsRegion& Or(const nsRect& aRect, const nsRegion& aRegion) {
    return Or(aRegion, aRect);
  }
  nsRegion& Or(const nsRect& aRect1, const nsRect& aRect2) {
    Copy(aRect1);
    return Or(*this, aRect2);
  }

  nsRegion& XorWith(const nsRegion& aOther) { return Xor(*this, aOther); }
  nsRegion& XorWith(const nsRect& aOther) { return Xor(*this, aOther); }
  nsRegion& Xor(const nsRegion& aRgn1, const nsRegion& aRgn2) {
    // this could be implemented better if pixman had direct
    // support for xoring regions.
    nsRegion p;
    p.Sub(aRgn1, aRgn2);
    nsRegion q;
    q.Sub(aRgn2, aRgn1);
    return Or(p, q);
  }
  nsRegion& Xor(const nsRegion& aRegion, const nsRect& aRect) {
    return Xor(aRegion, nsRegion(aRect));
  }
  nsRegion& Xor(const nsRect& aRect, const nsRegion& aRegion) {
    return Xor(nsRegion(aRect), aRegion);
  }
  nsRegion& Xor(const nsRect& aRect1, const nsRect& aRect2) {
    return Xor(nsRegion(aRect1), nsRegion(aRect2));
  }

  nsRegion ToAppUnits(nscoord aAppUnitsPerPixel) const;

  nsRegion& SubOut(const nsRegion& aOther) { return Sub(*this, aOther); }
  nsRegion& SubOut(const nsRect& aOther) { return Sub(*this, aOther); }
  nsRegion& Sub(const nsRegion& aRgn1, const nsRegion& aRgn2) {
    pixman_region32_subtract(&mImpl, aRgn1.Impl(), aRgn2.Impl());
    return *this;
  }
  nsRegion& Sub(const nsRegion& aRegion, const nsRect& aRect) {
    return Sub(aRegion, nsRegion(aRect));
  }
  nsRegion& Sub(const nsRect& aRect, const nsRegion& aRegion) {
    return Sub(nsRegion(aRect), aRegion);
  }
  nsRegion& Sub(const nsRect& aRect1, const nsRect& aRect2) {
    Copy(aRect1);
    return Sub(*this, aRect2);
  }

  /**
   * Returns true iff the given point is inside the region. A region
   * created from a rect (x=0, y=0, w=100, h=100) will NOT contain
   * the point x=100, y=100.
   */
  bool Contains(int aX, int aY) const {
    return pixman_region32_contains_point(Impl(), aX, aY, nullptr);
  }
  bool Contains(const nsRect& aRect) const {
    pixman_box32_t box = RectToBox(aRect);
    return pixman_region32_contains_rectangle(Impl(), &box) == PIXMAN_REGION_IN;
  }
  bool Contains(const nsRegion& aRgn) const;
  bool Intersects(const nsRect& aRect) const;

  void MoveBy(int32_t aXOffset, int32_t aYOffset) {
    MoveBy(nsPoint(aXOffset, aYOffset));
  }
  void MoveBy(nsPoint aPt) { pixman_region32_translate(&mImpl, aPt.x, aPt.y); }
  void SetEmpty() { pixman_region32_clear(&mImpl); }

  nsRegion MovedBy(int32_t aXOffset, int32_t aYOffset) const {
    return MovedBy(nsPoint(aXOffset, aYOffset));
  }
  nsRegion MovedBy(const nsPoint& aPt) const {
    nsRegion copy(*this);
    copy.MoveBy(aPt);
    return copy;
  }

  nsRegion Intersect(const nsRegion& aOther) const {
    nsRegion intersection;
    intersection.And(*this, aOther);
    return intersection;
  }

  void Inflate(const nsMargin& aMargin);

  nsRegion Inflated(const nsMargin& aMargin) const {
    nsRegion copy(*this);
    copy.Inflate(aMargin);
    return copy;
  }

  bool IsEmpty() const { return !pixman_region32_not_empty(Impl()); }
  bool IsComplex() const { return GetNumRects() > 1; }
  bool IsEqual(const nsRegion& aRegion) const {
    return pixman_region32_equal(Impl(), aRegion.Impl());
  }
  uint32_t GetNumRects() const {
    // Work around pixman bug. Sometimes pixman creates regions with 1 rect
    // that's empty.
    uint32_t result = pixman_region32_n_rects(Impl());
    return (result == 1 && GetBounds().IsEmpty()) ? 0 : result;
  }
  const nsRect GetBounds() const { return BoxToRect(mImpl.extents); }
  uint64_t Area() const;

  /**
   * Return this region scaled to a different appunits per pixel (APP) ratio.
   * This applies nsRect::ScaleToOtherAppUnitsRoundOut/In to each rect of the
   * region.
   * @param aFromAPP the APP to scale from
   * @param aToAPP the APP to scale to
   * @note this can turn an empty region into a non-empty region
   */
  MOZ_MUST_USE nsRegion ScaleToOtherAppUnitsRoundOut(int32_t aFromAPP,
                                                     int32_t aToAPP) const;
  MOZ_MUST_USE nsRegion ScaleToOtherAppUnitsRoundIn(int32_t aFromAPP,
                                                    int32_t aToAPP) const;
  nsRegion& ScaleRoundOut(float aXScale, float aYScale);
  nsRegion& ScaleInverseRoundOut(float aXScale, float aYScale);
  nsRegion& Transform(const mozilla::gfx::Matrix4x4& aTransform);
  nsIntRegion ScaleToOutsidePixels(float aXScale, float aYScale,
                                   nscoord aAppUnitsPerPixel) const;
  nsIntRegion ScaleToInsidePixels(float aXScale, float aYScale,
                                  nscoord aAppUnitsPerPixel) const;
  nsIntRegion ScaleToNearestPixels(float aXScale, float aYScale,
                                   nscoord aAppUnitsPerPixel) const;
  nsIntRegion ToOutsidePixels(nscoord aAppUnitsPerPixel) const;
  nsIntRegion ToNearestPixels(nscoord aAppUnitsPerPixel) const;

  /**
   * Gets the largest rectangle contained in the region.
   * @param aContainingRect if non-empty, we choose a rectangle that
   * maximizes the area intersecting with aContainingRect (and break ties by
   * then choosing the largest rectangle overall)
   */
  nsRect GetLargestRectangle(const nsRect& aContainingRect = nsRect()) const;

  /**
   * Make sure the region has at most aMaxRects by adding area to it
   * if necessary. The simplified region will be a superset of the
   * original region. The simplified region's bounding box will be
   * the same as for the current region.
   */
  void SimplifyOutward(uint32_t aMaxRects);
  /**
   * Simplify the region by adding at most aThreshold area between spans of
   * rects.  The simplified region will be a superset of the original region.
   * The simplified region's bounding box will be the same as for the current
   * region.
   */
  void SimplifyOutwardByArea(uint32_t aThreshold);
  /**
   * Make sure the region has at most aMaxRects by removing area from
   * it if necessary. The simplified region will be a subset of the
   * original region.
   */
  void SimplifyInward(uint32_t aMaxRects);

  /**
   * VisitEdges is a weird kind of function that we use for padding
   * out surfaces to prevent texture filtering artifacts.
   * It calls the visitFn callback for each of the exterior edges of
   * the regions. The top and bottom edges will be expanded 1 pixel
   * to the left and right if there's an outside corner. The order
   * the edges are visited is not guaranteed.
   *
   * visitFn has a side parameter that can be TOP,BOTTOM,LEFT,RIGHT
   * and specifies which kind of edge is being visited. x1, y1, x2, y2
   * are the coordinates of the line. (x1 == x2) || (y1 == y2)
   */
  typedef void (*visitFn)(void* closure, VisitSide side, int x1, int y1, int x2,
                          int y2);
  void VisitEdges(visitFn, void* closure);

  nsCString ToString() const;

  class RectIterator {
    int mCurrent;         // Index of the current entry
    int mLimit;           // Index one past the final entry.
    mutable nsRect mTmp;  // The most recently gotten rectangle.
    pixman_box32_t* mBoxes;

   public:
    explicit RectIterator(const nsRegion& aRegion) {
      mCurrent = 0;
      mBoxes = pixman_region32_rectangles(aRegion.Impl(), &mLimit);
      // Work around pixman bug. Sometimes pixman creates regions with 1 rect
      // that's empty.
      if (mLimit == 1 && nsRegion::BoxToRect(mBoxes[0]).IsEmpty()) {
        mLimit = 0;
      }
    }

    bool Done() const { return mCurrent == mLimit; }

    const nsRect& Get() const {
      MOZ_ASSERT(!Done());
      mTmp = nsRegion::BoxToRect(mBoxes[mCurrent]);
      NS_ASSERTION(!mTmp.IsEmpty(), "Shouldn't return empty rect");
      return mTmp;
    }

    void Next() {
      MOZ_ASSERT(!Done());
      mCurrent++;
    }
  };

  RectIterator RectIter() const { return RectIterator(*this); }

  static inline pixman_box32_t RectToBox(const nsRect& aRect) {
    pixman_box32_t box = {aRect.X(), aRect.Y(), aRect.XMost(), aRect.YMost()};
    return box;
  }

  static inline pixman_box32_t RectToBox(const mozilla::gfx::IntRect& aRect) {
    pixman_box32_t box = {aRect.X(), aRect.Y(), aRect.XMost(), aRect.YMost()};
    return box;
  }

  static inline nsRect BoxToRect(const pixman_box32_t& aBox) {
    return nsRect(aBox.x1, aBox.y1, aBox.x2 - aBox.x1, aBox.y2 - aBox.y1);
  }

 private:
  pixman_region32_t mImpl;

#ifndef MOZ_TREE_PIXMAN
  // For compatibility with pixman versions older than 0.25.2.
  static inline void pixman_region32_clear(pixman_region32_t* region) {
    pixman_region32_fini(region);
    pixman_region32_init(region);
  }
#endif

  nsIntRegion ToPixels(nscoord aAppUnitsPerPixel, bool aOutsidePixels) const;

  nsRegion& Copy(const nsRegion& aRegion) {
    pixman_region32_copy(&mImpl, aRegion.Impl());
    return *this;
  }

  nsRegion& Copy(const nsRect& aRect) {
    // pixman needs to distinguish between an empty region and a region
    // with one rect so that it can return a different number of rectangles.
    // Empty rect: data = empty_box
    //     1 rect: data = null
    //    >1 rect: data = rects
    if (aRect.IsEmpty()) {
      pixman_region32_clear(&mImpl);
    } else {
      pixman_box32_t box = RectToBox(aRect);
      pixman_region32_reset(&mImpl, &box);
    }
    return *this;
  }

  pixman_region32_t* Impl() const {
    return const_cast<pixman_region32_t*>(&mImpl);
  }
};

namespace mozilla {
namespace gfx {

/**
 * BaseIntRegions use int32_t coordinates.
 */
template <typename Derived, typename Rect, typename Point, typename Margin>
class BaseIntRegion {
  friend class ::nsRegion;

  // Give access to all specializations of IntRegionTyped, not just ones that
  // derive from this specialization of BaseIntRegion.
  template <typename units>
  friend class IntRegionTyped;

 public:
  typedef Rect RectType;
  typedef Point PointType;
  typedef Margin MarginType;

  BaseIntRegion() {}
  MOZ_IMPLICIT BaseIntRegion(const Rect& aRect) : mImpl(ToRect(aRect)) {}
  explicit BaseIntRegion(mozilla::gfx::ArrayView<pixman_box32_t> aRects)
      : mImpl(aRects) {}
  BaseIntRegion(const BaseIntRegion& aRegion) : mImpl(aRegion.mImpl) {}
  BaseIntRegion(BaseIntRegion&& aRegion)
      : mImpl(mozilla::Move(aRegion.mImpl)) {}
  Derived& operator=(const Rect& aRect) {
    mImpl = ToRect(aRect);
    return This();
  }
  Derived& operator=(const Derived& aRegion) {
    mImpl = aRegion.mImpl;
    return This();
  }
  Derived& operator=(Derived&& aRegion) {
    mImpl = mozilla::Move(aRegion.mImpl);
    return This();
  }

  bool operator==(const Derived& aRgn) const { return IsEqual(aRgn); }
  bool operator!=(const Derived& aRgn) const { return !(*this == aRgn); }

  friend std::ostream& operator<<(std::ostream& stream, const Derived& m) {
    return stream << m.mImpl;
  }

  void Swap(Derived* aOther) { mImpl.Swap(&aOther->mImpl); }

  void AndWith(const Derived& aOther) { And(This(), aOther); }
  void AndWith(const Rect& aOther) { And(This(), aOther); }
  Derived& And(const Derived& aRgn1, const Derived& aRgn2) {
    mImpl.And(aRgn1.mImpl, aRgn2.mImpl);
    return This();
  }
  Derived& And(const Derived& aRegion, const Rect& aRect) {
    mImpl.And(aRegion.mImpl, ToRect(aRect));
    return This();
  }
  Derived& And(const Rect& aRect, const Derived& aRegion) {
    return And(aRegion, aRect);
  }
  Derived& And(const Rect& aRect1, const Rect& aRect2) {
    Rect TmpRect;

    TmpRect.IntersectRect(aRect1, aRect2);
    mImpl = ToRect(TmpRect);
    return This();
  }

  Derived& OrWith(const Derived& aOther) { return Or(This(), aOther); }
  Derived& OrWith(const Rect& aOther) { return Or(This(), aOther); }
  Derived& Or(const Derived& aRgn1, const Derived& aRgn2) {
    mImpl.Or(aRgn1.mImpl, aRgn2.mImpl);
    return This();
  }
  Derived& Or(const Derived& aRegion, const Rect& aRect) {
    mImpl.Or(aRegion.mImpl, ToRect(aRect));
    return This();
  }
  Derived& Or(const Rect& aRect, const Derived& aRegion) {
    return Or(aRegion, aRect);
  }
  Derived& Or(const Rect& aRect1, const Rect& aRect2) {
    mImpl = ToRect(aRect1);
    return Or(This(), aRect2);
  }

  Derived& XorWith(const Derived& aOther) { return Xor(This(), aOther); }
  Derived& XorWith(const Rect& aOther) { return Xor(This(), aOther); }
  Derived& Xor(const Derived& aRgn1, const Derived& aRgn2) {
    mImpl.Xor(aRgn1.mImpl, aRgn2.mImpl);
    return This();
  }
  Derived& Xor(const Derived& aRegion, const Rect& aRect) {
    mImpl.Xor(aRegion.mImpl, ToRect(aRect));
    return This();
  }
  Derived& Xor(const Rect& aRect, const Derived& aRegion) {
    return Xor(aRegion, aRect);
  }
  Derived& Xor(const Rect& aRect1, const Rect& aRect2) {
    mImpl = ToRect(aRect1);
    return Xor(This(), aRect2);
  }

  Derived& SubOut(const Derived& aOther) { return Sub(This(), aOther); }
  Derived& SubOut(const Rect& aOther) { return Sub(This(), aOther); }
  Derived& Sub(const Derived& aRgn1, const Derived& aRgn2) {
    mImpl.Sub(aRgn1.mImpl, aRgn2.mImpl);
    return This();
  }
  Derived& Sub(const Derived& aRegion, const Rect& aRect) {
    mImpl.Sub(aRegion.mImpl, ToRect(aRect));
    return This();
  }
  Derived& Sub(const Rect& aRect, const Derived& aRegion) {
    return Sub(Derived(aRect), aRegion);
  }
  Derived& Sub(const Rect& aRect1, const Rect& aRect2) {
    mImpl = ToRect(aRect1);
    return Sub(This(), aRect2);
  }

  /**
   * Returns true iff the given point is inside the region. A region
   * created from a rect (x=0, y=0, w=100, h=100) will NOT contain
   * the point x=100, y=100.
   */
  bool Contains(int aX, int aY) const { return mImpl.Contains(aX, aY); }
  bool Contains(const Rect& aRect) const {
    return mImpl.Contains(ToRect(aRect));
  }
  bool Contains(const Derived& aRgn) const {
    return mImpl.Contains(aRgn.mImpl);
  }
  bool Intersects(const Rect& aRect) const {
    return mImpl.Intersects(ToRect(aRect));
  }

  void MoveBy(int32_t aXOffset, int32_t aYOffset) {
    MoveBy(Point(aXOffset, aYOffset));
  }
  void MoveBy(Point aPt) { mImpl.MoveBy(aPt.x, aPt.y); }
  Derived MovedBy(int32_t aXOffset, int32_t aYOffset) const {
    return MovedBy(Point(aXOffset, aYOffset));
  }
  Derived MovedBy(const Point& aPt) const {
    Derived copy(This());
    copy.MoveBy(aPt);
    return copy;
  }

  Derived Intersect(const Derived& aOther) const {
    Derived intersection;
    intersection.And(This(), aOther);
    return intersection;
  }

  void Inflate(const Margin& aMargin) {
    mImpl.Inflate(
        nsMargin(aMargin.top, aMargin.right, aMargin.bottom, aMargin.left));
  }
  Derived Inflated(const Margin& aMargin) const {
    Derived copy(This());
    copy.Inflate(aMargin);
    return copy;
  }

  void SetEmpty() { mImpl.SetEmpty(); }

  bool IsEmpty() const { return mImpl.IsEmpty(); }
  bool IsComplex() const { return mImpl.IsComplex(); }
  bool IsEqual(const Derived& aRegion) const {
    return mImpl.IsEqual(aRegion.mImpl);
  }
  uint32_t GetNumRects() const { return mImpl.GetNumRects(); }
  Rect GetBounds() const { return FromRect(mImpl.GetBounds()); }
  uint64_t Area() const { return mImpl.Area(); }
  nsRegion ToAppUnits(nscoord aAppUnitsPerPixel) const {
    nsRegion result;
    for (auto iter = RectIter(); !iter.Done(); iter.Next()) {
      nsRect appRect = ::ToAppUnits(iter.Get(), aAppUnitsPerPixel);
      result.Or(result, appRect);
    }
    return result;
  }
  Rect GetLargestRectangle(const Rect& aContainingRect = Rect()) const {
    return FromRect(mImpl.GetLargestRectangle(ToRect(aContainingRect)));
  }

  Derived& ScaleRoundOut(float aXScale, float aYScale) {
    mImpl.ScaleRoundOut(aXScale, aYScale);
    return This();
  }

  Derived& ScaleInverseRoundOut(float aXScale, float aYScale) {
    mImpl.ScaleInverseRoundOut(aXScale, aYScale);
    return This();
  }

  // Prefer using TransformBy(matrix, region) from UnitTransforms.h,
  // as applying the transform should typically change the unit system.
  // TODO(botond): Move this to IntRegionTyped and disable it for
  //               unit != UnknownUnits.
  Derived& Transform(const mozilla::gfx::Matrix4x4& aTransform) {
    mImpl.Transform(aTransform);
    return This();
  }

  /**
   * Make sure the region has at most aMaxRects by adding area to it
   * if necessary. The simplified region will be a superset of the
   * original region. The simplified region's bounding box will be
   * the same as for the current region.
   */
  void SimplifyOutward(uint32_t aMaxRects) { mImpl.SimplifyOutward(aMaxRects); }
  void SimplifyOutwardByArea(uint32_t aThreshold) {
    mImpl.SimplifyOutwardByArea(aThreshold);
  }
  /**
   * Make sure the region has at most aMaxRects by removing area from
   * it if necessary. The simplified region will be a subset of the
   * original region.
   */
  void SimplifyInward(uint32_t aMaxRects) { mImpl.SimplifyInward(aMaxRects); }

  typedef void (*visitFn)(void* closure, VisitSide side, int x1, int y1, int x2,
                          int y2);
  void VisitEdges(visitFn visit, void* closure) {
    mImpl.VisitEdges(visit, closure);
  }

  nsCString ToString() const { return mImpl.ToString(); }

  class RectIterator {
    nsRegion::RectIterator mImpl;  // The underlying iterator.
    mutable Rect mTmp;             // The most recently gotten rectangle.

   public:
    explicit RectIterator(const BaseIntRegion& aRegion)
        : mImpl(aRegion.mImpl) {}

    bool Done() const { return mImpl.Done(); }

    const Rect& Get() const {
      mTmp = FromRect(mImpl.Get());
      return mTmp;
    }

    void Next() { mImpl.Next(); }
  };

  RectIterator RectIter() const { return RectIterator(*this); }

 protected:
  // Expose enough to derived classes from them to define conversions
  // between different types of BaseIntRegions.
  explicit BaseIntRegion(const nsRegion& aImpl) : mImpl(aImpl) {}
  const nsRegion& Impl() const { return mImpl; }

 private:
  nsRegion mImpl;

  static nsRect ToRect(const Rect& aRect) {
    return nsRect(aRect.X(), aRect.Y(), aRect.Width(), aRect.Height());
  }
  static Rect FromRect(const nsRect& aRect) {
    return Rect(aRect.X(), aRect.Y(), aRect.Width(), aRect.Height());
  }

  Derived& This() { return *static_cast<Derived*>(this); }
  const Derived& This() const { return *static_cast<const Derived*>(this); }
};

template <class units>
class IntRegionTyped
    : public BaseIntRegion<IntRegionTyped<units>, IntRectTyped<units>,
                           IntPointTyped<units>, IntMarginTyped<units>> {
  typedef BaseIntRegion<IntRegionTyped<units>, IntRectTyped<units>,
                        IntPointTyped<units>, IntMarginTyped<units>>
      Super;

  // Make other specializations of IntRegionTyped friends.
  template <typename OtherUnits>
  friend class IntRegionTyped;

  static_assert(IsPixel<units>::value,
                "'units' must be a coordinate system tag");

 public:
  typedef IntRectTyped<units> RectType;
  typedef IntPointTyped<units> PointType;
  typedef IntMarginTyped<units> MarginType;

  // Forward constructors.
  IntRegionTyped() {}
  MOZ_IMPLICIT IntRegionTyped(const IntRectTyped<units>& aRect)
      : Super(aRect) {}
  IntRegionTyped(const IntRegionTyped& aRegion) : Super(aRegion) {}
  explicit IntRegionTyped(mozilla::gfx::ArrayView<pixman_box32_t> aRects)
      : Super(aRects) {}
  IntRegionTyped(IntRegionTyped&& aRegion) : Super(mozilla::Move(aRegion)) {}

  // Assignment operators need to be forwarded as well, otherwise the compiler
  // will declare deleted ones.
  IntRegionTyped& operator=(const IntRegionTyped& aRegion) {
    return Super::operator=(aRegion);
  }
  IntRegionTyped& operator=(IntRegionTyped&& aRegion) {
    return Super::operator=(mozilla::Move(aRegion));
  }

  static IntRegionTyped FromUnknownRegion(const IntRegion& aRegion) {
    return IntRegionTyped(aRegion.Impl());
  }
  IntRegion ToUnknownRegion() const {
    // Need |this->| because Impl() is defined in a dependent base class.
    return IntRegion(this->Impl());
  }

 private:
  // This is deliberately private, so calling code uses FromUnknownRegion().
  explicit IntRegionTyped(const nsRegion& aRegion) : Super(aRegion) {}
};

}  // namespace gfx
}  // namespace mozilla

typedef mozilla::gfx::IntRegion nsIntRegion;

#endif