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/. */

#include "nsRegion.h"
#include "nsTArray.h"
#include "gfxUtils.h"
#include "mozilla/ToString.h"

bool nsRegion::Contains(const nsRegion &aRgn) const {
  // XXX this could be made faster by iterating over
  // both regions at the same time some how
  for (auto iter = aRgn.RectIter(); !iter.Done(); iter.Next()) {
    if (!Contains(iter.Get())) {
      return false;
    }
  }
  return true;
}

bool nsRegion::Intersects(const nsRect &aRect) const {
  // XXX this could be made faster by using pixman_region32_contains_rect
  for (auto iter = RectIter(); !iter.Done(); iter.Next()) {
    if (iter.Get().Intersects(aRect)) {
      return true;
    }
  }
  return false;
}

void nsRegion::Inflate(const nsMargin &aMargin) {
  int n;
  pixman_box32_t *boxes = pixman_region32_rectangles(&mImpl, &n);
  for (int i = 0; i < n; i++) {
    nsRect rect = BoxToRect(boxes[i]);
    rect.Inflate(aMargin);
    boxes[i] = RectToBox(rect);
  }

  pixman_region32_t region;
  // This will union all of the rectangles and runs in about O(n lg(n))
  pixman_region32_init_rects(&region, boxes, n);

  pixman_region32_fini(&mImpl);
  mImpl = region;
}

void nsRegion::SimplifyOutward(uint32_t aMaxRects) {
  MOZ_ASSERT(aMaxRects >= 1, "Invalid max rect count");

  if (GetNumRects() <= aMaxRects) return;

  pixman_box32_t *boxes;
  int n;
  boxes = pixman_region32_rectangles(&mImpl, &n);

  // Try combining rects in horizontal bands into a single rect
  int dest = 0;
  for (int src = 1; src < n; src++) {
    // The goal here is to try to keep groups of rectangles that are vertically
    // discontiguous as separate rectangles in the final region. This is
    // simple and fast to implement and page contents tend to vary more
    // vertically than horizontally (which is why our rectangles are stored
    // sorted by y-coordinate, too).
    //
    // Note: if boxes share y1 because of the canonical representation they
    // will share y2
    while ((src < (n)) && boxes[dest].y1 == boxes[src].y1) {
      // merge box[i] and box[i+1]
      boxes[dest].x2 = boxes[src].x2;
      src++;
    }
    if (src < n) {
      dest++;
      boxes[dest] = boxes[src];
    }
  }

  uint32_t reducedCount = dest + 1;
  // pixman has a special representation for
  // regions of 1 rectangle. So just use the
  // bounds in that case
  if (reducedCount > 1 && reducedCount <= aMaxRects) {
    // reach into pixman and lower the number
    // of rects stored in data.
    mImpl.data->numRects = reducedCount;
  } else {
    *this = GetBounds();
  }
}

// compute the covered area difference between two rows.
// by iterating over both rows simultaneously and adding up
// the additional increase in area caused by extending each
// of the rectangles to the combined height of both rows
static uint32_t ComputeMergedAreaIncrease(pixman_box32_t *topRects,
                                          pixman_box32_t *topRectsEnd,
                                          pixman_box32_t *bottomRects,
                                          pixman_box32_t *bottomRectsEnd) {
  uint32_t totalArea = 0;
  struct pt {
    int32_t x, y;
  };

  pt *i = (pt *)topRects;
  pt *end_i = (pt *)topRectsEnd;
  pt *j = (pt *)bottomRects;
  pt *end_j = (pt *)bottomRectsEnd;
  bool top = false;
  bool bottom = false;

  int cur_x = i->x;
  bool top_next = top;
  bool bottom_next = bottom;
  // XXX: we could probably simplify this condition and perhaps move it into the
  // loop below
  if (j->x < cur_x) {
    cur_x = j->x;
    j++;
    bottom_next = !bottom;
  } else if (j->x == cur_x) {
    i++;
    top_next = !top;
    bottom_next = !bottom;
    j++;
  } else {
    top_next = !top;
    i++;
  }

  int topRectsHeight = topRects->y2 - topRects->y1;
  int bottomRectsHeight = bottomRects->y2 - bottomRects->y1;
  int inbetweenHeight = bottomRects->y1 - topRects->y2;
  int width = cur_x;
  // top and bottom are the in-status to the left of cur_x
  do {
    if (top && !bottom) {
      totalArea += (inbetweenHeight + bottomRectsHeight) * width;
    } else if (bottom && !top) {
      totalArea += (inbetweenHeight + topRectsHeight) * width;
    } else if (bottom && top) {
      totalArea += (inbetweenHeight)*width;
    }
    top = top_next;
    bottom = bottom_next;
    // find the next edge
    if (i->x < j->x) {
      top_next = !top;
      width = i->x - cur_x;
      cur_x = i->x;
      i++;
    } else if (j->x < i->x) {
      bottom_next = !bottom;
      width = j->x - cur_x;
      cur_x = j->x;
      j++;
    } else {  // i->x == j->x
      top_next = !top;
      bottom_next = !bottom;
      width = i->x - cur_x;
      cur_x = i->x;
      i++;
      j++;
    }
  } while (i < end_i && j < end_j);

  // handle any remaining rects
  while (i < end_i) {
    width = i->x - cur_x;
    cur_x = i->x;
    i++;
    if (top) totalArea += (inbetweenHeight + bottomRectsHeight) * width;
    top = !top;
  }

  while (j < end_j) {
    width = j->x - cur_x;
    cur_x = j->x;
    j++;
    if (bottom) totalArea += (inbetweenHeight + topRectsHeight) * width;
    bottom = !bottom;
  }
  return totalArea;
}

