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 #include "nsRegion.h"
8 #include "nsTArray.h"
9 #include "gfxUtils.h"
10 #include "gfx2DGlue.h"
11 #include "mozilla/ToString.h"
12 
AssertStateInternal() const13 void nsRegion::AssertStateInternal() const {
14   bool failed = false;
15   // Verify consistent state inside the region.
16   int32_t lastY = INT32_MIN;
17   int32_t lowestX = INT32_MAX;
18   int32_t highestX = INT32_MIN;
19   for (auto iter = mBands.begin(); iter != mBands.end(); iter++) {
20     const Band& band = *iter;
21     if (band.bottom <= band.top) {
22       failed = true;
23       break;
24     }
25     if (band.top < lastY) {
26       failed = true;
27       break;
28     }
29     lastY = band.bottom;
30 
31     lowestX = std::min(lowestX, band.mStrips.begin()->left);
32     highestX = std::max(highestX, band.mStrips.LastElement().right);
33 
34     int32_t lastX = INT32_MIN;
35     if (iter != mBands.begin()) {
36       auto prev = iter;
37       prev--;
38 
39       if (prev->bottom == iter->top) {
40         if (band.EqualStrips(*prev)) {
41           failed = true;
42           break;
43         }
44       }
45     }
46     for (const Strip& strip : band.mStrips) {
47       if (strip.right <= strip.left) {
48         failed = true;
49         break;
50       }
51       if (strip.left <= lastX) {
52         failed = true;
53         break;
54       }
55       lastX = strip.right;
56     }
57     if (failed) {
58       break;
59     }
60   }
61 
62   if (!(mBounds.IsEqualEdges(CalculateBounds()))) {
63     failed = true;
64   }
65 
66   if (failed) {
67 #ifdef DEBUG_REGIONS
68     if (mCurrentOpGenerator) {
69       mCurrentOpGenerator->OutputOp();
70     }
71 #endif
72     MOZ_ASSERT(false);
73   }
74 }
75 
Contains(const nsRegion & aRgn) const76 bool nsRegion::Contains(const nsRegion& aRgn) const {
77   // XXX this could be made faster by iterating over
78   // both regions at the same time some how
79   for (auto iter = aRgn.RectIter(); !iter.Done(); iter.Next()) {
80     if (!Contains(iter.Get())) {
81       return false;
82     }
83   }
84   return true;
85 }
86 
Intersects(const nsRectAbsolute & aRect) const87 bool nsRegion::Intersects(const nsRectAbsolute& aRect) const {
88   if (mBands.IsEmpty()) {
89     return mBounds.Intersects(aRect);
90   }
91 
92   if (!mBounds.Intersects(aRect)) {
93     return false;
94   }
95 
96   Strip rectStrip(aRect.X(), aRect.XMost());
97 
98   auto iter = mBands.begin();
99   while (iter != mBands.end()) {
100     if (iter->top >= aRect.YMost()) {
101       return false;
102     }
103 
104     if (iter->bottom <= aRect.Y()) {
105       // This band is entirely before aRect, move on.
106       iter++;
107       continue;
108     }
109 
110     if (!iter->Intersects(rectStrip)) {
111       // This band does not intersect aRect horizontally. Move on.
112       iter++;
113       continue;
114     }
115 
116     // This band intersects with aRect.
117     return true;
118   }
119 
120   return false;
121 }
122 
Inflate(const nsMargin & aMargin)123 void nsRegion::Inflate(const nsMargin& aMargin) {
124   nsRegion newRegion;
125   for (RectIterator iter = RectIterator(*this); !iter.Done(); iter.Next()) {
126     nsRectAbsolute rect = iter.GetAbsolute();
127     rect.Inflate(aMargin);
128     newRegion.AddRect(rect);
129   }
130 
131   *this = std::move(newRegion);
132 }
133 
SimplifyOutward(uint32_t aMaxRects)134 void nsRegion::SimplifyOutward(uint32_t aMaxRects) {
135   MOZ_ASSERT(aMaxRects >= 1, "Invalid max rect count");
136 
137   if (GetNumRects() <= aMaxRects) {
138     return;
139   }
140 
141   // Try combining rects in horizontal bands into a single rect
142   // The goal here is to try to keep groups of rectangles that are vertically
143   // discontiguous as separate rectangles in the final region. This is
144   // simple and fast to implement and page contents tend to vary more
145   // vertically than horizontally (which is why our rectangles are stored
146   // sorted by y-coordinate, too).
147   //
148   // Note: if boxes share y1 because of the canonical representation they
149   // will share y2
150 
151   size_t idx = 0;
152 
153   while (idx < mBands.Length()) {
154     size_t oldIdx = idx;
155     mBands[idx].mStrips.begin()->right =
156         mBands[idx].mStrips.LastElement().right;
157     mBands[idx].mStrips.TruncateLength(1);
158     idx++;
159 
160     // Merge any bands with the same bounds.
161     while (idx < mBands.Length() &&
162            mBands[idx].mStrips.begin()->left ==
163                mBands[oldIdx].mStrips.begin()->left &&
164            mBands[idx].mStrips.LastElement().right ==
165                mBands[oldIdx].mStrips.begin()->right) {
166       mBands[oldIdx].bottom = mBands[idx].bottom;
167       mBands.RemoveElementAt(idx);
168     }
169   }
170 
171   AssertState();
172 
173   // mBands.size() is now equal to our rect count.
174   if (mBands.Length() > aMaxRects) {
175     *this = GetBounds();
176   }
177 }
178 
179 // compute the covered area difference between two rows.
180 // by iterating over both rows simultaneously and adding up
181 // the additional increase in area caused by extending each
182 // of the rectangles to the combined height of both rows
ComputeMergedAreaIncrease(const Band & aTopBand,const Band & aBottomBand)183 uint32_t nsRegion::ComputeMergedAreaIncrease(const Band& aTopBand,
184                                              const Band& aBottomBand) {
185   uint32_t totalArea = 0;
186 
187   uint32_t topHeight = aBottomBand.top - aTopBand.top;
188   uint32_t bottomHeight = aBottomBand.bottom - aTopBand.bottom;
189   uint32_t currentStripBottom = 0;
190 
191   // This could be done with slightly better worse case performance by merging
192   // these two for-loops, but this makes the code a lot easier to understand.
193   for (auto& strip : aTopBand.mStrips) {
194     if (currentStripBottom == aBottomBand.mStrips.Length() ||
195         strip.right < aBottomBand.mStrips[currentStripBottom].left) {
196       totalArea += bottomHeight * strip.Size();
197       continue;
198     }
199 
200     int32_t currentX = strip.left;
201     while (currentStripBottom != aBottomBand.mStrips.Length() &&
202            aBottomBand.mStrips[currentStripBottom].left < strip.right) {
203       if (currentX >= strip.right) {
204         break;
205       }
206       if (currentX < aBottomBand.mStrips[currentStripBottom].left) {
207         // Add the part that's not intersecting.
208         totalArea += (aBottomBand.mStrips[currentStripBottom].left - currentX) *
209                      bottomHeight;
210       }
211 
212       currentX =
213           std::max(aBottomBand.mStrips[currentStripBottom].right, currentX);
214       currentStripBottom++;
215     }
216 
217     // Add remainder of this strip.
218     if (currentX < strip.right) {
219       totalArea += (strip.right - currentX) * bottomHeight;
220     }
221     if (currentStripBottom) {
222       currentStripBottom--;
223     }
224   }
225   uint32_t currentStripTop = 0;
226   for (auto& strip : aBottomBand.mStrips) {
227     if (currentStripTop == aTopBand.mStrips.Length() ||
228         strip.right < aTopBand.mStrips[currentStripTop].left) {
229       totalArea += topHeight * strip.Size();
230       continue;
231     }
232 
233     int32_t currentX = strip.left;
234     while (currentStripTop != aTopBand.mStrips.Length() &&
235            aTopBand.mStrips[currentStripTop].left < strip.right) {
236       if (currentX >= strip.right) {
237         break;
238       }
239       if (currentX < aTopBand.mStrips[currentStripTop].left) {
240         // Add the part that's not intersecting.
241         totalArea +=
242             (aTopBand.mStrips[currentStripTop].left - currentX) * topHeight;
243       }
244 
245       currentX = std::max(aTopBand.mStrips[currentStripTop].right, currentX);
246       currentStripTop++;
247     }
248 
249     // Add remainder of this strip.
250     if (currentX < strip.right) {
251       totalArea += (strip.right - currentX) * topHeight;
252     }
253     if (currentStripTop) {
254       currentStripTop--;
255     }
256   }
257   return totalArea;
258 }
259 
SimplifyOutwardByArea(uint32_t aThreshold)260 void nsRegion::SimplifyOutwardByArea(uint32_t aThreshold) {
261   if (mBands.Length() < 2) {
262     // We have only one or no row and we're done.
263     return;
264   }
265 
266   uint32_t currentBand = 0;
267   do {
268     Band& band = mBands[currentBand];
269 
270     uint32_t totalArea =
271         ComputeMergedAreaIncrease(band, mBands[currentBand + 1]);
272 
273     if (totalArea <= aThreshold) {
274       for (Strip& strip : mBands[currentBand + 1].mStrips) {
275         // This could use an optimized function to merge two bands.
276         band.InsertStrip(strip);
277       }
278       band.bottom = mBands[currentBand + 1].bottom;
279       mBands.RemoveElementAt(currentBand + 1);
280     } else {
281       currentBand++;
282     }
283   } while (currentBand + 1 < mBands.Length());
284 
285   EnsureSimplified();
286   AssertState();
287 }
288 
289 typedef void (*visit_fn)(void* closure, VisitSide side, int x1, int y1, int x2,
290                          int y2);
291 
VisitEdges(visit_fn visit,void * closure) const292 void nsRegion::VisitEdges(visit_fn visit, void* closure) const {
293   if (mBands.IsEmpty()) {
294     visit(closure, VisitSide::LEFT, mBounds.X(), mBounds.Y(), mBounds.X(),
295           mBounds.YMost());
296     visit(closure, VisitSide::RIGHT, mBounds.XMost(), mBounds.Y(),
297           mBounds.XMost(), mBounds.YMost());
298     visit(closure, VisitSide::TOP, mBounds.X() - 1, mBounds.Y(),
299           mBounds.XMost() + 1, mBounds.Y());
300     visit(closure, VisitSide::BOTTOM, mBounds.X() - 1, mBounds.YMost(),
301           mBounds.XMost() + 1, mBounds.YMost());
302     return;
303   }
304 
305   auto band = std::begin(mBands);
306   auto bandFinal = std::end(mBands);
307   bandFinal--;
308   for (const Strip& strip : band->mStrips) {
309     visit(closure, VisitSide::LEFT, strip.left, band->top, strip.left,
310           band->bottom);
311     visit(closure, VisitSide::RIGHT, strip.right, band->top, strip.right,
312           band->bottom);
313     visit(closure, VisitSide::TOP, strip.left - 1, band->top, strip.right + 1,
314           band->top);
315   }
316 
317   if (band != bandFinal) {
318     do {
319       const Band& topBand = *band;
320       band++;
321 
322       for (const Strip& strip : band->mStrips) {
323         visit(closure, VisitSide::LEFT, strip.left, band->top, strip.left,
324               band->bottom);
325         visit(closure, VisitSide::RIGHT, strip.right, band->top, strip.right,
326               band->bottom);
327       }
328 
329       if (band->top == topBand.bottom) {
330         // Two bands touching each other vertically.
331         const Band& bottomBand = *band;
332         auto topStrip = std::begin(topBand.mStrips);
333         auto bottomStrip = std::begin(bottomBand.mStrips);
334 
335         int y = topBand.bottom;
336 
337         // State from this point on along the vertical edge:
338         // 0 - Empty
339         // 1 - Touched by top rect
340         // 2 - Touched by bottom rect
341         // 3 - Touched on both sides
342         int state;
343         const int TouchedByNothing = 0;
344         const int TouchedByTop = 1;
345         const int TouchedByBottom = 2;
346         // We always start with nothing.
347         int oldState = TouchedByNothing;
348         // Last state change, adjusted by -1 if the last state change was
349         // a change away from 0.
350         int lastX = std::min(topStrip->left, bottomStrip->left) - 1;
351 
352         // Current edge being considered for top and bottom,
353         // 0 - left, 1 - right.
354         bool topEdgeIsLeft = true;
355         bool bottomEdgeIsLeft = true;
356         while (topStrip != std::end(topBand.mStrips) &&
357                bottomStrip != std::end(bottomBand.mStrips)) {
358           int topPos;
359           int bottomPos;
360           if (topEdgeIsLeft) {
361             topPos = topStrip->left;
362           } else {
363             topPos = topStrip->right;
364           }
365           if (bottomEdgeIsLeft) {
366             bottomPos = bottomStrip->left;
367           } else {
368             bottomPos = bottomStrip->right;
369           }
370 
371           int currentX = std::min(topPos, bottomPos);
372           if (topPos < bottomPos) {
373             if (topEdgeIsLeft) {
374               state = oldState | TouchedByTop;
375             } else {
376               state = oldState ^ TouchedByTop;
377               topStrip++;
378             }
379             topEdgeIsLeft = !topEdgeIsLeft;
380           } else if (bottomPos < topPos) {
381             if (bottomEdgeIsLeft) {
382               state = oldState | TouchedByBottom;
383             } else {
384               state = oldState ^ TouchedByBottom;
385               bottomStrip++;
386             }
387             bottomEdgeIsLeft = !bottomEdgeIsLeft;
388           } else {
389             // bottomPos == topPos
390             state = TouchedByNothing;
391             if (bottomEdgeIsLeft) {
392               state = TouchedByBottom;
393             } else {
394               bottomStrip++;
395             }
396             if (topEdgeIsLeft) {
397               state |= TouchedByTop;
398             } else {
399               topStrip++;
400             }
401             topEdgeIsLeft = !topEdgeIsLeft;
402             bottomEdgeIsLeft = !bottomEdgeIsLeft;
403           }
404 
405           MOZ_ASSERT(state != oldState);
406           if (oldState == TouchedByNothing) {
407             // We had nothing before, make sure the left edge will be padded.
408             lastX = currentX - 1;
409           } else if (oldState == TouchedByTop) {
410             if (state == TouchedByNothing) {
411               visit(closure, VisitSide::BOTTOM, lastX, y, currentX + 1, y);
412             } else {
413               visit(closure, VisitSide::BOTTOM, lastX, y, currentX, y);
414               lastX = currentX;
415             }
416           } else if (oldState == TouchedByBottom) {
417             if (state == TouchedByNothing) {
418               visit(closure, VisitSide::TOP, lastX, y, currentX + 1, y);
419             } else {
420               visit(closure, VisitSide::TOP, lastX, y, currentX, y);
421               lastX = currentX;
422             }
423           } else {
424             lastX = currentX;
425           }
426           oldState = state;
427         }
428 
429         MOZ_ASSERT(!state || (topEdgeIsLeft || bottomEdgeIsLeft));
430         if (topStrip != std::end(topBand.mStrips)) {
431           if (!topEdgeIsLeft) {
432             visit(closure, VisitSide::BOTTOM, lastX, y, topStrip->right + 1, y);
433             topStrip++;
434           }
435           while (topStrip != std::end(topBand.mStrips)) {
436             visit(closure, VisitSide::BOTTOM, topStrip->left - 1, y,
437                   topStrip->right + 1, y);
438             topStrip++;
439           }
440         } else if (bottomStrip != std::end(bottomBand.mStrips)) {
441           if (!bottomEdgeIsLeft) {
442             visit(closure, VisitSide::TOP, lastX, y, bottomStrip->right + 1, y);
443             bottomStrip++;
444           }
445           while (bottomStrip != std::end(bottomBand.mStrips)) {
446             visit(closure, VisitSide::TOP, bottomStrip->left - 1, y,
447                   bottomStrip->right + 1, y);
448             bottomStrip++;
449           }
450         }
451       } else {
452         for (const Strip& strip : topBand.mStrips) {
453           visit(closure, VisitSide::BOTTOM, strip.left - 1, topBand.bottom,
454                 strip.right + 1, topBand.bottom);
455         }
456         for (const Strip& strip : band->mStrips) {
457           visit(closure, VisitSide::TOP, strip.left - 1, band->top,
458                 strip.right + 1, band->top);
459         }
460       }
461     } while (band != bandFinal);
462   }
463 
464   for (const Strip& strip : band->mStrips) {
465     visit(closure, VisitSide::BOTTOM, strip.left - 1, band->bottom,
466           strip.right + 1, band->bottom);
467   }
468 }
469 
SimplifyInward(uint32_t aMaxRects)470 void nsRegion::SimplifyInward(uint32_t aMaxRects) {
471   NS_ASSERTION(aMaxRects >= 1, "Invalid max rect count");
472 
473   if (GetNumRects() <= aMaxRects) return;
474 
475   SetEmpty();
476 }
477 
Area() const478 uint64_t nsRegion::Area() const {
479   if (mBands.IsEmpty()) {
480     return mBounds.Area();
481   }
482 
483   uint64_t area = 0;
484   for (const Band& band : mBands) {
485     uint32_t height = band.bottom - band.top;
486     for (const Strip& strip : band.mStrips) {
487       area += (strip.right - strip.left) * height;
488     }
489   }
490 
491   return area;
492 }
493 
ScaleRoundOut(float aXScale,float aYScale)494 nsRegion& nsRegion::ScaleRoundOut(float aXScale, float aYScale) {
495   if (mozilla::gfx::FuzzyEqual(aXScale, 1.0f) &&
496       mozilla::gfx::FuzzyEqual(aYScale, 1.0f)) {
497     return *this;
498   }
499 
500   nsRegion newRegion;
501   for (RectIterator iter = RectIterator(*this); !iter.Done(); iter.Next()) {
502     nsRectAbsolute rect = iter.GetAbsolute();
503     rect.ScaleRoundOut(aXScale, aYScale);
504     newRegion.AddRect(rect);
505   }
506 
507   *this = std::move(newRegion);
508   return *this;
509 }
510 
ScaleInverseRoundOut(float aXScale,float aYScale)511 nsRegion& nsRegion::ScaleInverseRoundOut(float aXScale, float aYScale) {
512   nsRegion newRegion;
513   for (RectIterator iter = RectIterator(*this); !iter.Done(); iter.Next()) {
514     nsRectAbsolute rect = iter.GetAbsolute();
515     rect.ScaleInverseRoundOut(aXScale, aYScale);
516     newRegion.AddRect(rect);
517   }
518 
519   *this = std::move(newRegion);
520   return *this;
521 }
522 
TransformRect(const mozilla::gfx::IntRect & aRect,const mozilla::gfx::Matrix4x4 & aTransform)523 static mozilla::gfx::IntRect TransformRect(
524     const mozilla::gfx::IntRect& aRect,
525     const mozilla::gfx::Matrix4x4& aTransform) {
526   if (aRect.IsEmpty()) {
527     return mozilla::gfx::IntRect();
528   }
529 
530   mozilla::gfx::RectDouble rect(aRect.X(), aRect.Y(), aRect.Width(),
531                                 aRect.Height());
532   rect = aTransform.TransformAndClipBounds(
533       rect, mozilla::gfx::RectDouble::MaxIntRect());
534   rect.RoundOut();
535 
536   mozilla::gfx::IntRect intRect;
537   if (!gfxUtils::GfxRectToIntRect(ThebesRect(rect), &intRect)) {
538     return mozilla::gfx::IntRect();
539   }
540 
541   return intRect;
542 }
543 
Transform(const mozilla::gfx::Matrix4x4 & aTransform)544 nsRegion& nsRegion::Transform(const mozilla::gfx::Matrix4x4& aTransform) {
545   nsRegion newRegion;
546   for (RectIterator iter = RectIterator(*this); !iter.Done(); iter.Next()) {
547     nsRect rect = nsIntRegion::ToRect(
548         TransformRect(nsIntRegion::FromRect(iter.Get()), aTransform));
549     newRegion.AddRect(nsRectAbsolute::FromRect(rect));
550   }
551 
552   *this = std::move(newRegion);
553   return *this;
554 }
555 
ScaleToOtherAppUnitsRoundOut(int32_t aFromAPP,int32_t aToAPP) const556 nsRegion nsRegion::ScaleToOtherAppUnitsRoundOut(int32_t aFromAPP,
557                                                 int32_t aToAPP) const {
558   if (aFromAPP == aToAPP) {
559     return *this;
560   }
561   nsRegion newRegion;
562   for (RectIterator iter = RectIterator(*this); !iter.Done(); iter.Next()) {
563     nsRect rect = iter.Get();
564     rect = rect.ScaleToOtherAppUnitsRoundOut(aFromAPP, aToAPP);
565     newRegion.AddRect(nsRectAbsolute::FromRect(rect));
566   }
567 
568   return newRegion;
569 }
570 
ScaleToOtherAppUnitsRoundIn(int32_t aFromAPP,int32_t aToAPP) const571 nsRegion nsRegion::ScaleToOtherAppUnitsRoundIn(int32_t aFromAPP,
572                                                int32_t aToAPP) const {
573   if (aFromAPP == aToAPP) {
574     return *this;
575   }
576 
577   nsRegion newRegion;
578   for (RectIterator iter = RectIterator(*this); !iter.Done(); iter.Next()) {
579     nsRect rect = iter.Get();
580     rect = rect.ScaleToOtherAppUnitsRoundIn(aFromAPP, aToAPP);
581     newRegion.AddRect(nsRectAbsolute::FromRect(rect));
582   }
583 
584   return newRegion;
585 }
586 
ToPixels(nscoord aAppUnitsPerPixel,bool aOutsidePixels) const587 nsIntRegion nsRegion::ToPixels(nscoord aAppUnitsPerPixel,
588                                bool aOutsidePixels) const {
589   nsIntRegion intRegion;
590   for (RectIterator iter = RectIterator(*this); !iter.Done(); iter.Next()) {
591     mozilla::gfx::IntRect deviceRect;
592     nsRect rect = iter.Get();
593     if (aOutsidePixels)
594       deviceRect = rect.ToOutsidePixels(aAppUnitsPerPixel);
595     else
596       deviceRect = rect.ToNearestPixels(aAppUnitsPerPixel);
597     intRegion.OrWith(deviceRect);
598   }
599 
600   return intRegion;
601 }
602 
ToOutsidePixels(nscoord aAppUnitsPerPixel) const603 nsIntRegion nsRegion::ToOutsidePixels(nscoord aAppUnitsPerPixel) const {
604   return ToPixels(aAppUnitsPerPixel, true);
605 }
606 
ToNearestPixels(nscoord aAppUnitsPerPixel) const607 nsIntRegion nsRegion::ToNearestPixels(nscoord aAppUnitsPerPixel) const {
608   return ToPixels(aAppUnitsPerPixel, false);
609 }
610 
ScaleToNearestPixels(float aScaleX,float aScaleY,nscoord aAppUnitsPerPixel) const611 nsIntRegion nsRegion::ScaleToNearestPixels(float aScaleX, float aScaleY,
612                                            nscoord aAppUnitsPerPixel) const {
613   nsIntRegion result;
614   for (auto iter = RectIter(); !iter.Done(); iter.Next()) {
615     mozilla::gfx::IntRect deviceRect =
616         iter.Get().ScaleToNearestPixels(aScaleX, aScaleY, aAppUnitsPerPixel);
617     result.Or(result, deviceRect);
618   }
619   return result;
620 }
621 
ScaleToOutsidePixels(float aScaleX,float aScaleY,nscoord aAppUnitsPerPixel) const622 nsIntRegion nsRegion::ScaleToOutsidePixels(float aScaleX, float aScaleY,
623                                            nscoord aAppUnitsPerPixel) const {
624   // make a copy of the region so that we can mutate it inplace
625   nsIntRegion intRegion;
626   for (RectIterator iter = RectIterator(*this); !iter.Done(); iter.Next()) {
627     nsRect rect = iter.Get();
628     intRegion.OrWith(
629         rect.ScaleToOutsidePixels(aScaleX, aScaleY, aAppUnitsPerPixel));
630   }
631   return intRegion;
632 }
633 
ScaleToInsidePixels(float aScaleX,float aScaleY,nscoord aAppUnitsPerPixel) const634 nsIntRegion nsRegion::ScaleToInsidePixels(float aScaleX, float aScaleY,
635                                           nscoord aAppUnitsPerPixel) const {
636   /* When scaling a rect, walk forward through the rect list up until the y
637    * value is greater than the current rect's YMost() value.
638    *
639    * For each rect found, check if the rects have a touching edge (in unscaled
640    * coordinates), and if one edge is entirely contained within the other.
641    *
642    * If it is, then the contained edge can be moved (in scaled pixels) to ensure
643    * that no gap exists.
644    *
645    * Since this could be potentially expensive - O(n^2), we only attempt this
646    * algorithm for the first rect.
647    */
648 
649   if (mBands.IsEmpty()) {
650     nsIntRect rect = mBounds.ToNSRect().ScaleToInsidePixels(aScaleX, aScaleY,
651                                                             aAppUnitsPerPixel);
652     return nsIntRegion(rect);
653   }
654 
655   nsIntRegion intRegion;
656   RectIterator iter = RectIterator(*this);
657 
658   nsRect first = iter.Get();
659 
660   mozilla::gfx::IntRect firstDeviceRect =
661       first.ScaleToInsidePixels(aScaleX, aScaleY, aAppUnitsPerPixel);
662 
663   for (iter.Next(); !iter.Done(); iter.Next()) {
664     nsRect rect = iter.Get();
665     mozilla::gfx::IntRect deviceRect =
666         rect.ScaleToInsidePixels(aScaleX, aScaleY, aAppUnitsPerPixel);
667 
668     if (rect.Y() <= first.YMost()) {
669       if (rect.XMost() == first.X() && rect.YMost() <= first.YMost()) {
670         // rect is touching on the left edge of the first rect and contained
671         // within the length of its left edge
672         deviceRect.SetRightEdge(firstDeviceRect.X());
673       } else if (rect.X() == first.XMost() && rect.YMost() <= first.YMost()) {
674         // rect is touching on the right edge of the first rect and contained
675         // within the length of its right edge
676         deviceRect.SetLeftEdge(firstDeviceRect.XMost());
677       } else if (rect.Y() == first.YMost()) {
678         // The bottom of the first rect is on the same line as the top of rect,
679         // but they aren't necessarily contained.
680         if (rect.X() <= first.X() && rect.XMost() >= first.XMost()) {
681           // The top of rect contains the bottom of the first rect
682           firstDeviceRect.SetBottomEdge(deviceRect.Y());
683         } else if (rect.X() >= first.X() && rect.XMost() <= first.XMost()) {
684           // The bottom of the first contains the top of rect
685           deviceRect.SetTopEdge(firstDeviceRect.YMost());
686         }
687       }
688     }
689 
690     intRegion.OrWith(deviceRect);
691   }
692 
693   intRegion.OrWith(firstDeviceRect);
694   return intRegion;
695 }
696 
697 // A cell's "value" is a pair consisting of
698 // a) the area of the subrectangle it corresponds to, if it's in
699 // aContainingRect and in the region, 0 otherwise
700 // b) the area of the subrectangle it corresponds to, if it's in the region,
701 // 0 otherwise
702 // Addition, subtraction and identity are defined on these values in the
703 // obvious way. Partial order is lexicographic.
704 // A "large negative value" is defined with large negative numbers for both
705 // fields of the pair. This negative value has the property that adding any
706 // number of non-negative values to it always results in a negative value.
707 //
708 // The GetLargestRectangle algorithm works in three phases:
709 //  1) Convert the region into a grid by adding vertical/horizontal lines for
710 //     each edge of each rectangle in the region.
711 //  2) For each rectangle in the region, for each cell it contains, set that
712 //     cells's value as described above.
713 //  3) Calculate the submatrix with the largest sum such that none of its cells
714 //     contain any 0s (empty regions). The rectangle represented by the
715 //     submatrix is the largest rectangle in the region.
716 //
717 // Let k be the number of rectangles in the region.
718 // Let m be the height of the grid generated in step 1.
719 // Let n be the width of the grid generated in step 1.
720 //
721 // Step 1 is O(k) in time and O(m+n) in space for the sparse grid.
722 // Step 2 is O(mn) in time and O(mn) in additional space for the full grid.
723 // Step 3 is O(m^2 n) in time and O(mn) in additional space
724 //
725 // The implementation of steps 1 and 2 are rather straightforward. However our
726 // implementation of step 3 uses dynamic programming to achieve its efficiency.
727 //
728 // Psuedo code for step 3 is as follows where G is the grid from step 1 and A
729 // is the array from step 2:
730 // Phase3 = function (G, A, m, n) {
731 //   let (t,b,l,r,_) = MaxSum2D(A,m,n)
732 //   return rect(G[t],G[l],G[r],G[b]);
733 // }
734 // MaxSum2D = function (A, m, n) {
735 //   S = array(m+1,n+1)
736 //   S[0][i] = 0 for i in [0,n]
737 //   S[j][0] = 0 for j in [0,m]
738 //   S[j][i] = (if A[j-1][i-1] = 0 then some large negative value
739 //                                 else A[j-1][i-1])
740 //           + S[j-1][n] + S[j][i-1] - S[j-1][i-1]
741 //
742 //   // top, bottom, left, right, area
743 //   var maxRect = (-1, -1, -1, -1, 0);
744 //
745 //   for all (m',m'') in [0, m]^2 {
746 //     let B = { S[m'][i] - S[m''][i] | 0 <= i <= n }
747 //     let ((l,r),area) = MaxSum1D(B,n+1)
748 //     if (area > maxRect.area) {
749 //       maxRect := (m', m'', l, r, area)
750 //     }
751 //   }
752 //
753 //   return maxRect;
754 // }
755 //
756 // Originally taken from Improved algorithms for the k-maximum subarray problem
757 // for small k - SE Bae, T Takaoka but modified to show the explicit tracking
758 // of indices and we already have the prefix sums from our one call site so
759 // there's no need to construct them.
760 // MaxSum1D = function (A,n) {
761 //   var minIdx = 0;
762 //   var min = 0;
763 //   var maxIndices = (0,0);
764 //   var max = 0;
765 //   for i in range(n) {
766 //     let cand = A[i] - min;
767 //     if (cand > max) {
768 //       max := cand;
769 //       maxIndices := (minIdx, i)
770 //     }
771 //     if (min > A[i]) {
772 //       min := A[i];
773 //       minIdx := i;
774 //     }
775 //   }
776 //   return (minIdx, maxIdx, max);
777 // }
778 
779 namespace {
780 // This class represents a partitioning of an axis delineated by coordinates.
781 // It internally maintains a sorted array of coordinates.
782 class AxisPartition {
783  public:
784   // Adds a new partition at the given coordinate to this partitioning. If
785   // the coordinate is already present in the partitioning, this does nothing.
InsertCoord(nscoord c)786   void InsertCoord(nscoord c) {
787     uint32_t i = mStops.IndexOfFirstElementGt(c);
788     if (i == 0 || mStops[i - 1] != c) {
789       mStops.InsertElementAt(i, c);
790     }
791   }
792 
793   // Returns the array index of the given partition point. The partition
794   // point must already be present in the partitioning.
IndexOf(nscoord p) const795   int32_t IndexOf(nscoord p) const { return mStops.BinaryIndexOf(p); }
796 
797   // Returns the partition at the given index which must be non-zero and
798   // less than the number of partitions in this partitioning.
StopAt(int32_t index) const799   nscoord StopAt(int32_t index) const { return mStops[index]; }
800 
801   // Returns the size of the gap between the partition at the given index and
802   // the next partition in this partitioning. If the index is the last index
803   // in the partitioning, the result is undefined.
StopSize(int32_t index) const804   nscoord StopSize(int32_t index) const {
805     return mStops[index + 1] - mStops[index];
806   }
807 
808   // Returns the number of partitions in this partitioning.
GetNumStops() const809   int32_t GetNumStops() const { return mStops.Length(); }
810 
811  private:
812   nsTArray<nscoord> mStops;
813 };
814 
815 const int64_t kVeryLargeNegativeNumber = 0xffff000000000000ll;
816 
817 struct SizePair {
818   int64_t mSizeContainingRect;
819   int64_t mSize;
820 
SizePair__anon8f2486cc0111::SizePair821   SizePair() : mSizeContainingRect(0), mSize(0) {}
822 
VeryLargeNegative__anon8f2486cc0111::SizePair823   static SizePair VeryLargeNegative() {
824     SizePair result;
825     result.mSize = result.mSizeContainingRect = kVeryLargeNegativeNumber;
826     return result;
827   }
operator <__anon8f2486cc0111::SizePair828   bool operator<(const SizePair& aOther) const {
829     if (mSizeContainingRect < aOther.mSizeContainingRect) return true;
830     if (mSizeContainingRect > aOther.mSizeContainingRect) return false;
831     return mSize < aOther.mSize;
832   }
operator >__anon8f2486cc0111::SizePair833   bool operator>(const SizePair& aOther) const {
834     return aOther.operator<(*this);
835   }
operator +__anon8f2486cc0111::SizePair836   SizePair operator+(const SizePair& aOther) const {
837     SizePair result = *this;
838     result.mSizeContainingRect += aOther.mSizeContainingRect;
839     result.mSize += aOther.mSize;
840     return result;
841   }
operator -__anon8f2486cc0111::SizePair842   SizePair operator-(const SizePair& aOther) const {
843     SizePair result = *this;
844     result.mSizeContainingRect -= aOther.mSizeContainingRect;
845     result.mSize -= aOther.mSize;
846     return result;
847   }
848 };
849 
850 // Returns the sum and indices of the subarray with the maximum sum of the
851 // given array (A,n), assuming the array is already in prefix sum form.
MaxSum1D(const nsTArray<SizePair> & A,int32_t n,int32_t * minIdx,int32_t * maxIdx)852 SizePair MaxSum1D(const nsTArray<SizePair>& A, int32_t n, int32_t* minIdx,
853                   int32_t* maxIdx) {
854   // The min/max indicies of the largest subarray found so far
855   SizePair min, max;
856   int32_t currentMinIdx = 0;
857 
858   *minIdx = 0;
859   *maxIdx = 0;
860 
861   // Because we're given the array in prefix sum form, we know the first
862   // element is 0
863   for (int32_t i = 1; i < n; i++) {
864     SizePair cand = A[i] - min;
865     if (cand > max) {
866       max = cand;
867       *minIdx = currentMinIdx;
868       *maxIdx = i;
869     }
870     if (min > A[i]) {
871       min = A[i];
872       currentMinIdx = i;
873     }
874   }
875 
876   return max;
877 }
878 }  // namespace
879 
GetLargestRectangle(const nsRect & aContainingRect) const880 nsRect nsRegion::GetLargestRectangle(const nsRect& aContainingRect) const {
881   nsRect bestRect;
882 
883   if (GetNumRects() <= 1) {
884     bestRect = GetBounds();
885     return bestRect;
886   }
887 
888   AxisPartition xaxis, yaxis;
889 
890   // Step 1: Calculate the grid lines
891   for (auto iter = RectIter(); !iter.Done(); iter.Next()) {
892     const nsRect& rect = iter.Get();
893     xaxis.InsertCoord(rect.X());
894     xaxis.InsertCoord(rect.XMost());
895     yaxis.InsertCoord(rect.Y());
896     yaxis.InsertCoord(rect.YMost());
897   }
898   if (!aContainingRect.IsEmpty()) {
899     xaxis.InsertCoord(aContainingRect.X());
900     xaxis.InsertCoord(aContainingRect.XMost());
901     yaxis.InsertCoord(aContainingRect.Y());
902     yaxis.InsertCoord(aContainingRect.YMost());
903   }
904 
905   // Step 2: Fill out the grid with the areas
906   // Note: due to the ordering of rectangles in the region, it is not always
907   // possible to combine steps 2 and 3 so we don't try to be clever.
908   int32_t matrixHeight = yaxis.GetNumStops() - 1;
909   int32_t matrixWidth = xaxis.GetNumStops() - 1;
910   int32_t matrixSize = matrixHeight * matrixWidth;
911   nsTArray<SizePair> areas(matrixSize);
912   areas.SetLength(matrixSize);
913 
914   for (auto iter = RectIter(); !iter.Done(); iter.Next()) {
915     const nsRect& rect = iter.Get();
916     int32_t xstart = xaxis.IndexOf(rect.X());
917     int32_t xend = xaxis.IndexOf(rect.XMost());
918     int32_t y = yaxis.IndexOf(rect.Y());
919     int32_t yend = yaxis.IndexOf(rect.YMost());
920 
921     for (; y < yend; y++) {
922       nscoord height = yaxis.StopSize(y);
923       for (int32_t x = xstart; x < xend; x++) {
924         nscoord width = xaxis.StopSize(x);
925         int64_t size = width * int64_t(height);
926         if (rect.Intersects(aContainingRect)) {
927           areas[y * matrixWidth + x].mSizeContainingRect = size;
928         }
929         areas[y * matrixWidth + x].mSize = size;
930       }
931     }
932   }
933 
934   // Step 3: Find the maximum submatrix sum that does not contain a rectangle
935   {
936     // First get the prefix sum array
937     int32_t m = matrixHeight + 1;
938     int32_t n = matrixWidth + 1;
939     nsTArray<SizePair> pareas(m * n);
940     pareas.SetLength(m * n);
941     for (int32_t y = 1; y < m; y++) {
942       for (int32_t x = 1; x < n; x++) {
943         SizePair area = areas[(y - 1) * matrixWidth + x - 1];
944         if (!area.mSize) {
945           area = SizePair::VeryLargeNegative();
946         }
947         area = area + pareas[y * n + x - 1] + pareas[(y - 1) * n + x] -
948                pareas[(y - 1) * n + x - 1];
949         pareas[y * n + x] = area;
950       }
951     }
952 
953     // No longer need the grid
954     areas.SetLength(0);
955 
956     SizePair bestArea;
957     struct {
958       int32_t left, top, right, bottom;
959     } bestRectIndices = {0, 0, 0, 0};
960     for (int32_t m1 = 0; m1 < m; m1++) {
961       for (int32_t m2 = m1 + 1; m2 < m; m2++) {
962         nsTArray<SizePair> B;
963         B.SetLength(n);
964         for (int32_t i = 0; i < n; i++) {
965           B[i] = pareas[m2 * n + i] - pareas[m1 * n + i];
966         }
967         int32_t minIdx, maxIdx;
968         SizePair area = MaxSum1D(B, n, &minIdx, &maxIdx);
969         if (area > bestArea) {
970           bestRectIndices.left = minIdx;
971           bestRectIndices.top = m1;
972           bestRectIndices.right = maxIdx;
973           bestRectIndices.bottom = m2;
974           bestArea = area;
975         }
976       }
977     }
978 
979     bestRect.MoveTo(xaxis.StopAt(bestRectIndices.left),
980                     yaxis.StopAt(bestRectIndices.top));
981     bestRect.SizeTo(xaxis.StopAt(bestRectIndices.right) - bestRect.X(),
982                     yaxis.StopAt(bestRectIndices.bottom) - bestRect.Y());
983   }
984 
985   return bestRect;
986 }
987 
operator <<(std::ostream & stream,const nsRegion & m)988 std::ostream& operator<<(std::ostream& stream, const nsRegion& m) {
989   stream << "[";
990 
991   bool first = true;
992   for (auto iter = m.RectIter(); !iter.Done(); iter.Next()) {
993     if (!first) {
994       stream << "; ";
995     } else {
996       first = false;
997     }
998     const nsRect& rect = iter.Get();
999     stream << rect.X() << "," << rect.Y() << "," << rect.XMost() << ","
1000            << rect.YMost();
1001   }
1002 
1003   stream << "]";
1004   return stream;
1005 }
1006 
OutputToStream(std::string aObjName,std::ostream & stream) const1007 void nsRegion::OutputToStream(std::string aObjName,
1008                               std::ostream& stream) const {
1009   auto iter = RectIter();
1010   nsRect r = iter.Get();
1011   stream << "nsRegion " << aObjName << "(nsRect(" << r.X() << ", " << r.Y()
1012          << ", " << r.Width() << ", " << r.Height() << "));\n";
1013   iter.Next();
1014 
1015   for (; !iter.Done(); iter.Next()) {
1016     nsRect r = iter.Get();
1017     stream << aObjName << ".OrWith(nsRect(" << r.X() << ", " << r.Y() << ", "
1018            << r.Width() << ", " << r.Height() << "));\n";
1019   }
1020 }
1021 
ToString() const1022 nsCString nsRegion::ToString() const {
1023   return nsCString(mozilla::ToString(*this).c_str());
1024 }
1025