static pixman_box32_t *CopyRow(pixman_box32_t *dest_it,
                               pixman_box32_t *src_start,
                               pixman_box32_t *src_end) {
  // XXX: std::copy
  pixman_box32_t *src_it = src_start;
  while (src_it < src_end) {
    *dest_it++ = *src_it++;
  }
  return dest_it;
}

#define WRITE_RECT(x1, x2, y1, y2) \
  do {                             \
    tmpRect->x1 = x1;              \
    tmpRect->x2 = x2;              \
    tmpRect->y1 = y1;              \
    tmpRect->y2 = y2;              \
    tmpRect++;                     \
  } while (0)

/* If 'r' overlaps the current rect, then expand the current rect to include
 * it. Otherwise write the current rect out to tmpRect, and set r as the
 * updated current rect. */
#define MERGE_RECT(r)             \
  do {                            \
    if (r->x1 <= x2) {            \
      if (x2 < r->x2) x2 = r->x2; \
    } else {                      \
      WRITE_RECT(x1, x2, y1, y2); \
      x1 = r->x1;                 \
      x2 = r->x2;                 \
    }                             \
    r++;                          \
  } while (0)

/* Can we merge two sets of rects without extra space?
 * Yes, but not easily. We can even do it stably
 * but we don't need that property.
 *
 * This is written in the style of pixman_region_union_o */
static pixman_box32_t *MergeRects(pixman_box32_t *r1, pixman_box32_t *r1_end,
                                  pixman_box32_t *r2, pixman_box32_t *r2_end,
                                  pixman_box32_t *tmpRect) {
  /* This routine works by maintaining the current
   * rectangle in x1,x2,y1,y2 and either merging
   * in the left most rectangle if it overlaps or
   * outputing the current rectangle and setting
   * it to the the left most one */
  const int y1 = r1->y1;
  const int y2 = r2->y2;
  int x1;
  int x2;

  /* Find the left-most edge */
  if (r1->x1 < r2->x1) {
    x1 = r1->x1;
    x2 = r1->x2;
    r1++;
  } else {
    x1 = r2->x1;
    x2 = r2->x2;
    r2++;
  }

  while (r1 != r1_end && r2 != r2_end) {
    /* Find and merge the left-most rectangle */
    if (r1->x1 < r2->x1)
      MERGE_RECT(r1);
    else
      MERGE_RECT(r2);
  }

  /* Finish up any left overs */
  if (r1 != r1_end) {
    do {
      MERGE_RECT(r1);
    } while (r1 != r1_end);
  } else if (r2 != r2_end) {
    do {
      MERGE_RECT(r2);
    } while (r2 != r2_end);
  }

  /* Finish up the last rectangle */
  WRITE_RECT(x1, x2, y1, y2);

  return tmpRect;
}

void nsRegion::SimplifyOutwardByArea(uint32_t aThreshold) {
  pixman_box32_t *boxes;
  int n;
  boxes = pixman_region32_rectangles(&mImpl, &n);

  // if we have no rectangles then we're done
  if (!n) return;

  pixman_box32_t *end = boxes + n;
  pixman_box32_t *topRectsEnd = boxes + 1;
  pixman_box32_t *topRects = boxes;

  // we need some temporary storage for merging both rows of rectangles
  AutoTArray<pixman_box32_t, 10> tmpStorage;
  tmpStorage.SetCapacity(n);
  pixman_box32_t *tmpRect = tmpStorage.Elements();

  pixman_box32_t *destRect = boxes;
  pixman_box32_t *rect = tmpRect;
  // find the end of the first span of rectangles
  while (topRectsEnd < end && topRectsEnd->y1 == topRects->y1) {
    topRectsEnd++;
  }

  // if we only have one row we are done
  if (topRectsEnd == end) return;

  pixman_box32_t *bottomRects = topRectsEnd;
  pixman_box32_t *bottomRectsEnd = bottomRects + 1;
  do {
    // find the end of the bottom span of rectangles
    while (bottomRectsEnd < end && bottomRectsEnd->y1 == bottomRects->y1) {
      bottomRectsEnd++;
    }
    uint32_t totalArea = ComputeMergedAreaIncrease(topRects, topRectsEnd,
                                                   bottomRects, bottomRectsEnd);

    if (totalArea <= aThreshold) {
      // merge the rects into tmpRect
      rect = MergeRects(topRects, topRectsEnd, bottomRects, bottomRectsEnd,
                        tmpRect);

      // set topRects to where the newly merged rects will be so that we use
      // them as our next set of topRects
      topRects = destRect;
      // copy the merged rects back into the destination
      topRectsEnd = CopyRow(destRect, tmpRect, rect);
    } else {
      // copy the unmerged rects
      destRect = CopyRow(destRect, topRects, topRectsEnd);

      topRects = bottomRects;
      topRectsEnd = bottomRectsEnd;
      if (bottomRectsEnd == end) {
        // copy the last row when we are done
        topRectsEnd = CopyRow(destRect, topRects, topRectsEnd);
      }
    }
    bottomRects = bottomRectsEnd;
  } while (bottomRectsEnd != end);

  uint32_t reducedCount =
      topRectsEnd - pixman_region32_rectangles(&this->mImpl, &n);
  // pixman has a special representation for
  // regions of 1 rectangle. So just use the
  // bounds in that case
  if (reducedCount > 1) {
    // reach into pixman and lower the number
    // of rects stored in data.
    this->mImpl.data->numRects = reducedCount;
  } else {
    *this = GetBounds();
  }
}

typedef void (*visit_fn)(void *closure, VisitSide side, int x1, int y1, int x2,
                         int y2);

static bool VisitNextEdgeBetweenRect(visit_fn visit, void *closure,
                                     VisitSide side, pixman_box32_t *&r1,
                                     pixman_box32_t *&r2, const int y,
                                     int &x1) {
  // check for overlap
  if (r1->x2 >= r2->x1) {
    MOZ_ASSERT(r2->x1 >= x1);
    visit(closure, side, x1, y, r2->x1, y);

    // find the rect that ends first or always drop the one that comes first?
    if (r1->x2 < r2->x2) {
      x1 = r1->x2;
      r1++;
    } else {
      x1 = r2->x2;
      r2++;
    }
    return true;
  } else {
    MOZ_ASSERT(r1->x2 < r2->x2);
    // we handle the corners by just extending the top and bottom edges
    visit(closure, side, x1, y, r1->x2 + 1, y);
    r1++;
    // we assign x1 because we can assume that x1 <= r2->x1 - 1
    // However the caller may know better and if so, may update
    // x1 to r1->x1
    x1 = r2->x1 - 1;
    return false;
  }
}

// XXX: if we need to this can compute the end of the row
static void VisitSides(visit_fn visit, void *closure, pixman_box32_t *r,
                       pixman_box32_t *r_end) {
  // XXX: we can drop LEFT/RIGHT and just use the orientation
  // of the line if it makes sense
  while (r != r_end) {
    visit(closure, VisitSide::LEFT, r->x1, r->y1, r->x1, r->y2);
    visit(closure, VisitSide::RIGHT, r->x2, r->y1, r->x2, r->y2);
    r++;
  }
}

static void VisitAbove(visit_fn visit, void *closure, pixman_box32_t *r,
                       pixman_box32_t *r_end) {
  while (r != r_end) {
    visit(closure, VisitSide::TOP, r->x1 - 1, r->y1, r->x2 + 1, r->y1);
    r++;
  }
}

static void VisitBelow(visit_fn visit, void *closure, pixman_box32_t *r,
                       pixman_box32_t *r_end) {
  while (r != r_end) {
    visit(closure, VisitSide::BOTTOM, r->x1 - 1, r->y2, r->x2 + 1, r->y2);
    r++;
  }
}

static pixman_box32_t *VisitInbetween(visit_fn visit, void *closure,
                                      pixman_box32_t *r1,
                                      pixman_box32_t *r1_end,
                                      pixman_box32_t *r2,
                                      pixman_box32_t *r2_end) {
  const int y = r1->y2;
  int x1;

  bool overlap = false;
  while (r1 != r1_end && r2 != r2_end) {
    if (!overlap) {
      /* Find the left-most edge */
      if (r1->x1 < r2->x1) {
        x1 = r1->x1 - 1;
      } else {
        x1 = r2->x1 - 1;
      }
    }

    MOZ_ASSERT((x1 >= (r1->x1 - 1)) || (x1 >= (r2->x1 - 1)));
    if (r1->x1 < r2->x1) {
      overlap = VisitNextEdgeBetweenRect(visit, closure, VisitSide::BOTTOM, r1,
                                         r2, y, x1);
    } else {
      overlap = VisitNextEdgeBetweenRect(visit, closure, VisitSide::TOP, r2, r1,
                                         y, x1);
    }
  }

  /* Finish up which ever row has remaining rects*/
  if (r1 != r1_end) {
    // top row
    do {
      visit(closure, VisitSide::BOTTOM, x1, y, r1->x2 + 1, y);
      r1++;
      if (r1 == r1_end) break;
      x1 = r1->x1 - 1;
    } while (true);
  } else if (r2 != r2_end) {
    // bottom row
    do {
      visit(closure, VisitSide::TOP, x1, y, r2->x2 + 1, y);
      r2++;
      if (r2 == r2_end) break;
      x1 = r2->x1 - 1;
    } while (true);
  }

  return 0;
}

void nsRegion::VisitEdges(visit_fn visit, void *closure) {
  pixman_box32_t *boxes;
  int n;
  boxes = pixman_region32_rectangles(&mImpl, &n);

  // if we have no rectangles then we're done
  if (!n) return;

  pixman_box32_t *end = boxes + n;
  pixman_box32_t *topRectsEnd = boxes + 1;
  pixman_box32_t *topRects = boxes;

  // find the end of the first span of rectangles
  while (topRectsEnd < end && topRectsEnd->y1 == topRects->y1) {
    topRectsEnd++;
  }

  // In order to properly handle convex corners we always visit the sides first
  // that way when we visit the corners we can pad using the value from the
  // sides
  VisitSides(visit, closure, topRects, topRectsEnd);

  VisitAbove(visit, closure, topRects, topRectsEnd);

  pixman_box32_t *bottomRects = topRects;
  pixman_box32_t *bottomRectsEnd = topRectsEnd;
  if (topRectsEnd != end) {
    do {
      // find the next row of rects
      bottomRects = topRectsEnd;
      bottomRectsEnd = topRectsEnd + 1;
      while (bottomRectsEnd < end && bottomRectsEnd->y1 == bottomRects->y1) {
        bottomRectsEnd++;
      }

      VisitSides(visit, closure, bottomRects, bottomRectsEnd);

      if (topRects->y2 == bottomRects->y1) {
        VisitInbetween(visit, closure, topRects, topRectsEnd, bottomRects,
                       bottomRectsEnd);
      } else {
        VisitBelow(visit, closure, topRects, topRectsEnd);
        VisitAbove(visit, closure, bottomRects, bottomRectsEnd);
      }

      topRects = bottomRects;
      topRectsEnd = bottomRectsEnd;
    } while (bottomRectsEnd != end);
  }

  // the bottom of the region doesn't touch anything else so we
  // can always visit it at the end
  VisitBelow(visit, closure, bottomRects, bottomRectsEnd);
}

void nsRegion::SimplifyInward(uint32_t aMaxRects) {
  NS_ASSERTION(aMaxRects >= 1, "Invalid max rect count");

  if (GetNumRects() <= aMaxRects) return;

  SetEmpty();
}

uint64_t nsRegion::Area() const {
  uint64_t area = 0;
  for (auto iter = RectIter(); !iter.Done(); iter.Next()) {
    const nsRect &rect = iter.Get();
    area += uint64_t(rect.Width()) * rect.Height();
  }
  return area;
}

nsRegion &nsRegion::ScaleRoundOut(float aXScale, float aYScale) {
  if (mozilla::gfx::FuzzyEqual(aXScale, 1.0f) &&
      mozilla::gfx::FuzzyEqual(aYScale, 1.0f)) {
    return *this;
  }

  int n;
  pixman_box32_t *boxes = pixman_region32_rectangles(&mImpl, &n);
  for (int i = 0; i < n; i++) {
    nsRect rect = BoxToRect(boxes[i]);
    rect.ScaleRoundOut(aXScale, aYScale);
    boxes[i] = RectToBox(rect);
  }

  pixman_region32_t region;
  // This will union all of the rectangles and runs in about O(n lg(n))
  pixman_region32_init_rects(&region, boxes, n);

  pixman_region32_fini(&mImpl);
  mImpl = region;
  return *this;
}

nsRegion &nsRegion::ScaleInverseRoundOut(float aXScale, float aYScale) {
  int n;
  pixman_box32_t *boxes = pixman_region32_rectangles(&mImpl, &n);
  for (int i = 0; i < n; i++) {
    nsRect rect = BoxToRect(boxes[i]);
    rect.ScaleInverseRoundOut(aXScale, aYScale);
    boxes[i] = RectToBox(rect);
  }

  pixman_region32_t region;
  // This will union all of the rectangles and runs in about O(n lg(n))
  pixman_region32_init_rects(&region, boxes, n);

  pixman_region32_fini(&mImpl);
  mImpl = region;
  return *this;
}

static mozilla::gfx::IntRect TransformRect(
    const mozilla::gfx::IntRect &aRect,
    const mozilla::gfx::Matrix4x4 &aTransform) {
  if (aRect.IsEmpty()) {
    return mozilla::gfx::IntRect();
  }

  mozilla::gfx::RectDouble rect(aRect.X(), aRect.Y(), aRect.Width(),
                                aRect.Height());
  rect = aTransform.TransformAndClipBounds(
      rect, mozilla::gfx::RectDouble::MaxIntRect());
  rect.RoundOut();

  mozilla::gfx::IntRect intRect;
  if (!gfxUtils::GfxRectToIntRect(ThebesRect(rect), &intRect)) {
    return mozilla::gfx::IntRect();
  }

  return intRect;
}

nsRegion &nsRegion::Transform(const mozilla::gfx::Matrix4x4 &aTransform) {
  int n;
  pixman_box32_t *boxes = pixman_region32_rectangles(&mImpl, &n);
  for (int i = 0; i < n; i++) {
    nsRect rect = BoxToRect(boxes[i]);
    boxes[i] = RectToBox(nsIntRegion::ToRect(
        TransformRect(nsIntRegion::FromRect(rect), aTransform)));
  }

  pixman_region32_t region;
  // This will union all of the rectangles and runs in about O(n lg(n))
  pixman_region32_init_rects(&region, boxes, n);

  pixman_region32_fini(&mImpl);
  mImpl = region;
  return *this;
}

nsRegion nsRegion::ScaleToOtherAppUnitsRoundOut(int32_t aFromAPP,
                                                int32_t aToAPP) const {
  if (aFromAPP == aToAPP) {
    return *this;
  }

  nsRegion region = *this;
  int n;
  pixman_box32_t *boxes = pixman_region32_rectangles(&region.mImpl, &n);
  for (int i = 0; i < n; i++) {
    nsRect rect = BoxToRect(boxes[i]);
    rect = rect.ScaleToOtherAppUnitsRoundOut(aFromAPP, aToAPP);
    boxes[i] = RectToBox(rect);
  }

  pixman_region32_t pixmanRegion;
  // This will union all of the rectangles and runs in about O(n lg(n))
  pixman_region32_init_rects(&pixmanRegion, boxes, n);

  pixman_region32_fini(&region.mImpl);
  region.mImpl = pixmanRegion;
  return region;
}

nsRegion nsRegion::ScaleToOtherAppUnitsRoundIn(int32_t aFromAPP,
                                               int32_t aToAPP) const {
  if (aFromAPP == aToAPP) {
    return *this;
  }

  nsRegion region = *this;
  int n;
  pixman_box32_t *boxes = pixman_region32_rectangles(&region.mImpl, &n);
  for (int i = 0; i < n; i++) {
    nsRect rect = BoxToRect(boxes[i]);
    rect = rect.ScaleToOtherAppUnitsRoundIn(aFromAPP, aToAPP);
    boxes[i] = RectToBox(rect);
  }

  pixman_region32_t pixmanRegion;
  // This will union all of the rectangles and runs in about O(n lg(n))
  pixman_region32_init_rects(&pixmanRegion, boxes, n);

  pixman_region32_fini(&region.mImpl);
  region.mImpl = pixmanRegion;
  return region;
}

nsIntRegion nsRegion::ToPixels(nscoord aAppUnitsPerPixel,
                               bool aOutsidePixels) const {
  nsRegion region = *this;
  int n;
  pixman_box32_t *boxes = pixman_region32_rectangles(&region.mImpl, &n);
  for (int i = 0; i < n; i++) {
    nsRect rect = BoxToRect(boxes[i]);
    mozilla::gfx::IntRect deviceRect;
    if (aOutsidePixels)
      deviceRect = rect.ToOutsidePixels(aAppUnitsPerPixel);
    else
      deviceRect = rect.ToNearestPixels(aAppUnitsPerPixel);

    boxes[i] = RectToBox(deviceRect);
  }

  nsIntRegion intRegion;
  pixman_region32_fini(&intRegion.mImpl.mImpl);
  // This will union all of the rectangles and runs in about O(n lg(n))
  pixman_region32_init_rects(&intRegion.mImpl.mImpl, boxes, n);

  return intRegion;
}

nsIntRegion nsRegion::ToOutsidePixels(nscoord aAppUnitsPerPixel) const {
  return ToPixels(aAppUnitsPerPixel, true);
}

nsIntRegion nsRegion::ToNearestPixels(nscoord aAppUnitsPerPixel) const {
  return ToPixels(aAppUnitsPerPixel, false);
}

nsIntRegion nsRegion::ScaleToNearestPixels(float aScaleX, float aScaleY,
                                           nscoord aAppUnitsPerPixel) const {
  nsIntRegion result;
  for (auto iter = RectIter(); !iter.Done(); iter.Next()) {
    mozilla::gfx::IntRect deviceRect =
        iter.Get().ScaleToNearestPixels(aScaleX, aScaleY, aAppUnitsPerPixel);
    result.Or(result, deviceRect);
  }
  return result;
}

nsIntRegion nsRegion::ScaleToOutsidePixels(float aScaleX, float aScaleY,
                                           nscoord aAppUnitsPerPixel) const {
  // make a copy of the region so that we can mutate it inplace
  nsRegion region = *this;
  int n;
  pixman_box32_t *boxes = pixman_region32_rectangles(&region.mImpl, &n);
  boxes = pixman_region32_rectangles(&region.mImpl, &n);
  for (int i = 0; i < n; i++) {
    nsRect rect = BoxToRect(boxes[i]);
    mozilla::gfx::IntRect irect =
        rect.ScaleToOutsidePixels(aScaleX, aScaleY, aAppUnitsPerPixel);
    boxes[i] = RectToBox(irect);
  }

  nsIntRegion iRegion;
  // clear out the initial pixman_region so that we can replace it below
  pixman_region32_fini(&iRegion.mImpl.mImpl);
  // This will union all of the rectangles and runs in about O(n lg(n))
  pixman_region32_init_rects(&iRegion.mImpl.mImpl, boxes, n);

  return iRegion;
}

nsIntRegion nsRegion::ScaleToInsidePixels(float aScaleX, float aScaleY,
                                          nscoord aAppUnitsPerPixel) const {
  /* When scaling a rect, walk forward through the rect list up until the y
   * value is greater than the current rect's YMost() value.
   *
   * For each rect found, check if the rects have a touching edge (in unscaled
   * coordinates), and if one edge is entirely contained within the other.
   *
   * If it is, then the contained edge can be moved (in scaled pixels) to ensure
   * that no gap exists.
   *
   * Since this could be potentially expensive - O(n^2), we only attempt this
   * algorithm for the first rect.
   */

  // make a copy of this region so that we can mutate it in place
  nsRegion region = *this;
  int n;
  pixman_box32_t *boxes = pixman_region32_rectangles(&region.mImpl, &n);

  nsIntRegion intRegion;
  if (n) {
    nsRect first = BoxToRect(boxes[0]);
    mozilla::gfx::IntRect firstDeviceRect =
        first.ScaleToInsidePixels(aScaleX, aScaleY, aAppUnitsPerPixel);

    for (int i = 1; i < n; i++) {
      nsRect rect = nsRect(boxes[i].x1, boxes[i].y1, boxes[i].x2 - boxes[i].x1,
                           boxes[i].y2 - boxes[i].y1);
      mozilla::gfx::IntRect deviceRect =
          rect.ScaleToInsidePixels(aScaleX, aScaleY, aAppUnitsPerPixel);

      if (rect.Y() <= first.YMost()) {
        if (rect.XMost() == first.X() && rect.YMost() <= first.YMost()) {
          // rect is touching on the left edge of the first rect and contained
          // within the length of its left edge
          deviceRect.SetRightEdge(firstDeviceRect.X());
        } else if (rect.X() == first.XMost() && rect.YMost() <= first.YMost()) {
          // rect is touching on the right edge of the first rect and contained
          // within the length of its right edge
          deviceRect.SetLeftEdge(firstDeviceRect.XMost());
        } else if (rect.Y() == first.YMost()) {
          // The bottom of the first rect is on the same line as the top of
          // rect, but they aren't necessarily contained.
          if (rect.X() <= first.X() && rect.XMost() >= first.XMost()) {
            // The top of rect contains the bottom of the first rect
            firstDeviceRect.SetBottomEdge(deviceRect.Y());
          } else if (rect.X() >= first.X() && rect.XMost() <= first.XMost()) {
            // The bottom of the first contains the top of rect
            deviceRect.SetTopEdge(firstDeviceRect.YMost());
          }
        }
      }

      boxes[i] = RectToBox(deviceRect);
    }

    boxes[0] = RectToBox(firstDeviceRect);

    pixman_region32_fini(&intRegion.mImpl.mImpl);
    // This will union all of the rectangles and runs in about O(n lg(n))
    pixman_region32_init_rects(&intRegion.mImpl.mImpl, boxes, n);
  }
  return intRegion;
}

// A cell's "value" is a pair consisting of
// a) the area of the subrectangle it corresponds to, if it's in
// aContainingRect and in the region, 0 otherwise
// b) the area of the subrectangle it corresponds to, if it's in the region,
// 0 otherwise
// Addition, subtraction and identity are defined on these values in the
// obvious way. Partial order is lexicographic.
// A "large negative value" is defined with large negative numbers for both
// fields of the pair. This negative value has the property that adding any
// number of non-negative values to it always results in a negative value.
//
// The GetLargestRectangle algorithm works in three phases:
//  1) Convert the region into a grid by adding vertical/horizontal lines for
//     each edge of each rectangle in the region.
//  2) For each rectangle in the region, for each cell it contains, set that
//     cells's value as described above.
//  3) Calculate the submatrix with the largest sum such that none of its cells
//     contain any 0s (empty regions). The rectangle represented by the
//     submatrix is the largest rectangle in the region.
//
// Let k be the number of rectangles in the region.
// Let m be the height of the grid generated in step 1.
// Let n be the width of the grid generated in step 1.
//
// Step 1 is O(k) in time and O(m+n) in space for the sparse grid.
// Step 2 is O(mn) in time and O(mn) in additional space for the full grid.
// Step 3 is O(m^2 n) in time and O(mn) in additional space
//
// The implementation of steps 1 and 2 are rather straightforward. However our
// implementation of step 3 uses dynamic programming to achieve its efficiency.
//
// Psuedo code for step 3 is as follows where G is the grid from step 1 and A
// is the array from step 2:
// Phase3 = function (G, A, m, n) {
//   let (t,b,l,r,_) = MaxSum2D(A,m,n)
//   return rect(G[t],G[l],G[r],G[b]);
// }
// MaxSum2D = function (A, m, n) {
//   S = array(m+1,n+1)
//   S[0][i] = 0 for i in [0,n]
//   S[j][0] = 0 for j in [0,m]
//   S[j][i] = (if A[j-1][i-1] = 0 then some large negative value
//                                 else A[j-1][i-1])
//           + S[j-1][n] + S[j][i-1] - S[j-1][i-1]
//
//   // top, bottom, left, right, area
//   var maxRect = (-1, -1, -1, -1, 0);
//
//   for all (m',m'') in [0, m]^2 {
//     let B = { S[m'][i] - S[m''][i] | 0 <= i <= n }
//     let ((l,r),area) = MaxSum1D(B,n+1)
//     if (area > maxRect.area) {
//       maxRect := (m', m'', l, r, area)
//     }
//   }
//
//   return maxRect;
// }
//
// Originally taken from Improved algorithms for the k-maximum subarray problem
// for small k - SE Bae, T Takaoka but modified to show the explicit tracking
// of indices and we already have the prefix sums from our one call site so
// there's no need to construct them.
// MaxSum1D = function (A,n) {
//   var minIdx = 0;
//   var min = 0;
//   var maxIndices = (0,0);
//   var max = 0;
//   for i in range(n) {
//     let cand = A[i] - min;
//     if (cand > max) {
//       max := cand;
//       maxIndices := (minIdx, i)
//     }
//     if (min > A[i]) {
//       min := A[i];
//       minIdx := i;
//     }
//   }
//   return (minIdx, maxIdx, max);
// }

namespace {
// This class represents a partitioning of an axis delineated by coordinates.
// It internally maintains a sorted array of coordinates.
class AxisPartition {
 public:
  // Adds a new partition at the given coordinate to this partitioning. If
  // the coordinate is already present in the partitioning, this does nothing.
  void InsertCoord(nscoord c) {
    uint32_t i = mStops.IndexOfFirstElementGt(c);
    if (i == 0 || mStops[i - 1] != c) {
      mStops.InsertElementAt(i, c);
    }
  }

  // Returns the array index of the given partition point. The partition
  // point must already be present in the partitioning.
  int32_t IndexOf(nscoord p) const { return mStops.BinaryIndexOf(p); }

  // Returns the partition at the given index which must be non-zero and
  // less than the number of partitions in this partitioning.
  nscoord StopAt(int32_t index) const { return mStops[index]; }

  // Returns the size of the gap between the partition at the given index and
  // the next partition in this partitioning. If the index is the last index
  // in the partitioning, the result is undefined.
  nscoord StopSize(int32_t index) const {
    return mStops[index + 1] - mStops[index];
  }

  // Returns the number of partitions in this partitioning.
  int32_t GetNumStops() const { return mStops.Length(); }

 private:
  nsTArray<nscoord> mStops;
};

const int64_t kVeryLargeNegativeNumber = 0xffff000000000000ll;

struct SizePair {
  int64_t mSizeContainingRect;
  int64_t mSize;

  SizePair() : mSizeContainingRect(0), mSize(0) {}

  static SizePair VeryLargeNegative() {
    SizePair result;
    result.mSize = result.mSizeContainingRect = kVeryLargeNegativeNumber;
    return result;
  }
  bool operator<(const SizePair &aOther) const {
    if (mSizeContainingRect < aOther.mSizeContainingRect) return true;
    if (mSizeContainingRect > aOther.mSizeContainingRect) return false;
    return mSize < aOther.mSize;
  }
  bool operator>(const SizePair &aOther) const {
    return aOther.operator<(*this);
  }
  SizePair operator+(const SizePair &aOther) const {
    SizePair result = *this;
    result.mSizeContainingRect += aOther.mSizeContainingRect;
    result.mSize += aOther.mSize;
    return result;
  }
  SizePair operator-(const SizePair &aOther) const {
    SizePair result = *this;
    result.mSizeContainingRect -= aOther.mSizeContainingRect;
    result.mSize -= aOther.mSize;
    return result;
  }
};

// Returns the sum and indices of the subarray with the maximum sum of the
// given array (A,n), assuming the array is already in prefix sum form.
SizePair MaxSum1D(const nsTArray<SizePair> &A, int32_t n, int32_t *minIdx,
                  int32_t *maxIdx) {
  // The min/max indicies of the largest subarray found so far
  SizePair min, max;
  int32_t currentMinIdx = 0;

  *minIdx = 0;
  *maxIdx = 0;

  // Because we're given the array in prefix sum form, we know the first
  // element is 0
  for (int32_t i = 1; i < n; i++) {
    SizePair cand = A[i] - min;
    if (cand > max) {
      max = cand;
      *minIdx = currentMinIdx;
      *maxIdx = i;
    }
    if (min > A[i]) {
      min = A[i];
      currentMinIdx = i;
    }
  }

  return max;
}
}  // namespace

nsRect nsRegion::GetLargestRectangle(const nsRect &aContainingRect) const {
  nsRect bestRect;

  if (GetNumRects() <= 1) {
    bestRect = GetBounds();
    return bestRect;
  }

  AxisPartition xaxis, yaxis;

  // Step 1: Calculate the grid lines
  for (auto iter = RectIter(); !iter.Done(); iter.Next()) {
    const nsRect &rect = iter.Get();
    xaxis.InsertCoord(rect.X());
    xaxis.InsertCoord(rect.XMost());
    yaxis.InsertCoord(rect.Y());
    yaxis.InsertCoord(rect.YMost());
  }
  if (!aContainingRect.IsEmpty()) {
    xaxis.InsertCoord(aContainingRect.X());
    xaxis.InsertCoord(aContainingRect.XMost());
    yaxis.InsertCoord(aContainingRect.Y());
    yaxis.InsertCoord(aContainingRect.YMost());
  }

  // Step 2: Fill out the grid with the areas
  // Note: due to the ordering of rectangles in the region, it is not always
  // possible to combine steps 2 and 3 so we don't try to be clever.
  int32_t matrixHeight = yaxis.GetNumStops() - 1;
  int32_t matrixWidth = xaxis.GetNumStops() - 1;
  int32_t matrixSize = matrixHeight * matrixWidth;
  nsTArray<SizePair> areas(matrixSize);
  areas.SetLength(matrixSize);

  for (auto iter = RectIter(); !iter.Done(); iter.Next()) {
    const nsRect &rect = iter.Get();
    int32_t xstart = xaxis.IndexOf(rect.X());
    int32_t xend = xaxis.IndexOf(rect.XMost());
    int32_t y = yaxis.IndexOf(rect.Y());
    int32_t yend = yaxis.IndexOf(rect.YMost());

    for (; y < yend; y++) {
      nscoord height = yaxis.StopSize(y);
      for (int32_t x = xstart; x < xend; x++) {
        nscoord width = xaxis.StopSize(x);
        int64_t size = width * int64_t(height);
        if (rect.Intersects(aContainingRect)) {
          areas[y * matrixWidth + x].mSizeContainingRect = size;
        }
        areas[y * matrixWidth + x].mSize = size;
      }
    }
  }

  // Step 3: Find the maximum submatrix sum that does not contain a rectangle
  {
    // First get the prefix sum array
    int32_t m = matrixHeight + 1;
    int32_t n = matrixWidth + 1;
    nsTArray<SizePair> pareas(m * n);
    pareas.SetLength(m * n);
    for (int32_t y = 1; y < m; y++) {
      for (int32_t x = 1; x < n; x++) {
        SizePair area = areas[(y - 1) * matrixWidth + x - 1];
        if (!area.mSize) {
          area = SizePair::VeryLargeNegative();
        }
        area = area + pareas[y * n + x - 1] + pareas[(y - 1) * n + x] -
               pareas[(y - 1) * n + x - 1];
        pareas[y * n + x] = area;
      }
    }

    // No longer need the grid
    areas.SetLength(0);

    SizePair bestArea;
    struct {
      int32_t left, top, right, bottom;
    } bestRectIndices = {0, 0, 0, 0};
    for (int32_t m1 = 0; m1 < m; m1++) {
      for (int32_t m2 = m1 + 1; m2 < m; m2++) {
        nsTArray<SizePair> B;
        B.SetLength(n);
        for (int32_t i = 0; i < n; i++) {
          B[i] = pareas[m2 * n + i] - pareas[m1 * n + i];
        }
        int32_t minIdx, maxIdx;
        SizePair area = MaxSum1D(B, n, &minIdx, &maxIdx);
        if (area > bestArea) {
          bestRectIndices.left = minIdx;
          bestRectIndices.top = m1;
          bestRectIndices.right = maxIdx;
          bestRectIndices.bottom = m2;
          bestArea = area;
        }
      }
    }

    bestRect.MoveTo(xaxis.StopAt(bestRectIndices.left),
                    yaxis.StopAt(bestRectIndices.top));
    bestRect.SizeTo(xaxis.StopAt(bestRectIndices.right) - bestRect.X(),
                    yaxis.StopAt(bestRectIndices.bottom) - bestRect.Y());
  }

  return bestRect;
}

std::ostream &operator<<(std::ostream &stream, const nsRegion &m) {
  stream << "[";

  int n;
  pixman_box32_t *boxes =
      pixman_region32_rectangles(const_cast<pixman_region32_t *>(&m.mImpl), &n);
  for (int i = 0; i < n; i++) {
    if (i != 0) {
      stream << "; ";
    }
    stream << boxes[i].x1 << "," << boxes[i].y1 << "," << boxes[i].x2 << ","
           << boxes[i].y2;
  }

  stream << "]";
  return stream;
}

nsCString nsRegion::ToString() const {
  return nsCString(mozilla::ToString(*this).c_str());
}