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 /* class that manages rules for positioning floats */
8 
9 #include "nsFloatManager.h"
10 
11 #include <algorithm>
12 #include <initializer_list>
13 
14 #include "gfxContext.h"
15 #include "mozilla/PresShell.h"
16 #include "mozilla/ReflowInput.h"
17 #include "mozilla/ShapeUtils.h"
18 #include "nsBlockFrame.h"
19 #include "nsDeviceContext.h"
20 #include "nsError.h"
21 #include "nsIFrame.h"
22 #include "nsIFrameInlines.h"
23 #include "nsImageRenderer.h"
24 #include "nsMemory.h"
25 
26 using namespace mozilla;
27 using namespace mozilla::image;
28 using namespace mozilla::gfx;
29 
30 int32_t nsFloatManager::sCachedFloatManagerCount = 0;
31 void* nsFloatManager::sCachedFloatManagers[NS_FLOAT_MANAGER_CACHE_SIZE];
32 
33 /////////////////////////////////////////////////////////////////////////////
34 // nsFloatManager
35 
nsFloatManager(PresShell * aPresShell,WritingMode aWM)36 nsFloatManager::nsFloatManager(PresShell* aPresShell, WritingMode aWM)
37     :
38 #ifdef DEBUG
39       mWritingMode(aWM),
40 #endif
41       mLineLeft(0),
42       mBlockStart(0),
43       mFloatDamage(aPresShell),
44       mPushedLeftFloatPastBreak(false),
45       mPushedRightFloatPastBreak(false),
46       mSplitLeftFloatAcrossBreak(false),
47       mSplitRightFloatAcrossBreak(false) {
48   MOZ_COUNT_CTOR(nsFloatManager);
49 }
50 
~nsFloatManager()51 nsFloatManager::~nsFloatManager() { MOZ_COUNT_DTOR(nsFloatManager); }
52 
53 // static
operator new(size_t aSize)54 void* nsFloatManager::operator new(size_t aSize) noexcept(true) {
55   if (sCachedFloatManagerCount > 0) {
56     // We have cached unused instances of this class, return a cached
57     // instance in stead of always creating a new one.
58     return sCachedFloatManagers[--sCachedFloatManagerCount];
59   }
60 
61   // The cache is empty, this means we have to create a new instance using
62   // the global |operator new|.
63   return moz_xmalloc(aSize);
64 }
65 
operator delete(void * aPtr,size_t aSize)66 void nsFloatManager::operator delete(void* aPtr, size_t aSize) {
67   if (!aPtr) return;
68   // This float manager is no longer used, if there's still room in
69   // the cache we'll cache this float manager, unless the layout
70   // module was already shut down.
71 
72   if (sCachedFloatManagerCount < NS_FLOAT_MANAGER_CACHE_SIZE &&
73       sCachedFloatManagerCount >= 0) {
74     // There's still space in the cache for more instances, put this
75     // instance in the cache in stead of deleting it.
76 
77     sCachedFloatManagers[sCachedFloatManagerCount++] = aPtr;
78     return;
79   }
80 
81   // The cache is full, or the layout module has been shut down,
82   // delete this float manager.
83   free(aPtr);
84 }
85 
86 /* static */
Shutdown()87 void nsFloatManager::Shutdown() {
88   // The layout module is being shut down, clean up the cache and
89   // disable further caching.
90 
91   int32_t i;
92 
93   for (i = 0; i < sCachedFloatManagerCount; i++) {
94     void* floatManager = sCachedFloatManagers[i];
95     if (floatManager) free(floatManager);
96   }
97 
98   // Disable further caching.
99   sCachedFloatManagerCount = -1;
100 }
101 
102 #define CHECK_BLOCK_AND_LINE_DIR(aWM)                                       \
103   NS_ASSERTION((aWM).GetBlockDir() == mWritingMode.GetBlockDir() &&         \
104                    (aWM).IsLineInverted() == mWritingMode.IsLineInverted(), \
105                "incompatible writing modes")
106 
GetFlowArea(WritingMode aWM,nscoord aBCoord,nscoord aBSize,BandInfoType aBandInfoType,ShapeType aShapeType,LogicalRect aContentArea,SavedState * aState,const nsSize & aContainerSize) const107 nsFlowAreaRect nsFloatManager::GetFlowArea(
108     WritingMode aWM, nscoord aBCoord, nscoord aBSize,
109     BandInfoType aBandInfoType, ShapeType aShapeType, LogicalRect aContentArea,
110     SavedState* aState, const nsSize& aContainerSize) const {
111   CHECK_BLOCK_AND_LINE_DIR(aWM);
112   NS_ASSERTION(aBSize >= 0, "unexpected max block size");
113   NS_ASSERTION(aContentArea.ISize(aWM) >= 0,
114                "unexpected content area inline size");
115 
116   nscoord blockStart = aBCoord + mBlockStart;
117   if (blockStart < nscoord_MIN) {
118     NS_WARNING("bad value");
119     blockStart = nscoord_MIN;
120   }
121 
122   // Determine the last float that we should consider.
123   uint32_t floatCount;
124   if (aState) {
125     // Use the provided state.
126     floatCount = aState->mFloatInfoCount;
127     MOZ_ASSERT(floatCount <= mFloats.Length(), "bad state");
128   } else {
129     // Use our current state.
130     floatCount = mFloats.Length();
131   }
132 
133   // If there are no floats at all, or we're below the last one, return
134   // quickly.
135   if (floatCount == 0 || (mFloats[floatCount - 1].mLeftBEnd <= blockStart &&
136                           mFloats[floatCount - 1].mRightBEnd <= blockStart)) {
137     return nsFlowAreaRect(aWM, aContentArea.IStart(aWM), aBCoord,
138                           aContentArea.ISize(aWM), aBSize,
139                           nsFlowAreaRectFlags::NoFlags);
140   }
141 
142   nscoord blockEnd;
143   if (aBSize == nscoord_MAX) {
144     // This warning (and the two below) are possible to hit on pages
145     // with really large objects.
146     NS_WARNING_ASSERTION(aBandInfoType == BandInfoType::BandFromPoint,
147                          "bad height");
148     blockEnd = nscoord_MAX;
149   } else {
150     blockEnd = blockStart + aBSize;
151     if (blockEnd < blockStart || blockEnd > nscoord_MAX) {
152       NS_WARNING("bad value");
153       blockEnd = nscoord_MAX;
154     }
155   }
156   nscoord lineLeft = mLineLeft + aContentArea.LineLeft(aWM, aContainerSize);
157   nscoord lineRight = mLineLeft + aContentArea.LineRight(aWM, aContainerSize);
158   if (lineRight < lineLeft) {
159     NS_WARNING("bad value");
160     lineRight = lineLeft;
161   }
162 
163   // Walk backwards through the floats until we either hit the front of
164   // the list or we're above |blockStart|.
165   bool haveFloats = false;
166   bool mayWiden = false;
167   for (uint32_t i = floatCount; i > 0; --i) {
168     const FloatInfo& fi = mFloats[i - 1];
169     if (fi.mLeftBEnd <= blockStart && fi.mRightBEnd <= blockStart) {
170       // There aren't any more floats that could intersect this band.
171       break;
172     }
173     if (fi.IsEmpty(aShapeType)) {
174       // Ignore empty float areas.
175       // https://drafts.csswg.org/css-shapes/#relation-to-box-model-and-float-behavior
176       continue;
177     }
178 
179     nscoord floatBStart = fi.BStart(aShapeType);
180     nscoord floatBEnd = fi.BEnd(aShapeType);
181     if (blockStart < floatBStart &&
182         aBandInfoType == BandInfoType::BandFromPoint) {
183       // This float is below our band.  Shrink our band's height if needed.
184       if (floatBStart < blockEnd) {
185         blockEnd = floatBStart;
186       }
187     }
188     // If blockStart == blockEnd (which happens only with WidthWithinHeight),
189     // we include floats that begin at our 0-height vertical area.  We
190     // need to do this to satisfy the invariant that a
191     // WidthWithinHeight call is at least as narrow on both sides as a
192     // BandFromPoint call beginning at its blockStart.
193     else if (blockStart < floatBEnd &&
194              (floatBStart < blockEnd ||
195               (floatBStart == blockEnd && blockStart == blockEnd))) {
196       // This float is in our band.
197 
198       // Shrink our band's width if needed.
199       StyleFloat floatStyle = fi.mFrame->StyleDisplay()->mFloat;
200 
201       // When aBandInfoType is BandFromPoint, we're only intended to
202       // consider a point along the y axis rather than a band.
203       const nscoord bandBlockEnd =
204           aBandInfoType == BandInfoType::BandFromPoint ? blockStart : blockEnd;
205       if (floatStyle == StyleFloat::Left) {
206         // A left float
207         nscoord lineRightEdge =
208             fi.LineRight(aShapeType, blockStart, bandBlockEnd);
209         if (lineRightEdge > lineLeft) {
210           lineLeft = lineRightEdge;
211           // Only set haveFloats to true if the float is inside our
212           // containing block.  This matches the spec for what some
213           // callers want and disagrees for other callers, so we should
214           // probably provide better information at some point.
215           haveFloats = true;
216 
217           // Our area may widen in the block direction if this float may
218           // narrow in the block direction.
219           mayWiden = mayWiden || fi.MayNarrowInBlockDirection(aShapeType);
220         }
221       } else {
222         // A right float
223         nscoord lineLeftEdge =
224             fi.LineLeft(aShapeType, blockStart, bandBlockEnd);
225         if (lineLeftEdge < lineRight) {
226           lineRight = lineLeftEdge;
227           // See above.
228           haveFloats = true;
229           mayWiden = mayWiden || fi.MayNarrowInBlockDirection(aShapeType);
230         }
231       }
232 
233       // Shrink our band's height if needed.
234       if (floatBEnd < blockEnd &&
235           aBandInfoType == BandInfoType::BandFromPoint) {
236         blockEnd = floatBEnd;
237       }
238     }
239   }
240 
241   nscoord blockSize =
242       (blockEnd == nscoord_MAX) ? nscoord_MAX : (blockEnd - blockStart);
243   // convert back from LineLeft/Right to IStart
244   nscoord inlineStart =
245       aWM.IsBidiLTR()
246           ? lineLeft - mLineLeft
247           : mLineLeft - lineRight + LogicalSize(aWM, aContainerSize).ISize(aWM);
248 
249   nsFlowAreaRectFlags flags =
250       (haveFloats ? nsFlowAreaRectFlags::HasFloats
251                   : nsFlowAreaRectFlags::NoFlags) |
252       (mayWiden ? nsFlowAreaRectFlags::MayWiden : nsFlowAreaRectFlags::NoFlags);
253 
254   return nsFlowAreaRect(aWM, inlineStart, blockStart - mBlockStart,
255                         lineRight - lineLeft, blockSize, flags);
256 }
257 
AddFloat(nsIFrame * aFloatFrame,const LogicalRect & aMarginRect,WritingMode aWM,const nsSize & aContainerSize)258 void nsFloatManager::AddFloat(nsIFrame* aFloatFrame,
259                               const LogicalRect& aMarginRect, WritingMode aWM,
260                               const nsSize& aContainerSize) {
261   CHECK_BLOCK_AND_LINE_DIR(aWM);
262   NS_ASSERTION(aMarginRect.ISize(aWM) >= 0, "negative inline size!");
263   NS_ASSERTION(aMarginRect.BSize(aWM) >= 0, "negative block size!");
264 
265   FloatInfo info(aFloatFrame, mLineLeft, mBlockStart, aMarginRect, aWM,
266                  aContainerSize);
267 
268   // Set mLeftBEnd and mRightBEnd.
269   if (HasAnyFloats()) {
270     FloatInfo& tail = mFloats[mFloats.Length() - 1];
271     info.mLeftBEnd = tail.mLeftBEnd;
272     info.mRightBEnd = tail.mRightBEnd;
273   } else {
274     info.mLeftBEnd = nscoord_MIN;
275     info.mRightBEnd = nscoord_MIN;
276   }
277   StyleFloat floatStyle = aFloatFrame->StyleDisplay()->mFloat;
278   MOZ_ASSERT(floatStyle == StyleFloat::Left || floatStyle == StyleFloat::Right,
279              "Unexpected float style!");
280   nscoord& sideBEnd =
281       floatStyle == StyleFloat::Left ? info.mLeftBEnd : info.mRightBEnd;
282   nscoord thisBEnd = info.BEnd();
283   if (thisBEnd > sideBEnd) sideBEnd = thisBEnd;
284 
285   mFloats.AppendElement(std::move(info));
286 }
287 
288 // static
CalculateRegionFor(WritingMode aWM,nsIFrame * aFloat,const LogicalMargin & aMargin,const nsSize & aContainerSize)289 LogicalRect nsFloatManager::CalculateRegionFor(WritingMode aWM,
290                                                nsIFrame* aFloat,
291                                                const LogicalMargin& aMargin,
292                                                const nsSize& aContainerSize) {
293   // We consider relatively positioned frames at their original position.
294   LogicalRect region(aWM,
295                      nsRect(aFloat->GetNormalPosition(), aFloat->GetSize()),
296                      aContainerSize);
297 
298   // Float region includes its margin
299   region.Inflate(aWM, aMargin);
300 
301   // Don't store rectangles with negative margin-box width or height in
302   // the float manager; it can't deal with them.
303   if (region.ISize(aWM) < 0) {
304     // Preserve the right margin-edge for left floats and the left
305     // margin-edge for right floats
306     const nsStyleDisplay* display = aFloat->StyleDisplay();
307     StyleFloat floatStyle = display->mFloat;
308     if ((StyleFloat::Left == floatStyle) == aWM.IsBidiLTR()) {
309       region.IStart(aWM) = region.IEnd(aWM);
310     }
311     region.ISize(aWM) = 0;
312   }
313   if (region.BSize(aWM) < 0) {
314     region.BSize(aWM) = 0;
315   }
316   return region;
317 }
318 
NS_DECLARE_FRAME_PROPERTY_DELETABLE(FloatRegionProperty,nsMargin)319 NS_DECLARE_FRAME_PROPERTY_DELETABLE(FloatRegionProperty, nsMargin)
320 
321 LogicalRect nsFloatManager::GetRegionFor(WritingMode aWM, nsIFrame* aFloat,
322                                          const nsSize& aContainerSize) {
323   LogicalRect region = aFloat->GetLogicalRect(aWM, aContainerSize);
324   void* storedRegion = aFloat->GetProperty(FloatRegionProperty());
325   if (storedRegion) {
326     nsMargin margin = *static_cast<nsMargin*>(storedRegion);
327     region.Inflate(aWM, LogicalMargin(aWM, margin));
328   }
329   return region;
330 }
331 
StoreRegionFor(WritingMode aWM,nsIFrame * aFloat,const LogicalRect & aRegion,const nsSize & aContainerSize)332 void nsFloatManager::StoreRegionFor(WritingMode aWM, nsIFrame* aFloat,
333                                     const LogicalRect& aRegion,
334                                     const nsSize& aContainerSize) {
335   nsRect region = aRegion.GetPhysicalRect(aWM, aContainerSize);
336   nsRect rect = aFloat->GetRect();
337   if (region.IsEqualEdges(rect)) {
338     aFloat->RemoveProperty(FloatRegionProperty());
339   } else {
340     nsMargin* storedMargin = aFloat->GetProperty(FloatRegionProperty());
341     if (!storedMargin) {
342       storedMargin = new nsMargin();
343       aFloat->SetProperty(FloatRegionProperty(), storedMargin);
344     }
345     *storedMargin = region - rect;
346   }
347 }
348 
RemoveTrailingRegions(nsIFrame * aFrameList)349 nsresult nsFloatManager::RemoveTrailingRegions(nsIFrame* aFrameList) {
350   if (!aFrameList) {
351     return NS_OK;
352   }
353   // This could be a good bit simpler if we could guarantee that the
354   // floats given were at the end of our list, so we could just search
355   // for the head of aFrameList.  (But we can't;
356   // layout/reftests/bugs/421710-1.html crashes.)
357   nsTHashSet<nsIFrame*> frameSet(1);
358 
359   for (nsIFrame* f = aFrameList; f; f = f->GetNextSibling()) {
360     frameSet.Insert(f);
361   }
362 
363   uint32_t newLength = mFloats.Length();
364   while (newLength > 0) {
365     if (!frameSet.Contains(mFloats[newLength - 1].mFrame)) {
366       break;
367     }
368     --newLength;
369   }
370   mFloats.TruncateLength(newLength);
371 
372 #ifdef DEBUG
373   for (uint32_t i = 0; i < mFloats.Length(); ++i) {
374     NS_ASSERTION(
375         !frameSet.Contains(mFloats[i].mFrame),
376         "Frame region deletion was requested but we couldn't delete it");
377   }
378 #endif
379 
380   return NS_OK;
381 }
382 
PushState(SavedState * aState)383 void nsFloatManager::PushState(SavedState* aState) {
384   MOZ_ASSERT(aState, "Need a place to save state");
385 
386   // This is a cheap push implementation, which
387   // only saves the (x,y) and last frame in the mFrameInfoMap
388   // which is enough info to get us back to where we should be
389   // when pop is called.
390   //
391   // This push/pop mechanism is used to undo any
392   // floats that were added during the unconstrained reflow
393   // in nsBlockReflowContext::DoReflowBlock(). (See bug 96736)
394   //
395   // It should also be noted that the state for mFloatDamage is
396   // intentionally not saved or restored in PushState() and PopState(),
397   // since that could lead to bugs where damage is missed/dropped when
398   // we move from position A to B (during the intermediate incremental
399   // reflow mentioned above) and then from B to C during the subsequent
400   // reflow. In the typical case A and C will be the same, but not always.
401   // Allowing mFloatDamage to accumulate the damage incurred during both
402   // reflows ensures that nothing gets missed.
403   aState->mLineLeft = mLineLeft;
404   aState->mBlockStart = mBlockStart;
405   aState->mPushedLeftFloatPastBreak = mPushedLeftFloatPastBreak;
406   aState->mPushedRightFloatPastBreak = mPushedRightFloatPastBreak;
407   aState->mSplitLeftFloatAcrossBreak = mSplitLeftFloatAcrossBreak;
408   aState->mSplitRightFloatAcrossBreak = mSplitRightFloatAcrossBreak;
409   aState->mFloatInfoCount = mFloats.Length();
410 }
411 
PopState(SavedState * aState)412 void nsFloatManager::PopState(SavedState* aState) {
413   MOZ_ASSERT(aState, "No state to restore?");
414 
415   mLineLeft = aState->mLineLeft;
416   mBlockStart = aState->mBlockStart;
417   mPushedLeftFloatPastBreak = aState->mPushedLeftFloatPastBreak;
418   mPushedRightFloatPastBreak = aState->mPushedRightFloatPastBreak;
419   mSplitLeftFloatAcrossBreak = aState->mSplitLeftFloatAcrossBreak;
420   mSplitRightFloatAcrossBreak = aState->mSplitRightFloatAcrossBreak;
421 
422   NS_ASSERTION(aState->mFloatInfoCount <= mFloats.Length(),
423                "somebody misused PushState/PopState");
424   mFloats.TruncateLength(aState->mFloatInfoCount);
425 }
426 
GetLowestFloatTop() const427 nscoord nsFloatManager::GetLowestFloatTop() const {
428   if (mPushedLeftFloatPastBreak || mPushedRightFloatPastBreak) {
429     return nscoord_MAX;
430   }
431   if (!HasAnyFloats()) {
432     return nscoord_MIN;
433   }
434   return mFloats[mFloats.Length() - 1].BStart() - mBlockStart;
435 }
436 
437 #ifdef DEBUG_FRAME_DUMP
DebugListFloatManager(const nsFloatManager * aFloatManager)438 void DebugListFloatManager(const nsFloatManager* aFloatManager) {
439   aFloatManager->List(stdout);
440 }
441 
List(FILE * out) const442 nsresult nsFloatManager::List(FILE* out) const {
443   if (!HasAnyFloats()) return NS_OK;
444 
445   for (uint32_t i = 0; i < mFloats.Length(); ++i) {
446     const FloatInfo& fi = mFloats[i];
447     fprintf_stderr(out,
448                    "Float %u: frame=%p rect={%d,%d,%d,%d} BEnd={l:%d, r:%d}\n",
449                    i, static_cast<void*>(fi.mFrame), fi.LineLeft(), fi.BStart(),
450                    fi.ISize(), fi.BSize(), fi.mLeftBEnd, fi.mRightBEnd);
451   }
452   return NS_OK;
453 }
454 #endif
455 
ClearFloats(nscoord aBCoord,StyleClear aBreakType) const456 nscoord nsFloatManager::ClearFloats(nscoord aBCoord,
457                                     StyleClear aBreakType) const {
458   if (!HasAnyFloats()) {
459     return aBCoord;
460   }
461 
462   nscoord blockEnd = aBCoord + mBlockStart;
463 
464   const FloatInfo& tail = mFloats[mFloats.Length() - 1];
465   switch (aBreakType) {
466     case StyleClear::Both:
467       blockEnd = std::max(blockEnd, tail.mLeftBEnd);
468       blockEnd = std::max(blockEnd, tail.mRightBEnd);
469       break;
470     case StyleClear::Left:
471       blockEnd = std::max(blockEnd, tail.mLeftBEnd);
472       break;
473     case StyleClear::Right:
474       blockEnd = std::max(blockEnd, tail.mRightBEnd);
475       break;
476     default:
477       // Do nothing
478       break;
479   }
480 
481   blockEnd -= mBlockStart;
482 
483   return blockEnd;
484 }
485 
ClearContinues(StyleClear aBreakType) const486 bool nsFloatManager::ClearContinues(StyleClear aBreakType) const {
487   return ((mPushedLeftFloatPastBreak || mSplitLeftFloatAcrossBreak) &&
488           (aBreakType == StyleClear::Both || aBreakType == StyleClear::Left)) ||
489          ((mPushedRightFloatPastBreak || mSplitRightFloatAcrossBreak) &&
490           (aBreakType == StyleClear::Both || aBreakType == StyleClear::Right));
491 }
492 
493 /////////////////////////////////////////////////////////////////////////////
494 // ShapeInfo is an abstract class for implementing all the shapes in CSS
495 // Shapes Module. A subclass needs to override all the methods to adjust
496 // the flow area with respect to its shape.
497 //
498 class nsFloatManager::ShapeInfo {
499  public:
500   virtual ~ShapeInfo() = default;
501 
502   virtual nscoord LineLeft(const nscoord aBStart,
503                            const nscoord aBEnd) const = 0;
504   virtual nscoord LineRight(const nscoord aBStart,
505                             const nscoord aBEnd) const = 0;
506   virtual nscoord BStart() const = 0;
507   virtual nscoord BEnd() const = 0;
508   virtual bool IsEmpty() const = 0;
509 
510   // Does this shape possibly get inline narrower in the BStart() to BEnd()
511   // span when proceeding in the block direction? This is false for unrounded
512   // rectangles that span all the way to BEnd(), but could be true for other
513   // shapes. Note that we don't care if the BEnd() falls short of the margin
514   // rect -- the ShapeInfo can only affect float behavior in the span between
515   // BStart() and BEnd().
516   virtual bool MayNarrowInBlockDirection() const = 0;
517 
518   // Translate the current origin by the specified offsets.
519   virtual void Translate(nscoord aLineLeft, nscoord aBlockStart) = 0;
520 
521   static LogicalRect ComputeShapeBoxRect(StyleShapeBox, nsIFrame* const aFrame,
522                                          const LogicalRect& aMarginRect,
523                                          WritingMode aWM);
524 
525   // Convert the LogicalRect to the special logical coordinate space used
526   // in float manager.
ConvertToFloatLogical(const LogicalRect & aRect,WritingMode aWM,const nsSize & aContainerSize)527   static nsRect ConvertToFloatLogical(const LogicalRect& aRect, WritingMode aWM,
528                                       const nsSize& aContainerSize) {
529     return nsRect(aRect.LineLeft(aWM, aContainerSize), aRect.BStart(aWM),
530                   aRect.ISize(aWM), aRect.BSize(aWM));
531   }
532 
533   static UniquePtr<ShapeInfo> CreateShapeBox(nsIFrame* const aFrame,
534                                              nscoord aShapeMargin,
535                                              const LogicalRect& aShapeBoxRect,
536                                              WritingMode aWM,
537                                              const nsSize& aContainerSize);
538 
539   static UniquePtr<ShapeInfo> CreateBasicShape(
540       const StyleBasicShape& aBasicShape, nscoord aShapeMargin,
541       nsIFrame* const aFrame, const LogicalRect& aShapeBoxRect,
542       const LogicalRect& aMarginRect, WritingMode aWM,
543       const nsSize& aContainerSize);
544 
545   static UniquePtr<ShapeInfo> CreateInset(const StyleBasicShape& aBasicShape,
546                                           nscoord aShapeMargin,
547                                           nsIFrame* aFrame,
548                                           const LogicalRect& aShapeBoxRect,
549                                           WritingMode aWM,
550                                           const nsSize& aContainerSize);
551 
552   static UniquePtr<ShapeInfo> CreateCircleOrEllipse(
553       const StyleBasicShape& aBasicShape, nscoord aShapeMargin,
554       nsIFrame* const aFrame, const LogicalRect& aShapeBoxRect, WritingMode aWM,
555       const nsSize& aContainerSize);
556 
557   static UniquePtr<ShapeInfo> CreatePolygon(const StyleBasicShape& aBasicShape,
558                                             nscoord aShapeMargin,
559                                             nsIFrame* const aFrame,
560                                             const LogicalRect& aShapeBoxRect,
561                                             const LogicalRect& aMarginRect,
562                                             WritingMode aWM,
563                                             const nsSize& aContainerSize);
564 
565   static UniquePtr<ShapeInfo> CreateImageShape(const StyleImage& aShapeImage,
566                                                float aShapeImageThreshold,
567                                                nscoord aShapeMargin,
568                                                nsIFrame* const aFrame,
569                                                const LogicalRect& aMarginRect,
570                                                WritingMode aWM,
571                                                const nsSize& aContainerSize);
572 
573  protected:
574   // Compute the minimum line-axis difference between the bounding shape
575   // box and its rounded corner within the given band (block-axis region).
576   // This is used as a helper function to compute the LineRight() and
577   // LineLeft(). See the picture in the implementation for an example.
578   // RadiusL and RadiusB stand for radius on the line-axis and block-axis.
579   //
580   // Returns radius-x diff on the line-axis, or 0 if there's no rounded
581   // corner within the given band.
582   static nscoord ComputeEllipseLineInterceptDiff(
583       const nscoord aShapeBoxBStart, const nscoord aShapeBoxBEnd,
584       const nscoord aBStartCornerRadiusL, const nscoord aBStartCornerRadiusB,
585       const nscoord aBEndCornerRadiusL, const nscoord aBEndCornerRadiusB,
586       const nscoord aBandBStart, const nscoord aBandBEnd);
587 
588   static nscoord XInterceptAtY(const nscoord aY, const nscoord aRadiusX,
589                                const nscoord aRadiusY);
590 
591   // Convert the physical point to the special logical coordinate space
592   // used in float manager.
593   static nsPoint ConvertToFloatLogical(const nsPoint& aPoint, WritingMode aWM,
594                                        const nsSize& aContainerSize);
595 
596   // Convert the half corner radii (nscoord[8]) to the special logical
597   // coordinate space used in float manager.
598   static UniquePtr<nscoord[]> ConvertToFloatLogical(const nscoord aRadii[8],
599                                                     WritingMode aWM);
600 
601   // Some ShapeInfo subclasses may define their float areas in intervals.
602   // Each interval is a rectangle that is one device pixel deep in the block
603   // axis. The values are stored as block edges in the y coordinates,
604   // and inline edges as the x coordinates. Interval arrays should be sorted
605   // on increasing y values. This function uses a binary search to find the
606   // first interval that contains aTargetY. If no such interval exists, this
607   // function returns aIntervals.Length().
608   static size_t MinIntervalIndexContainingY(const nsTArray<nsRect>& aIntervals,
609                                             const nscoord aTargetY);
610 
611   // This interval function is designed to handle the arguments to ::LineLeft()
612   // and LineRight() and interpret them for the supplied aIntervals.
613   static nscoord LineEdge(const nsTArray<nsRect>& aIntervals,
614                           const nscoord aBStart, const nscoord aBEnd,
615                           bool aIsLineLeft);
616 
617   // These types, constants, and functions are useful for ShapeInfos that
618   // allocate a distance field. Efficient distance field calculations use
619   // integer values that are 5X the Euclidean distance. MAX_MARGIN_5X is the
620   // largest possible margin that we can calculate (in 5X integer dev pixels),
621   // given these constraints.
622   typedef uint16_t dfType;
623   static const dfType MAX_CHAMFER_VALUE;
624   static const dfType MAX_MARGIN;
625   static const dfType MAX_MARGIN_5X;
626 
627   // This function returns a typed, overflow-safe value of aShapeMargin in
628   // 5X integer dev pixels.
629   static dfType CalcUsedShapeMargin5X(nscoord aShapeMargin,
630                                       int32_t aAppUnitsPerDevPixel);
631 };
632 
633 const nsFloatManager::ShapeInfo::dfType
634     nsFloatManager::ShapeInfo::MAX_CHAMFER_VALUE = 11;
635 
636 const nsFloatManager::ShapeInfo::dfType nsFloatManager::ShapeInfo::MAX_MARGIN =
637     (std::numeric_limits<dfType>::max() - MAX_CHAMFER_VALUE) / 5;
638 
639 const nsFloatManager::ShapeInfo::dfType
640     nsFloatManager::ShapeInfo::MAX_MARGIN_5X = MAX_MARGIN * 5;
641 
642 /////////////////////////////////////////////////////////////////////////////
643 // EllipseShapeInfo
644 //
645 // Implements shape-outside: circle() and shape-outside: ellipse().
646 //
647 class nsFloatManager::EllipseShapeInfo final
648     : public nsFloatManager::ShapeInfo {
649  public:
650   // Construct the float area using math to calculate the shape boundary.
651   // This is the fast path and should be used when shape-margin is negligible,
652   // or when the two values of aRadii are roughly equal. Those two conditions
653   // are defined by ShapeMarginIsNegligible() and RadiiAreRoughlyEqual(). In
654   // those cases, we can conveniently represent the entire float area using
655   // an ellipse.
656   EllipseShapeInfo(const nsPoint& aCenter, const nsSize& aRadii,
657                    nscoord aShapeMargin);
658 
659   // Construct the float area using rasterization to calculate the shape
660   // boundary. This constructor accounts for the fact that applying
661   // 'shape-margin' to an ellipse produces a shape that is not mathematically
662   // representable as an ellipse.
663   EllipseShapeInfo(const nsPoint& aCenter, const nsSize& aRadii,
664                    nscoord aShapeMargin, int32_t aAppUnitsPerDevPixel);
665 
ShapeMarginIsNegligible(nscoord aShapeMargin)666   static bool ShapeMarginIsNegligible(nscoord aShapeMargin) {
667     // For now, only return true for a shape-margin of 0. In the future, if
668     // we want to enable use of the fast-path constructor more often, this
669     // limit could be increased;
670     static const nscoord SHAPE_MARGIN_NEGLIGIBLE_MAX(0);
671     return aShapeMargin <= SHAPE_MARGIN_NEGLIGIBLE_MAX;
672   }
673 
RadiiAreRoughlyEqual(const nsSize & aRadii)674   static bool RadiiAreRoughlyEqual(const nsSize& aRadii) {
675     // For now, only return true when we are exactly equal. In the future, if
676     // we want to enable use of the fast-path constructor more often, this
677     // could be generalized to allow radii that are in some close proportion
678     // to each other.
679     return aRadii.width == aRadii.height;
680   }
681   nscoord LineEdge(const nscoord aBStart, const nscoord aBEnd,
682                    bool aLeft) const;
683   nscoord LineLeft(const nscoord aBStart, const nscoord aBEnd) const override;
684   nscoord LineRight(const nscoord aBStart, const nscoord aBEnd) const override;
BStart() const685   nscoord BStart() const override {
686     return mCenter.y - mRadii.height - mShapeMargin;
687   }
BEnd() const688   nscoord BEnd() const override {
689     return mCenter.y + mRadii.height + mShapeMargin;
690   }
IsEmpty() const691   bool IsEmpty() const override {
692     // An EllipseShapeInfo is never empty, because an ellipse or circle with
693     // a zero radius acts like a point, and an ellipse with one zero radius
694     // acts like a line.
695     return false;
696   }
MayNarrowInBlockDirection() const697   bool MayNarrowInBlockDirection() const override { return true; }
698 
Translate(nscoord aLineLeft,nscoord aBlockStart)699   void Translate(nscoord aLineLeft, nscoord aBlockStart) override {
700     mCenter.MoveBy(aLineLeft, aBlockStart);
701 
702     for (nsRect& interval : mIntervals) {
703       interval.MoveBy(aLineLeft, aBlockStart);
704     }
705   }
706 
707  private:
708   // The position of the center of the ellipse. The coordinate space is the
709   // same as FloatInfo::mRect.
710   nsPoint mCenter;
711   // The radii of the ellipse in app units. The width and height represent
712   // the line-axis and block-axis radii of the ellipse.
713   nsSize mRadii;
714   // The shape-margin of the ellipse in app units. If this value is greater
715   // than zero, then we calculate the bounds of the ellipse + margin using
716   // numerical methods and store the values in mIntervals.
717   nscoord mShapeMargin;
718 
719   // An interval is slice of the float area defined by this EllipseShapeInfo.
720   // Each interval is a rectangle that is one pixel deep in the block
721   // axis. The values are stored as block edges in the y coordinates,
722   // and inline edges as the x coordinates.
723 
724   // The intervals are stored in ascending order on y.
725   nsTArray<nsRect> mIntervals;
726 };
727 
EllipseShapeInfo(const nsPoint & aCenter,const nsSize & aRadii,nscoord aShapeMargin)728 nsFloatManager::EllipseShapeInfo::EllipseShapeInfo(const nsPoint& aCenter,
729                                                    const nsSize& aRadii,
730                                                    nscoord aShapeMargin)
731     : mCenter(aCenter),
732       mRadii(aRadii),
733       mShapeMargin(
734           0)  // We intentionally ignore the value of aShapeMargin here.
735 {
736   MOZ_ASSERT(
737       RadiiAreRoughlyEqual(aRadii) || ShapeMarginIsNegligible(aShapeMargin),
738       "This constructor should only be called when margin is "
739       "negligible or radii are roughly equal.");
740 
741   // We add aShapeMargin into the radii, and we earlier stored a mShapeMargin
742   // of zero.
743   mRadii.width += aShapeMargin;
744   mRadii.height += aShapeMargin;
745 }
746 
EllipseShapeInfo(const nsPoint & aCenter,const nsSize & aRadii,nscoord aShapeMargin,int32_t aAppUnitsPerDevPixel)747 nsFloatManager::EllipseShapeInfo::EllipseShapeInfo(const nsPoint& aCenter,
748                                                    const nsSize& aRadii,
749                                                    nscoord aShapeMargin,
750                                                    int32_t aAppUnitsPerDevPixel)
751     : mCenter(aCenter), mRadii(aRadii), mShapeMargin(aShapeMargin) {
752   if (RadiiAreRoughlyEqual(aRadii) || ShapeMarginIsNegligible(aShapeMargin)) {
753     // Mimic the behavior of the simple constructor, by adding aShapeMargin
754     // into the radii, and then storing mShapeMargin of zero.
755     mRadii.width += mShapeMargin;
756     mRadii.height += mShapeMargin;
757     mShapeMargin = 0;
758     return;
759   }
760 
761   // We have to calculate a distance field from the ellipse edge, then build
762   // intervals based on pixels with less than aShapeMargin distance to an
763   // edge pixel.
764 
765   // mCenter and mRadii have already been translated into logical coordinates.
766   // x = inline, y = block. Due to symmetry, we only need to calculate the
767   // distance field for one quadrant of the ellipse. We choose the positive-x,
768   // positive-y quadrant (the lower right quadrant in horizontal-tb writing
769   // mode). We choose this quadrant because it allows us to traverse our
770   // distance field in memory order, which is more cache efficient.
771   // When we apply these intervals in LineLeft() and LineRight(), we
772   // account for block ranges that hit other quadrants, or hit multiple
773   // quadrants.
774 
775   // Given this setup, computing the distance field is a one-pass O(n)
776   // operation that runs from block top-to-bottom, inline left-to-right. We
777   // use a chamfer 5-7-11 5x5 matrix to compute minimum distance to an edge
778   // pixel. This integer math computation is reasonably close to the true
779   // Euclidean distance. The distances will be approximately 5x the true
780   // distance, quantized in integer units. The 5x is factored away in the
781   // comparison which builds the intervals.
782   dfType usedMargin5X =
783       CalcUsedShapeMargin5X(aShapeMargin, aAppUnitsPerDevPixel);
784 
785   // Calculate the bounds of one quadrant of the ellipse, in integer device
786   // pixels. These bounds are equal to the rectangle defined by the radii,
787   // plus the shape-margin value in both dimensions.
788   const LayoutDeviceIntSize bounds =
789       LayoutDevicePixel::FromAppUnitsRounded(mRadii, aAppUnitsPerDevPixel) +
790       LayoutDeviceIntSize(usedMargin5X / 5, usedMargin5X / 5);
791 
792   // Since our distance field is computed with a 5x5 neighborhood, but only
793   // looks in the negative block and negative inline directions, it is
794   // effectively a 3x3 neighborhood. We need to expand our distance field
795   // outwards by a further 2 pixels in both axes (on the minimum block edge
796   // and the minimum inline edge). We call this edge area the expanded region.
797 
798   static const uint32_t iExpand = 2;
799   static const uint32_t bExpand = 2;
800 
801   // Clamp the size of our distance field sizes to prevent multiplication
802   // overflow.
803   static const uint32_t DF_SIDE_MAX =
804       floor(sqrt((double)(std::numeric_limits<int32_t>::max())));
805   const uint32_t iSize = std::min(bounds.width + iExpand, DF_SIDE_MAX);
806   const uint32_t bSize = std::min(bounds.height + bExpand, DF_SIDE_MAX);
807   auto df = MakeUniqueFallible<dfType[]>(iSize * bSize);
808   if (!df) {
809     // Without a distance field, we can't reason about the float area.
810     return;
811   }
812 
813   // Single pass setting distance field, in positive block direction, three
814   // cases:
815   // 1) Expanded region pixel: set to MAX_MARGIN_5X.
816   // 2) Pixel within the ellipse: set to 0.
817   // 3) Other pixel: set to minimum neighborhood distance value, computed
818   //                 with 5-7-11 chamfer.
819 
820   for (uint32_t b = 0; b < bSize; ++b) {
821     bool bIsInExpandedRegion(b < bExpand);
822     nscoord bInAppUnits = (b - bExpand) * aAppUnitsPerDevPixel;
823     bool bIsMoreThanEllipseBEnd(bInAppUnits > mRadii.height);
824 
825     // Find the i intercept of the ellipse edge for this block row, and
826     // adjust it to compensate for the expansion of the inline dimension.
827     // If we're in the expanded region, or if we're using a b that's more
828     // than the bEnd of the ellipse, the intercept is nscoord_MIN.
829     // We have one other special case to consider: when the ellipse has no
830     // height. In that case we treat the bInAppUnits == 0 case as
831     // intercepting at the width of the ellipse. All other cases solve
832     // the intersection mathematically.
833     const int32_t iIntercept =
834         (bIsInExpandedRegion || bIsMoreThanEllipseBEnd)
835             ? nscoord_MIN
836             : iExpand + NSAppUnitsToIntPixels(
837                             (!!mRadii.height || bInAppUnits)
838                                 ? XInterceptAtY(bInAppUnits, mRadii.width,
839                                                 mRadii.height)
840                                 : mRadii.width,
841                             aAppUnitsPerDevPixel);
842 
843     // Set iMax in preparation for this block row.
844     int32_t iMax = iIntercept;
845 
846     for (uint32_t i = 0; i < iSize; ++i) {
847       const uint32_t index = i + b * iSize;
848       MOZ_ASSERT(index < (iSize * bSize),
849                  "Our distance field index should be in-bounds.");
850 
851       // Handle our three cases, in order.
852       if (i < iExpand || bIsInExpandedRegion) {
853         // Case 1: Expanded reqion pixel.
854         df[index] = MAX_MARGIN_5X;
855       } else if ((int32_t)i <= iIntercept) {
856         // Case 2: Pixel within the ellipse, or just outside the edge of it.
857         // Having a positive height indicates that there's an area we can
858         // be inside of.
859         df[index] = (!!mRadii.height) ? 0 : 5;
860       } else {
861         // Case 3: Other pixel.
862 
863         // Backward-looking neighborhood distance from target pixel X
864         // with chamfer 5-7-11 looks like:
865         //
866         // +--+--+--+
867         // |  |11|  |
868         // +--+--+--+
869         // |11| 7| 5|
870         // +--+--+--+
871         // |  | 5| X|
872         // +--+--+--+
873         //
874         // X should be set to the minimum of the values of all of the numbered
875         // neighbors summed with the value in that chamfer cell.
876         MOZ_ASSERT(index - iSize - 2 < (iSize * bSize) &&
877                        index - (iSize * 2) - 1 < (iSize * bSize),
878                    "Our distance field most extreme indices should be "
879                    "in-bounds.");
880 
881         // clang-format off
882         df[index] = std::min<dfType>(df[index - 1] + 5,
883                     std::min<dfType>(df[index - iSize] + 5,
884                     std::min<dfType>(df[index - iSize - 1] + 7,
885                     std::min<dfType>(df[index - iSize - 2] + 11,
886                     df[index - (iSize * 2) - 1] + 11))));
887         // clang-format on
888 
889         // Check the df value and see if it's less than or equal to the
890         // usedMargin5X value.
891         if (df[index] <= usedMargin5X) {
892           MOZ_ASSERT(iMax < (int32_t)i);
893           iMax = i;
894         } else {
895           // Since we're computing the bottom-right quadrant, there's no way
896           // for a later i value in this row to be within the usedMargin5X
897           // value. Likewise, every row beyond us will encounter this
898           // condition with an i value less than or equal to our i value now.
899           // Since our chamfer only looks upward and leftward, we can stop
900           // calculating for the rest of the row, because the distance field
901           // values there will never be looked at in a later row's chamfer
902           // calculation.
903           break;
904         }
905       }
906     }
907 
908     // It's very likely, though not guaranteed that we will find an pixel
909     // within the shape-margin distance for each block row. This may not
910     // always be true due to rounding errors.
911     if (iMax > nscoord_MIN) {
912       // Origin for this interval is at the center of the ellipse, adjusted
913       // in the positive block direction by bInAppUnits.
914       nsPoint origin(aCenter.x, aCenter.y + bInAppUnits);
915       // Size is an inline iMax plus 1 (to account for the whole pixel) dev
916       // pixels, by 1 block dev pixel. We convert this to app units.
917       nsSize size((iMax - iExpand + 1) * aAppUnitsPerDevPixel,
918                   aAppUnitsPerDevPixel);
919       mIntervals.AppendElement(nsRect(origin, size));
920     }
921   }
922 }
923 
LineEdge(const nscoord aBStart,const nscoord aBEnd,bool aIsLineLeft) const924 nscoord nsFloatManager::EllipseShapeInfo::LineEdge(const nscoord aBStart,
925                                                    const nscoord aBEnd,
926                                                    bool aIsLineLeft) const {
927   // If no mShapeMargin, just compute the edge using math.
928   if (mShapeMargin == 0) {
929     nscoord lineDiff = ComputeEllipseLineInterceptDiff(
930         BStart(), BEnd(), mRadii.width, mRadii.height, mRadii.width,
931         mRadii.height, aBStart, aBEnd);
932     return mCenter.x + (aIsLineLeft ? (-mRadii.width + lineDiff)
933                                     : (mRadii.width - lineDiff));
934   }
935 
936   // We are checking against our intervals. Make sure we have some.
937   if (mIntervals.IsEmpty()) {
938     NS_WARNING("With mShapeMargin > 0, we can't proceed without intervals.");
939     return aIsLineLeft ? nscoord_MAX : nscoord_MIN;
940   }
941 
942   // Map aBStart and aBEnd into our intervals. Our intervals are calculated
943   // for the lower-right quadrant (in terms of horizontal-tb writing mode).
944   // If aBStart and aBEnd span the center of the ellipse, then we know we
945   // are at the maximum displacement from the center.
946   bool bStartIsAboveCenter = (aBStart < mCenter.y);
947   bool bEndIsBelowOrAtCenter = (aBEnd >= mCenter.y);
948   if (bStartIsAboveCenter && bEndIsBelowOrAtCenter) {
949     return mCenter.x + (aIsLineLeft ? (-mRadii.width - mShapeMargin)
950                                     : (mRadii.width + mShapeMargin));
951   }
952 
953   // aBStart and aBEnd don't span the center. Since the intervals are
954   // strictly wider approaching the center (the start of the mIntervals
955   // array), we only need to find the interval at the block value closest to
956   // the center. We find the min of aBStart, aBEnd, and their reflections --
957   // whichever two of them are within the lower-right quadrant. When we
958   // reflect from the upper-right quadrant to the lower-right, we have to
959   // subtract 1 from the reflection, to account that block values are always
960   // addressed from the leading block edge.
961 
962   // The key example is when we check with aBStart == aBEnd at the top of the
963   // intervals. That block line would be considered contained in the
964   // intervals (though it has no height), but its reflection would not be
965   // within the intervals unless we subtract 1.
966   nscoord bSmallestWithinIntervals = std::min(
967       bStartIsAboveCenter ? aBStart + (mCenter.y - aBStart) * 2 - 1 : aBStart,
968       bEndIsBelowOrAtCenter ? aBEnd : aBEnd + (mCenter.y - aBEnd) * 2 - 1);
969 
970   MOZ_ASSERT(bSmallestWithinIntervals >= mCenter.y &&
971                  bSmallestWithinIntervals < BEnd(),
972              "We should have a block value within the float area.");
973 
974   size_t index =
975       MinIntervalIndexContainingY(mIntervals, bSmallestWithinIntervals);
976   if (index >= mIntervals.Length()) {
977     // This indicates that our intervals don't cover the block value
978     // bSmallestWithinIntervals. This can happen when rounding error in the
979     // distance field calculation resulted in the last block pixel row not
980     // contributing to the float area. As long as we're within one block pixel
981     // past the last interval, this is an expected outcome.
982 #ifdef DEBUG
983     nscoord onePixelPastLastInterval =
984         mIntervals[mIntervals.Length() - 1].YMost() +
985         mIntervals[mIntervals.Length() - 1].Height();
986     NS_WARNING_ASSERTION(bSmallestWithinIntervals < onePixelPastLastInterval,
987                          "We should have found a matching interval for this "
988                          "block value.");
989 #endif
990     return aIsLineLeft ? nscoord_MAX : nscoord_MIN;
991   }
992 
993   // The interval is storing the line right value. If aIsLineLeft is true,
994   // return the line right value reflected about the center. Since this is
995   // an inline measurement, it's just checking the distance to an edge, and
996   // not a collision with a specific pixel. For that reason, we don't need
997   // to subtract 1 from the reflection, as we did with the block reflection.
998   nscoord iLineRight = mIntervals[index].XMost();
999   return aIsLineLeft ? iLineRight - (iLineRight - mCenter.x) * 2 : iLineRight;
1000 }
1001 
LineLeft(const nscoord aBStart,const nscoord aBEnd) const1002 nscoord nsFloatManager::EllipseShapeInfo::LineLeft(const nscoord aBStart,
1003                                                    const nscoord aBEnd) const {
1004   return LineEdge(aBStart, aBEnd, true);
1005 }
1006 
LineRight(const nscoord aBStart,const nscoord aBEnd) const1007 nscoord nsFloatManager::EllipseShapeInfo::LineRight(const nscoord aBStart,
1008                                                     const nscoord aBEnd) const {
1009   return LineEdge(aBStart, aBEnd, false);
1010 }
1011 
1012 /////////////////////////////////////////////////////////////////////////////
1013 // RoundedBoxShapeInfo
1014 //
1015 // Implements shape-outside: <shape-box> and shape-outside: inset().
1016 //
1017 class nsFloatManager::RoundedBoxShapeInfo final
1018     : public nsFloatManager::ShapeInfo {
1019  public:
RoundedBoxShapeInfo(const nsRect & aRect,UniquePtr<nscoord[]> aRadii)1020   RoundedBoxShapeInfo(const nsRect& aRect, UniquePtr<nscoord[]> aRadii)
1021       : mRect(aRect), mRadii(std::move(aRadii)), mShapeMargin(0) {}
1022 
1023   RoundedBoxShapeInfo(const nsRect& aRect, UniquePtr<nscoord[]> aRadii,
1024                       nscoord aShapeMargin, int32_t aAppUnitsPerDevPixel);
1025 
1026   nscoord LineLeft(const nscoord aBStart, const nscoord aBEnd) const override;
1027   nscoord LineRight(const nscoord aBStart, const nscoord aBEnd) const override;
BStart() const1028   nscoord BStart() const override { return mRect.y; }
BEnd() const1029   nscoord BEnd() const override { return mRect.YMost(); }
IsEmpty() const1030   bool IsEmpty() const override {
1031     // A RoundedBoxShapeInfo is never empty, because if it is collapsed to
1032     // zero area, it acts like a point. If it is collapsed further, to become
1033     // inside-out, it acts like a rect in the same shape as the inside-out
1034     // rect.
1035     return false;
1036   }
MayNarrowInBlockDirection() const1037   bool MayNarrowInBlockDirection() const override {
1038     // Only possible to narrow if there are non-null mRadii.
1039     return !!mRadii;
1040   }
1041 
Translate(nscoord aLineLeft,nscoord aBlockStart)1042   void Translate(nscoord aLineLeft, nscoord aBlockStart) override {
1043     mRect.MoveBy(aLineLeft, aBlockStart);
1044 
1045     if (mShapeMargin > 0) {
1046       MOZ_ASSERT(mLogicalTopLeftCorner && mLogicalTopRightCorner &&
1047                      mLogicalBottomLeftCorner && mLogicalBottomRightCorner,
1048                  "If we have positive shape-margin, we should have corners.");
1049       mLogicalTopLeftCorner->Translate(aLineLeft, aBlockStart);
1050       mLogicalTopRightCorner->Translate(aLineLeft, aBlockStart);
1051       mLogicalBottomLeftCorner->Translate(aLineLeft, aBlockStart);
1052       mLogicalBottomRightCorner->Translate(aLineLeft, aBlockStart);
1053     }
1054   }
1055 
EachCornerHasBalancedRadii(const nscoord * aRadii)1056   static bool EachCornerHasBalancedRadii(const nscoord* aRadii) {
1057     return (aRadii[eCornerTopLeftX] == aRadii[eCornerTopLeftY] &&
1058             aRadii[eCornerTopRightX] == aRadii[eCornerTopRightY] &&
1059             aRadii[eCornerBottomLeftX] == aRadii[eCornerBottomLeftY] &&
1060             aRadii[eCornerBottomRightX] == aRadii[eCornerBottomRightY]);
1061   }
1062 
1063  private:
1064   // The rect of the rounded box shape in the float manager's coordinate
1065   // space.
1066   nsRect mRect;
1067   // The half corner radii of the reference box. It's an nscoord[8] array
1068   // in the float manager's coordinate space. If there are no radii, it's
1069   // nullptr.
1070   const UniquePtr<nscoord[]> mRadii;
1071 
1072   // A shape-margin value extends the boundaries of the float area. When our
1073   // first constructor is used, it is for the creation of rounded boxes that
1074   // can ignore shape-margin -- either because it was specified as zero or
1075   // because the box shape and radii can be inflated to account for it. When
1076   // our second constructor is used, we store the shape-margin value here.
1077   const nscoord mShapeMargin;
1078 
1079   // If our second constructor is called (which implies mShapeMargin > 0),
1080   // we will construct EllipseShapeInfo objects for each corner. We use the
1081   // float logical naming here, where LogicalTopLeftCorner means the BStart
1082   // LineLeft corner, and similarly for the other corners.
1083   UniquePtr<EllipseShapeInfo> mLogicalTopLeftCorner;
1084   UniquePtr<EllipseShapeInfo> mLogicalTopRightCorner;
1085   UniquePtr<EllipseShapeInfo> mLogicalBottomLeftCorner;
1086   UniquePtr<EllipseShapeInfo> mLogicalBottomRightCorner;
1087 };
1088 
RoundedBoxShapeInfo(const nsRect & aRect,UniquePtr<nscoord[]> aRadii,nscoord aShapeMargin,int32_t aAppUnitsPerDevPixel)1089 nsFloatManager::RoundedBoxShapeInfo::RoundedBoxShapeInfo(
1090     const nsRect& aRect, UniquePtr<nscoord[]> aRadii, nscoord aShapeMargin,
1091     int32_t aAppUnitsPerDevPixel)
1092     : mRect(aRect), mRadii(std::move(aRadii)), mShapeMargin(aShapeMargin) {
1093   MOZ_ASSERT(mShapeMargin > 0 && !EachCornerHasBalancedRadii(mRadii.get()),
1094              "Slow constructor should only be used for for shape-margin > 0 "
1095              "and radii with elliptical corners.");
1096 
1097   // Before we inflate mRect by mShapeMargin, construct each of our corners.
1098   // If we do it in this order, it's a bit simpler to calculate the center
1099   // of each of the corners.
1100   mLogicalTopLeftCorner = MakeUnique<EllipseShapeInfo>(
1101       nsPoint(mRect.X() + mRadii[eCornerTopLeftX],
1102               mRect.Y() + mRadii[eCornerTopLeftY]),
1103       nsSize(mRadii[eCornerTopLeftX], mRadii[eCornerTopLeftY]), mShapeMargin,
1104       aAppUnitsPerDevPixel);
1105 
1106   mLogicalTopRightCorner = MakeUnique<EllipseShapeInfo>(
1107       nsPoint(mRect.XMost() - mRadii[eCornerTopRightX],
1108               mRect.Y() + mRadii[eCornerTopRightY]),
1109       nsSize(mRadii[eCornerTopRightX], mRadii[eCornerTopRightY]), mShapeMargin,
1110       aAppUnitsPerDevPixel);
1111 
1112   mLogicalBottomLeftCorner = MakeUnique<EllipseShapeInfo>(
1113       nsPoint(mRect.X() + mRadii[eCornerBottomLeftX],
1114               mRect.YMost() - mRadii[eCornerBottomLeftY]),
1115       nsSize(mRadii[eCornerBottomLeftX], mRadii[eCornerBottomLeftY]),
1116       mShapeMargin, aAppUnitsPerDevPixel);
1117 
1118   mLogicalBottomRightCorner = MakeUnique<EllipseShapeInfo>(
1119       nsPoint(mRect.XMost() - mRadii[eCornerBottomRightX],
1120               mRect.YMost() - mRadii[eCornerBottomRightY]),
1121       nsSize(mRadii[eCornerBottomRightX], mRadii[eCornerBottomRightY]),
1122       mShapeMargin, aAppUnitsPerDevPixel);
1123 
1124   // Now we inflate our mRect by mShapeMargin.
1125   mRect.Inflate(mShapeMargin);
1126 }
1127 
LineLeft(const nscoord aBStart,const nscoord aBEnd) const1128 nscoord nsFloatManager::RoundedBoxShapeInfo::LineLeft(
1129     const nscoord aBStart, const nscoord aBEnd) const {
1130   if (mShapeMargin == 0) {
1131     if (!mRadii) {
1132       return mRect.x;
1133     }
1134 
1135     nscoord lineLeftDiff = ComputeEllipseLineInterceptDiff(
1136         mRect.y, mRect.YMost(), mRadii[eCornerTopLeftX],
1137         mRadii[eCornerTopLeftY], mRadii[eCornerBottomLeftX],
1138         mRadii[eCornerBottomLeftY], aBStart, aBEnd);
1139     return mRect.x + lineLeftDiff;
1140   }
1141 
1142   MOZ_ASSERT(mLogicalTopLeftCorner && mLogicalBottomLeftCorner,
1143              "If we have positive shape-margin, we should have corners.");
1144 
1145   // Determine if aBEnd is within our top corner.
1146   if (aBEnd < mLogicalTopLeftCorner->BEnd()) {
1147     return mLogicalTopLeftCorner->LineLeft(aBStart, aBEnd);
1148   }
1149 
1150   // Determine if aBStart is within our bottom corner.
1151   if (aBStart >= mLogicalBottomLeftCorner->BStart()) {
1152     return mLogicalBottomLeftCorner->LineLeft(aBStart, aBEnd);
1153   }
1154 
1155   // Either aBStart or aBEnd or both are within the flat part of our left
1156   // edge. Because we've already inflated our mRect to encompass our
1157   // mShapeMargin, we can just return the edge.
1158   return mRect.X();
1159 }
1160 
LineRight(const nscoord aBStart,const nscoord aBEnd) const1161 nscoord nsFloatManager::RoundedBoxShapeInfo::LineRight(
1162     const nscoord aBStart, const nscoord aBEnd) const {
1163   if (mShapeMargin == 0) {
1164     if (!mRadii) {
1165       return mRect.XMost();
1166     }
1167 
1168     nscoord lineRightDiff = ComputeEllipseLineInterceptDiff(
1169         mRect.y, mRect.YMost(), mRadii[eCornerTopRightX],
1170         mRadii[eCornerTopRightY], mRadii[eCornerBottomRightX],
1171         mRadii[eCornerBottomRightY], aBStart, aBEnd);
1172     return mRect.XMost() - lineRightDiff;
1173   }
1174 
1175   MOZ_ASSERT(mLogicalTopRightCorner && mLogicalBottomRightCorner,
1176              "If we have positive shape-margin, we should have corners.");
1177 
1178   // Determine if aBEnd is within our top corner.
1179   if (aBEnd < mLogicalTopRightCorner->BEnd()) {
1180     return mLogicalTopRightCorner->LineRight(aBStart, aBEnd);
1181   }
1182 
1183   // Determine if aBStart is within our bottom corner.
1184   if (aBStart >= mLogicalBottomRightCorner->BStart()) {
1185     return mLogicalBottomRightCorner->LineRight(aBStart, aBEnd);
1186   }
1187 
1188   // Either aBStart or aBEnd or both are within the flat part of our right
1189   // edge. Because we've already inflated our mRect to encompass our
1190   // mShapeMargin, we can just return the edge.
1191   return mRect.XMost();
1192 }
1193 
1194 /////////////////////////////////////////////////////////////////////////////
1195 // PolygonShapeInfo
1196 //
1197 // Implements shape-outside: polygon().
1198 //
1199 class nsFloatManager::PolygonShapeInfo final
1200     : public nsFloatManager::ShapeInfo {
1201  public:
1202   explicit PolygonShapeInfo(nsTArray<nsPoint>&& aVertices);
1203   PolygonShapeInfo(nsTArray<nsPoint>&& aVertices, nscoord aShapeMargin,
1204                    int32_t aAppUnitsPerDevPixel, const nsRect& aMarginRect);
1205 
1206   nscoord LineLeft(const nscoord aBStart, const nscoord aBEnd) const override;
1207   nscoord LineRight(const nscoord aBStart, const nscoord aBEnd) const override;
BStart() const1208   nscoord BStart() const override { return mBStart; }
BEnd() const1209   nscoord BEnd() const override { return mBEnd; }
IsEmpty() const1210   bool IsEmpty() const override {
1211     // A PolygonShapeInfo is never empty, because the parser prevents us from
1212     // creating a shape with no vertices. If we only have 1 vertex, the
1213     // shape acts like a point. With 2 non-coincident vertices, the shape
1214     // acts like a line.
1215     return false;
1216   }
MayNarrowInBlockDirection() const1217   bool MayNarrowInBlockDirection() const override { return true; }
1218 
1219   void Translate(nscoord aLineLeft, nscoord aBlockStart) override;
1220 
1221  private:
1222   // Helper method for determining the mBStart and mBEnd based on the
1223   // vertices' y extent.
1224   void ComputeExtent();
1225 
1226   // Helper method for implementing LineLeft() and LineRight().
1227   nscoord ComputeLineIntercept(
1228       const nscoord aBStart, const nscoord aBEnd,
1229       nscoord (*aCompareOp)(std::initializer_list<nscoord>),
1230       const nscoord aLineInterceptInitialValue) const;
1231 
1232   // Given a horizontal line y, and two points p1 and p2 forming a line
1233   // segment L. Solve x for the intersection of y and L. This method
1234   // assumes y and L do intersect, and L is *not* horizontal.
1235   static nscoord XInterceptAtY(const nscoord aY, const nsPoint& aP1,
1236                                const nsPoint& aP2);
1237 
1238   // The vertices of the polygon in the float manager's coordinate space.
1239   nsTArray<nsPoint> mVertices;
1240 
1241   // An interval is slice of the float area defined by this PolygonShapeInfo.
1242   // These are only generated and used in float area calculations for
1243   // shape-margin > 0. Each interval is a rectangle that is one device pixel
1244   // deep in the block axis. The values are stored as block edges in the y
1245   // coordinates, and inline edges as the x coordinates.
1246 
1247   // The intervals are stored in ascending order on y.
1248   nsTArray<nsRect> mIntervals;
1249 
1250   // Computed block start and block end value of the polygon shape. These
1251   // initial values are set to correct values in ComputeExtent(), which is
1252   // called from all constructors. Afterwards, mBStart is guaranteed to be
1253   // less than or equal to mBEnd.
1254   nscoord mBStart = nscoord_MAX;
1255   nscoord mBEnd = nscoord_MIN;
1256 };
1257 
PolygonShapeInfo(nsTArray<nsPoint> && aVertices)1258 nsFloatManager::PolygonShapeInfo::PolygonShapeInfo(
1259     nsTArray<nsPoint>&& aVertices)
1260     : mVertices(std::move(aVertices)) {
1261   ComputeExtent();
1262 }
1263 
PolygonShapeInfo(nsTArray<nsPoint> && aVertices,nscoord aShapeMargin,int32_t aAppUnitsPerDevPixel,const nsRect & aMarginRect)1264 nsFloatManager::PolygonShapeInfo::PolygonShapeInfo(
1265     nsTArray<nsPoint>&& aVertices, nscoord aShapeMargin,
1266     int32_t aAppUnitsPerDevPixel, const nsRect& aMarginRect)
1267     : mVertices(std::move(aVertices)) {
1268   MOZ_ASSERT(aShapeMargin > 0,
1269              "This constructor should only be used for a "
1270              "polygon with a positive shape-margin.");
1271 
1272   ComputeExtent();
1273 
1274   // With a positive aShapeMargin, we have to calculate a distance
1275   // field from the opaque pixels, then build intervals based on
1276   // them being within aShapeMargin distance to an opaque pixel.
1277 
1278   // Roughly: for each pixel in the margin box, we need to determine the
1279   // distance to the nearest opaque image-pixel.  If that distance is less
1280   // than aShapeMargin, we consider this margin-box pixel as being part of
1281   // the float area.
1282 
1283   // Computing the distance field is a two-pass O(n) operation.
1284   // We use a chamfer 5-7-11 5x5 matrix to compute minimum distance
1285   // to an opaque pixel. This integer math computation is reasonably
1286   // close to the true Euclidean distance. The distances will be
1287   // approximately 5x the true distance, quantized in integer units.
1288   // The 5x is factored away in the comparison used in the final
1289   // pass which builds the intervals.
1290   dfType usedMargin5X =
1291       CalcUsedShapeMargin5X(aShapeMargin, aAppUnitsPerDevPixel);
1292 
1293   // Allocate our distance field.  The distance field has to cover
1294   // the entire aMarginRect, since aShapeMargin could bleed into it.
1295   // Conveniently, our vertices have been converted into this same space,
1296   // so if we cover the aMarginRect, we cover all the vertices.
1297   const LayoutDeviceIntSize marginRectDevPixels =
1298       LayoutDevicePixel::FromAppUnitsRounded(aMarginRect.Size(),
1299                                              aAppUnitsPerDevPixel);
1300 
1301   // Since our distance field is computed with a 5x5 neighborhood,
1302   // we need to expand our distance field by a further 4 pixels in
1303   // both axes, 2 on the leading edge and 2 on the trailing edge.
1304   // We call this edge area the "expanded region".
1305   static const uint32_t kiExpansionPerSide = 2;
1306   static const uint32_t kbExpansionPerSide = 2;
1307 
1308   // Clamp the size of our distance field sizes to prevent multiplication
1309   // overflow.
1310   static const uint32_t DF_SIDE_MAX =
1311       floor(sqrt((double)(std::numeric_limits<int32_t>::max())));
1312 
1313   // Clamp the margin plus 2X the expansion values between expansion + 1 and
1314   // DF_SIDE_MAX. This ensures that the distance field allocation doesn't
1315   // overflow during multiplication, and the reverse iteration doesn't
1316   // underflow.
1317   const uint32_t iSize =
1318       std::max(std::min(marginRectDevPixels.width + (kiExpansionPerSide * 2),
1319                         DF_SIDE_MAX),
1320                kiExpansionPerSide + 1);
1321   const uint32_t bSize =
1322       std::max(std::min(marginRectDevPixels.height + (kbExpansionPerSide * 2),
1323                         DF_SIDE_MAX),
1324                kbExpansionPerSide + 1);
1325 
1326   // Since the margin-box size is CSS controlled, and large values will
1327   // generate large iSize and bSize values, we do a fallible allocation for
1328   // the distance field. If allocation fails, we early exit and layout will
1329   // be wrong, but we'll avoid aborting from OOM.
1330   auto df = MakeUniqueFallible<dfType[]>(iSize * bSize);
1331   if (!df) {
1332     // Without a distance field, we can't reason about the float area.
1333     return;
1334   }
1335 
1336   // First pass setting distance field, starting at top-left, three cases:
1337   // 1) Expanded region pixel: set to MAX_MARGIN_5X.
1338   // 2) Pixel within the polygon: set to 0.
1339   // 3) Other pixel: set to minimum backward-looking neighborhood
1340   //                 distance value, computed with 5-7-11 chamfer.
1341 
1342   for (uint32_t b = 0; b < bSize; ++b) {
1343     // Find the left and right i intercepts of the polygon edge for this
1344     // block row, and adjust them to compensate for the expansion of the
1345     // inline dimension. If we're in the expanded region, or if we're using
1346     // a b that's less than the bStart of the polygon, the intercepts are
1347     // the nscoord min and max limits.
1348     nscoord bInAppUnits = (b - kbExpansionPerSide) * aAppUnitsPerDevPixel;
1349     bool bIsInExpandedRegion(b < kbExpansionPerSide ||
1350                              b >= bSize - kbExpansionPerSide);
1351 
1352     // We now figure out the i values that correspond to the left edge and
1353     // the right edge of the polygon at one-dev-pixel-thick strip of b. We
1354     // have a ComputeLineIntercept function that takes and returns app unit
1355     // coordinates in the space of aMarginRect. So to pass in b values, we
1356     // first have to add the aMarginRect.y value. And for the values that we
1357     // get out, we have to subtract away the aMarginRect.x value before
1358     // converting the app units to dev pixels.
1359     nscoord bInAppUnitsMarginRect = bInAppUnits + aMarginRect.y;
1360     bool bIsLessThanPolygonBStart(bInAppUnitsMarginRect < mBStart);
1361     bool bIsMoreThanPolygonBEnd(bInAppUnitsMarginRect > mBEnd);
1362 
1363     const int32_t iLeftEdge =
1364         (bIsInExpandedRegion || bIsLessThanPolygonBStart ||
1365          bIsMoreThanPolygonBEnd)
1366             ? nscoord_MAX
1367             : kiExpansionPerSide +
1368                   NSAppUnitsToIntPixels(
1369                       ComputeLineIntercept(
1370                           bInAppUnitsMarginRect,
1371                           bInAppUnitsMarginRect + aAppUnitsPerDevPixel,
1372                           std::min<nscoord>, nscoord_MAX) -
1373                           aMarginRect.x,
1374                       aAppUnitsPerDevPixel);
1375 
1376     const int32_t iRightEdge =
1377         (bIsInExpandedRegion || bIsLessThanPolygonBStart ||
1378          bIsMoreThanPolygonBEnd)
1379             ? nscoord_MIN
1380             : kiExpansionPerSide +
1381                   NSAppUnitsToIntPixels(
1382                       ComputeLineIntercept(
1383                           bInAppUnitsMarginRect,
1384                           bInAppUnitsMarginRect + aAppUnitsPerDevPixel,
1385                           std::max<nscoord>, nscoord_MIN) -
1386                           aMarginRect.x,
1387                       aAppUnitsPerDevPixel);
1388 
1389     for (uint32_t i = 0; i < iSize; ++i) {
1390       const uint32_t index = i + b * iSize;
1391       MOZ_ASSERT(index < (iSize * bSize),
1392                  "Our distance field index should be in-bounds.");
1393 
1394       // Handle our three cases, in order.
1395       if (i < kiExpansionPerSide || i >= iSize - kiExpansionPerSide ||
1396           bIsInExpandedRegion) {
1397         // Case 1: Expanded pixel.
1398         df[index] = MAX_MARGIN_5X;
1399       } else if ((int32_t)i >= iLeftEdge && (int32_t)i <= iRightEdge) {
1400         // Case 2: Polygon pixel, either inside or just adjacent to the right
1401         // edge. We need this special distinction to detect a space between
1402         // edges that is less than one dev pixel.
1403         df[index] = (int32_t)i < iRightEdge ? 0 : 5;
1404       } else {
1405         // Case 3: Other pixel.
1406 
1407         // Backward-looking neighborhood distance from target pixel X
1408         // with chamfer 5-7-11 looks like:
1409         //
1410         // +--+--+--+--+--+
1411         // |  |11|  |11|  |
1412         // +--+--+--+--+--+
1413         // |11| 7| 5| 7|11|
1414         // +--+--+--+--+--+
1415         // |  | 5| X|  |  |
1416         // +--+--+--+--+--+
1417         //
1418         // X should be set to the minimum of MAX_MARGIN_5X and the
1419         // values of all of the numbered neighbors summed with the
1420         // value in that chamfer cell.
1421         MOZ_ASSERT(index - (iSize * 2) - 1 < (iSize * bSize) &&
1422                        index - iSize - 2 < (iSize * bSize),
1423                    "Our distance field most extreme indices should be "
1424                    "in-bounds.");
1425 
1426         // clang-format off
1427         df[index] = std::min<dfType>(MAX_MARGIN_5X,
1428                     std::min<dfType>(df[index - (iSize * 2) - 1] + 11,
1429                     std::min<dfType>(df[index - (iSize * 2) + 1] + 11,
1430                     std::min<dfType>(df[index - iSize - 2] + 11,
1431                     std::min<dfType>(df[index - iSize - 1] + 7,
1432                     std::min<dfType>(df[index - iSize] + 5,
1433                     std::min<dfType>(df[index - iSize + 1] + 7,
1434                     std::min<dfType>(df[index - iSize + 2] + 11,
1435                                      df[index - 1] + 5))))))));
1436         // clang-format on
1437       }
1438     }
1439   }
1440 
1441   // Okay, time for the second pass. This pass is in reverse order from
1442   // the first pass. All of our opaque pixels have been set to 0, and all
1443   // of our expanded region pixels have been set to MAX_MARGIN_5X. Other
1444   // pixels have been set to some value between those two (inclusive) but
1445   // this hasn't yet taken into account the neighbors that were processed
1446   // after them in the first pass. This time we reverse iterate so we can
1447   // apply the forward-looking chamfer.
1448 
1449   // This time, we constrain our outer and inner loop to ignore the
1450   // expanded region pixels. For each pixel we iterate, we set the df value
1451   // to the minimum forward-looking neighborhood distance value, computed
1452   // with a 5-7-11 chamfer. We also check each df value against the
1453   // usedMargin5X threshold, and use that to set the iMin and iMax values
1454   // for the interval we'll create for that block axis value (b).
1455 
1456   // At the end of each row, if any of the other pixels had a value less
1457   // than usedMargin5X, we create an interval.
1458   for (uint32_t b = bSize - kbExpansionPerSide - 1; b >= kbExpansionPerSide;
1459        --b) {
1460     // iMin tracks the first df pixel and iMax the last df pixel whose
1461     // df[] value is less than usedMargin5X. Set iMin and iMax in
1462     // preparation for this row or column.
1463     int32_t iMin = iSize;
1464     int32_t iMax = -1;
1465 
1466     for (uint32_t i = iSize - kiExpansionPerSide - 1; i >= kiExpansionPerSide;
1467          --i) {
1468       const uint32_t index = i + b * iSize;
1469       MOZ_ASSERT(index < (iSize * bSize),
1470                  "Our distance field index should be in-bounds.");
1471 
1472       // Only apply the chamfer calculation if the df value is not
1473       // already 0, since the chamfer can only reduce the value.
1474       if (df[index]) {
1475         // Forward-looking neighborhood distance from target pixel X
1476         // with chamfer 5-7-11 looks like:
1477         //
1478         // +--+--+--+--+--+
1479         // |  |  | X| 5|  |
1480         // +--+--+--+--+--+
1481         // |11| 7| 5| 7|11|
1482         // +--+--+--+--+--+
1483         // |  |11|  |11|  |
1484         // +--+--+--+--+--+
1485         //
1486         // X should be set to the minimum of its current value and
1487         // the values of all of the numbered neighbors summed with
1488         // the value in that chamfer cell.
1489         MOZ_ASSERT(index + (iSize * 2) + 1 < (iSize * bSize) &&
1490                        index + iSize + 2 < (iSize * bSize),
1491                    "Our distance field most extreme indices should be "
1492                    "in-bounds.");
1493 
1494         // clang-format off
1495         df[index] = std::min<dfType>(df[index],
1496                     std::min<dfType>(df[index + (iSize * 2) + 1] + 11,
1497                     std::min<dfType>(df[index + (iSize * 2) - 1] + 11,
1498                     std::min<dfType>(df[index + iSize + 2] + 11,
1499                     std::min<dfType>(df[index + iSize + 1] + 7,
1500                     std::min<dfType>(df[index + iSize] + 5,
1501                     std::min<dfType>(df[index + iSize - 1] + 7,
1502                     std::min<dfType>(df[index + iSize - 2] + 11,
1503                                      df[index + 1] + 5))))))));
1504         // clang-format on
1505       }
1506 
1507       // Finally, we can check the df value and see if it's less than
1508       // or equal to the usedMargin5X value.
1509       if (df[index] <= usedMargin5X) {
1510         if (iMax == -1) {
1511           iMax = i;
1512         }
1513         MOZ_ASSERT(iMin > (int32_t)i);
1514         iMin = i;
1515       }
1516     }
1517 
1518     if (iMax != -1) {
1519       // Our interval values, iMin, iMax, and b are all calculated from
1520       // the expanded region, which is based on the margin rect. To create
1521       // our interval, we have to subtract kiExpansionPerSide from iMin and
1522       // iMax, and subtract kbExpansionPerSide from b to account for the
1523       // expanded region edges.  This produces coords that are relative to
1524       // our margin-rect.
1525 
1526       // Origin for this interval is at the aMarginRect origin, adjusted in
1527       // the block direction by b in app units, and in the inline direction
1528       // by iMin in app units.
1529       nsPoint origin(
1530           aMarginRect.x + (iMin - kiExpansionPerSide) * aAppUnitsPerDevPixel,
1531           aMarginRect.y + (b - kbExpansionPerSide) * aAppUnitsPerDevPixel);
1532 
1533       // Size is the difference in iMax and iMin, plus 1 (to account for the
1534       // whole pixel) dev pixels, by 1 block dev pixel. We don't bother
1535       // subtracting kiExpansionPerSide from iMin and iMax in this case
1536       // because we only care about the distance between them. We convert
1537       // everything to app units.
1538       nsSize size((iMax - iMin + 1) * aAppUnitsPerDevPixel,
1539                   aAppUnitsPerDevPixel);
1540 
1541       mIntervals.AppendElement(nsRect(origin, size));
1542     }
1543   }
1544 
1545   // Reverse the intervals keep the array sorted on the block direction.
1546   mIntervals.Reverse();
1547 
1548   // Adjust our extents by aShapeMargin. This may cause overflow of some
1549   // kind if aShapeMargin is large, so we do some clamping to maintain the
1550   // invariant mBStart <= mBEnd.
1551   mBStart = std::min(mBStart, mBStart - aShapeMargin);
1552   mBEnd = std::max(mBEnd, mBEnd + aShapeMargin);
1553 }
1554 
LineLeft(const nscoord aBStart,const nscoord aBEnd) const1555 nscoord nsFloatManager::PolygonShapeInfo::LineLeft(const nscoord aBStart,
1556                                                    const nscoord aBEnd) const {
1557   // Use intervals if we have them.
1558   if (!mIntervals.IsEmpty()) {
1559     return LineEdge(mIntervals, aBStart, aBEnd, true);
1560   }
1561 
1562   // We want the line-left-most inline-axis coordinate where the
1563   // (block-axis) aBStart/aBEnd band crosses a line segment of the polygon.
1564   // To get that, we start as line-right as possible (at nscoord_MAX). Then
1565   // we iterate each line segment to compute its intersection point with the
1566   // band (if any) and using std::min() successively to get the smallest
1567   // inline-coordinates among those intersection points.
1568   //
1569   // Note: std::min<nscoord> means the function std::min() with template
1570   // parameter nscoord, not the minimum value of nscoord.
1571   return ComputeLineIntercept(aBStart, aBEnd, std::min<nscoord>, nscoord_MAX);
1572 }
1573 
LineRight(const nscoord aBStart,const nscoord aBEnd) const1574 nscoord nsFloatManager::PolygonShapeInfo::LineRight(const nscoord aBStart,
1575                                                     const nscoord aBEnd) const {
1576   // Use intervals if we have them.
1577   if (!mIntervals.IsEmpty()) {
1578     return LineEdge(mIntervals, aBStart, aBEnd, false);
1579   }
1580 
1581   // Similar to LineLeft(). Though here, we want the line-right-most
1582   // inline-axis coordinate, so we instead start at nscoord_MIN and use
1583   // std::max() to get the biggest inline-coordinate among those
1584   // intersection points.
1585   return ComputeLineIntercept(aBStart, aBEnd, std::max<nscoord>, nscoord_MIN);
1586 }
1587 
ComputeExtent()1588 void nsFloatManager::PolygonShapeInfo::ComputeExtent() {
1589   // mBStart and mBEnd are the lower and the upper bounds of all the
1590   // vertex.y, respectively. The vertex.y is actually on the block-axis of
1591   // the float manager's writing mode.
1592   for (const nsPoint& vertex : mVertices) {
1593     mBStart = std::min(mBStart, vertex.y);
1594     mBEnd = std::max(mBEnd, vertex.y);
1595   }
1596 
1597   MOZ_ASSERT(mBStart <= mBEnd,
1598              "Start of float area should be less than "
1599              "or equal to the end.");
1600 }
1601 
ComputeLineIntercept(const nscoord aBStart,const nscoord aBEnd,nscoord (* aCompareOp)(std::initializer_list<nscoord>),const nscoord aLineInterceptInitialValue) const1602 nscoord nsFloatManager::PolygonShapeInfo::ComputeLineIntercept(
1603     const nscoord aBStart, const nscoord aBEnd,
1604     nscoord (*aCompareOp)(std::initializer_list<nscoord>),
1605     const nscoord aLineInterceptInitialValue) const {
1606   MOZ_ASSERT(aBStart <= aBEnd,
1607              "The band's block start is greater than its block end?");
1608 
1609   const size_t len = mVertices.Length();
1610   nscoord lineIntercept = aLineInterceptInitialValue;
1611 
1612   // We have some special treatment of horizontal lines between vertices.
1613   // Generally, we can ignore the impact of the horizontal lines since their
1614   // endpoints will be included in the lines preceeding or following them.
1615   // However, it's possible the polygon is entirely a horizontal line,
1616   // possibly built from more than one horizontal segment. In such a case,
1617   // we need to have the horizontal line(s) contribute to the line intercepts.
1618   // We do this by accepting horizontal lines until we find a non-horizontal
1619   // line, after which all further horizontal lines are ignored.
1620   bool canIgnoreHorizontalLines = false;
1621 
1622   // Iterate each line segment {p0, p1}, {p1, p2}, ..., {pn, p0}.
1623   for (size_t i = 0; i < len; ++i) {
1624     const nsPoint* smallYVertex = &mVertices[i];
1625     const nsPoint* bigYVertex = &mVertices[(i + 1) % len];
1626 
1627     // Swap the two points to satisfy the requirement for calling
1628     // XInterceptAtY.
1629     if (smallYVertex->y > bigYVertex->y) {
1630       std::swap(smallYVertex, bigYVertex);
1631     }
1632 
1633     // Generally, we need to ignore line segments that either don't intersect
1634     // the band, or merely touch it. However, if the polygon has no block extent
1635     // (it is a point, or a horizontal line), and the band touches the line
1636     // segment, we let that line segment through.
1637     if ((aBStart >= bigYVertex->y || aBEnd <= smallYVertex->y) &&
1638         !(mBStart == mBEnd && aBStart == bigYVertex->y)) {
1639       // Skip computing the intercept if the band doesn't intersect the
1640       // line segment.
1641       continue;
1642     }
1643 
1644     nscoord bStartLineIntercept;
1645     nscoord bEndLineIntercept;
1646 
1647     if (smallYVertex->y == bigYVertex->y) {
1648       // The line is horizontal; see if we can ignore it.
1649       if (canIgnoreHorizontalLines) {
1650         continue;
1651       }
1652 
1653       // For a horizontal line that we can't ignore, we treat the two x value
1654       // ends as the bStartLineIntercept and bEndLineIntercept. It doesn't
1655       // matter which is applied to which, because they'll both be applied
1656       // to aCompareOp.
1657       bStartLineIntercept = smallYVertex->x;
1658       bEndLineIntercept = bigYVertex->x;
1659     } else {
1660       // This is not a horizontal line. We can now ignore all future
1661       // horizontal lines.
1662       canIgnoreHorizontalLines = true;
1663 
1664       bStartLineIntercept =
1665           aBStart <= smallYVertex->y
1666               ? smallYVertex->x
1667               : XInterceptAtY(aBStart, *smallYVertex, *bigYVertex);
1668       bEndLineIntercept =
1669           aBEnd >= bigYVertex->y
1670               ? bigYVertex->x
1671               : XInterceptAtY(aBEnd, *smallYVertex, *bigYVertex);
1672     }
1673 
1674     // If either new intercept is more extreme than lineIntercept (per
1675     // aCompareOp), then update lineIntercept to that value.
1676     lineIntercept =
1677         aCompareOp({lineIntercept, bStartLineIntercept, bEndLineIntercept});
1678   }
1679 
1680   return lineIntercept;
1681 }
1682 
Translate(nscoord aLineLeft,nscoord aBlockStart)1683 void nsFloatManager::PolygonShapeInfo::Translate(nscoord aLineLeft,
1684                                                  nscoord aBlockStart) {
1685   for (nsPoint& vertex : mVertices) {
1686     vertex.MoveBy(aLineLeft, aBlockStart);
1687   }
1688   for (nsRect& interval : mIntervals) {
1689     interval.MoveBy(aLineLeft, aBlockStart);
1690   }
1691   mBStart += aBlockStart;
1692   mBEnd += aBlockStart;
1693 }
1694 
1695 /* static */
XInterceptAtY(const nscoord aY,const nsPoint & aP1,const nsPoint & aP2)1696 nscoord nsFloatManager::PolygonShapeInfo::XInterceptAtY(const nscoord aY,
1697                                                         const nsPoint& aP1,
1698                                                         const nsPoint& aP2) {
1699   // Solve for x in the linear equation: x = x1 + (y-y1) * (x2-x1) / (y2-y1),
1700   // where aP1 = (x1, y1) and aP2 = (x2, y2).
1701 
1702   MOZ_ASSERT(aP1.y <= aY && aY <= aP2.y,
1703              "This function won't work if the horizontal line at aY and "
1704              "the line segment (aP1, aP2) do not intersect!");
1705 
1706   MOZ_ASSERT(aP1.y != aP2.y,
1707              "A horizontal line segment results in dividing by zero error!");
1708 
1709   return aP1.x + (aY - aP1.y) * (aP2.x - aP1.x) / (aP2.y - aP1.y);
1710 }
1711 
1712 /////////////////////////////////////////////////////////////////////////////
1713 // ImageShapeInfo
1714 //
1715 // Implements shape-outside: <image>
1716 //
1717 class nsFloatManager::ImageShapeInfo final : public nsFloatManager::ShapeInfo {
1718  public:
1719   ImageShapeInfo(uint8_t* aAlphaPixels, int32_t aStride,
1720                  const LayoutDeviceIntSize& aImageSize,
1721                  int32_t aAppUnitsPerDevPixel, float aShapeImageThreshold,
1722                  nscoord aShapeMargin, const nsRect& aContentRect,
1723                  const nsRect& aMarginRect, WritingMode aWM,
1724                  const nsSize& aContainerSize);
1725 
1726   nscoord LineLeft(const nscoord aBStart, const nscoord aBEnd) const override;
1727   nscoord LineRight(const nscoord aBStart, const nscoord aBEnd) const override;
BStart() const1728   nscoord BStart() const override { return mBStart; }
BEnd() const1729   nscoord BEnd() const override { return mBEnd; }
IsEmpty() const1730   bool IsEmpty() const override { return mIntervals.IsEmpty(); }
MayNarrowInBlockDirection() const1731   bool MayNarrowInBlockDirection() const override { return true; }
1732 
1733   void Translate(nscoord aLineLeft, nscoord aBlockStart) override;
1734 
1735  private:
1736   // An interval is slice of the float area defined by this ImageShapeInfo.
1737   // Each interval is a rectangle that is one pixel deep in the block
1738   // axis. The values are stored as block edges in the y coordinates,
1739   // and inline edges as the x coordinates.
1740 
1741   // The intervals are stored in ascending order on y.
1742   nsTArray<nsRect> mIntervals;
1743 
1744   nscoord mBStart = nscoord_MAX;
1745   nscoord mBEnd = nscoord_MIN;
1746 
1747   // CreateInterval transforms the supplied aIMin and aIMax and aB
1748   // values into an interval that respects the writing mode. An
1749   // aOffsetFromContainer can be provided if the aIMin, aIMax, aB
1750   // values were generated relative to something other than the container
1751   // rect (such as the content rect or margin rect).
1752   void CreateInterval(int32_t aIMin, int32_t aIMax, int32_t aB,
1753                       int32_t aAppUnitsPerDevPixel,
1754                       const nsPoint& aOffsetFromContainer, WritingMode aWM,
1755                       const nsSize& aContainerSize);
1756 };
1757 
ImageShapeInfo(uint8_t * aAlphaPixels,int32_t aStride,const LayoutDeviceIntSize & aImageSize,int32_t aAppUnitsPerDevPixel,float aShapeImageThreshold,nscoord aShapeMargin,const nsRect & aContentRect,const nsRect & aMarginRect,WritingMode aWM,const nsSize & aContainerSize)1758 nsFloatManager::ImageShapeInfo::ImageShapeInfo(
1759     uint8_t* aAlphaPixels, int32_t aStride,
1760     const LayoutDeviceIntSize& aImageSize, int32_t aAppUnitsPerDevPixel,
1761     float aShapeImageThreshold, nscoord aShapeMargin,
1762     const nsRect& aContentRect, const nsRect& aMarginRect, WritingMode aWM,
1763     const nsSize& aContainerSize) {
1764   MOZ_ASSERT(aShapeImageThreshold >= 0.0 && aShapeImageThreshold <= 1.0,
1765              "The computed value of shape-image-threshold is wrong!");
1766 
1767   const uint8_t threshold = NSToIntFloor(aShapeImageThreshold * 255);
1768 
1769   MOZ_ASSERT(aImageSize.width >= 0 && aImageSize.height >= 0,
1770              "Image size must be non-negative for our math to work.");
1771   const uint32_t w = aImageSize.width;
1772   const uint32_t h = aImageSize.height;
1773 
1774   if (aShapeMargin <= 0) {
1775     // Without a positive aShapeMargin, all we have to do is a
1776     // direct threshold comparison of the alpha pixels.
1777     // https://drafts.csswg.org/css-shapes-1/#valdef-shape-image-threshold-number
1778 
1779     // Scan the pixels in a double loop. For horizontal writing modes, we do
1780     // this row by row, from top to bottom. For vertical writing modes, we do
1781     // column by column, from left to right. We define the two loops
1782     // generically, then figure out the rows and cols within the inner loop.
1783     const uint32_t bSize = aWM.IsVertical() ? w : h;
1784     const uint32_t iSize = aWM.IsVertical() ? h : w;
1785     for (uint32_t b = 0; b < bSize; ++b) {
1786       // iMin and max store the start and end of the float area for the row
1787       // or column represented by this iteration of the outer loop.
1788       int32_t iMin = -1;
1789       int32_t iMax = -1;
1790 
1791       for (uint32_t i = 0; i < iSize; ++i) {
1792         const uint32_t col = aWM.IsVertical() ? b : i;
1793         const uint32_t row = aWM.IsVertical() ? i : b;
1794         const uint32_t index = col + row * aStride;
1795 
1796         // Determine if the alpha pixel at this row and column has a value
1797         // greater than the threshold. If it does, update our iMin and iMax
1798         // values to track the edges of the float area for this row or column.
1799         // https://drafts.csswg.org/css-shapes-1/#valdef-shape-image-threshold-number
1800         const uint8_t alpha = aAlphaPixels[index];
1801         if (alpha > threshold) {
1802           if (iMin == -1) {
1803             iMin = i;
1804           }
1805           MOZ_ASSERT(iMax < (int32_t)i);
1806           iMax = i;
1807         }
1808       }
1809 
1810       // At the end of a row or column; did we find something?
1811       if (iMin != -1) {
1812         // We need to supply an offset of the content rect top left, since
1813         // our col and row have been calculated from the content rect,
1814         // instead of the margin rect (against which floats are applied).
1815         CreateInterval(iMin, iMax, b, aAppUnitsPerDevPixel,
1816                        aContentRect.TopLeft(), aWM, aContainerSize);
1817       }
1818     }
1819 
1820     if (aWM.IsVerticalRL()) {
1821       // vertical-rl or sideways-rl.
1822       // Because we scan the columns from left to right, we need to reverse
1823       // the array so that it's sorted (in ascending order) on the block
1824       // direction.
1825       mIntervals.Reverse();
1826     }
1827   } else {
1828     // With a positive aShapeMargin, we have to calculate a distance
1829     // field from the opaque pixels, then build intervals based on
1830     // them being within aShapeMargin distance to an opaque pixel.
1831 
1832     // Roughly: for each pixel in the margin box, we need to determine the
1833     // distance to the nearest opaque image-pixel.  If that distance is less
1834     // than aShapeMargin, we consider this margin-box pixel as being part of
1835     // the float area.
1836 
1837     // Computing the distance field is a two-pass O(n) operation.
1838     // We use a chamfer 5-7-11 5x5 matrix to compute minimum distance
1839     // to an opaque pixel. This integer math computation is reasonably
1840     // close to the true Euclidean distance. The distances will be
1841     // approximately 5x the true distance, quantized in integer units.
1842     // The 5x is factored away in the comparison used in the final
1843     // pass which builds the intervals.
1844     dfType usedMargin5X =
1845         CalcUsedShapeMargin5X(aShapeMargin, aAppUnitsPerDevPixel);
1846 
1847     // Allocate our distance field.  The distance field has to cover
1848     // the entire aMarginRect, since aShapeMargin could bleed into it,
1849     // beyond the content rect covered by aAlphaPixels. To make this work,
1850     // we calculate a dfOffset value which is the top left of the content
1851     // rect relative to the margin rect.
1852     nsPoint offsetPoint = aContentRect.TopLeft() - aMarginRect.TopLeft();
1853     LayoutDeviceIntPoint dfOffset = LayoutDevicePixel::FromAppUnitsRounded(
1854         offsetPoint, aAppUnitsPerDevPixel);
1855 
1856     // Since our distance field is computed with a 5x5 neighborhood,
1857     // we need to expand our distance field by a further 4 pixels in
1858     // both axes, 2 on the leading edge and 2 on the trailing edge.
1859     // We call this edge area the "expanded region".
1860 
1861     // Our expansion amounts need to be the same for our math to work.
1862     static uint32_t kExpansionPerSide = 2;
1863     // Since dfOffset will be used in comparisons against expanded region
1864     // pixel values, it's convenient to add expansion amounts to dfOffset in
1865     // both axes, to simplify comparison math later.
1866     dfOffset.x += kExpansionPerSide;
1867     dfOffset.y += kExpansionPerSide;
1868 
1869     // In all these calculations, we purposely ignore aStride, because
1870     // we don't have to replicate the packing that we received in
1871     // aAlphaPixels. When we need to convert from df coordinates to
1872     // alpha coordinates, we do that with math based on row and col.
1873     const LayoutDeviceIntSize marginRectDevPixels =
1874         LayoutDevicePixel::FromAppUnitsRounded(aMarginRect.Size(),
1875                                                aAppUnitsPerDevPixel);
1876 
1877     // Clamp the size of our distance field sizes to prevent multiplication
1878     // overflow.
1879     static const uint32_t DF_SIDE_MAX =
1880         floor(sqrt((double)(std::numeric_limits<int32_t>::max())));
1881 
1882     // Clamp the margin plus 2X the expansion values between expansion + 1
1883     // and DF_SIDE_MAX. This ensures that the distance field allocation
1884     // doesn't overflow during multiplication, and the reverse iteration
1885     // doesn't underflow.
1886     const uint32_t wEx =
1887         std::max(std::min(marginRectDevPixels.width + (kExpansionPerSide * 2),
1888                           DF_SIDE_MAX),
1889                  kExpansionPerSide + 1);
1890     const uint32_t hEx =
1891         std::max(std::min(marginRectDevPixels.height + (kExpansionPerSide * 2),
1892                           DF_SIDE_MAX),
1893                  kExpansionPerSide + 1);
1894 
1895     // Since the margin-box size is CSS controlled, and large values will
1896     // generate large wEx and hEx values, we do a falliable allocation for
1897     // the distance field. If allocation fails, we early exit and layout will
1898     // be wrong, but we'll avoid aborting from OOM.
1899     auto df = MakeUniqueFallible<dfType[]>(wEx * hEx);
1900     if (!df) {
1901       // Without a distance field, we can't reason about the float area.
1902       return;
1903     }
1904 
1905     const uint32_t bSize = aWM.IsVertical() ? wEx : hEx;
1906     const uint32_t iSize = aWM.IsVertical() ? hEx : wEx;
1907 
1908     // First pass setting distance field, starting at top-left, three cases:
1909     // 1) Expanded region pixel: set to MAX_MARGIN_5X.
1910     // 2) Image pixel with alpha greater than threshold: set to 0.
1911     // 3) Other pixel: set to minimum backward-looking neighborhood
1912     //                 distance value, computed with 5-7-11 chamfer.
1913 
1914     // Scan the pixels in a double loop. For horizontal writing modes, we do
1915     // this row by row, from top to bottom. For vertical writing modes, we do
1916     // column by column, from left to right. We define the two loops
1917     // generically, then figure out the rows and cols within the inner loop.
1918     for (uint32_t b = 0; b < bSize; ++b) {
1919       for (uint32_t i = 0; i < iSize; ++i) {
1920         const uint32_t col = aWM.IsVertical() ? b : i;
1921         const uint32_t row = aWM.IsVertical() ? i : b;
1922         const uint32_t index = col + row * wEx;
1923         MOZ_ASSERT(index < (wEx * hEx),
1924                    "Our distance field index should be in-bounds.");
1925 
1926         // Handle our three cases, in order.
1927         if (col < kExpansionPerSide || col >= wEx - kExpansionPerSide ||
1928             row < kExpansionPerSide || row >= hEx - kExpansionPerSide) {
1929           // Case 1: Expanded pixel.
1930           df[index] = MAX_MARGIN_5X;
1931         } else if ((int32_t)col >= dfOffset.x &&
1932                    (int32_t)col < (dfOffset.x + aImageSize.width) &&
1933                    (int32_t)row >= dfOffset.y &&
1934                    (int32_t)row < (dfOffset.y + aImageSize.height) &&
1935                    aAlphaPixels[col - dfOffset.x +
1936                                 (row - dfOffset.y) * aStride] > threshold) {
1937           // Case 2: Image pixel that is opaque.
1938           DebugOnly<uint32_t> alphaIndex =
1939               col - dfOffset.x + (row - dfOffset.y) * aStride;
1940           MOZ_ASSERT(alphaIndex < (aStride * h),
1941                      "Our aAlphaPixels index should be in-bounds.");
1942 
1943           df[index] = 0;
1944         } else {
1945           // Case 3: Other pixel.
1946           if (aWM.IsVertical()) {
1947             // Column-by-column, starting at the left, each column
1948             // top-to-bottom.
1949             // Backward-looking neighborhood distance from target pixel X
1950             // with chamfer 5-7-11 looks like:
1951             //
1952             // +--+--+--+
1953             // |  |11|  |   |    +
1954             // +--+--+--+   |   /|
1955             // |11| 7| 5|   |  / |
1956             // +--+--+--+   | /  V
1957             // |  | 5| X|   |/
1958             // +--+--+--+   +
1959             // |11| 7|  |
1960             // +--+--+--+
1961             // |  |11|  |
1962             // +--+--+--+
1963             //
1964             // X should be set to the minimum of MAX_MARGIN_5X and the
1965             // values of all of the numbered neighbors summed with the
1966             // value in that chamfer cell.
1967             MOZ_ASSERT(index - wEx - 2 < (iSize * bSize) &&
1968                            index + wEx - 2 < (iSize * bSize) &&
1969                            index - (wEx * 2) - 1 < (iSize * bSize),
1970                        "Our distance field most extreme indices should be "
1971                        "in-bounds.");
1972 
1973             // clang-format off
1974             df[index] = std::min<dfType>(MAX_MARGIN_5X,
1975                         std::min<dfType>(df[index - wEx - 2] + 11,
1976                         std::min<dfType>(df[index + wEx - 2] + 11,
1977                         std::min<dfType>(df[index - (wEx * 2) - 1] + 11,
1978                         std::min<dfType>(df[index - wEx - 1] + 7,
1979                         std::min<dfType>(df[index - 1] + 5,
1980                         std::min<dfType>(df[index + wEx - 1] + 7,
1981                         std::min<dfType>(df[index + (wEx * 2) - 1] + 11,
1982                                          df[index - wEx] + 5))))))));
1983             // clang-format on
1984           } else {
1985             // Row-by-row, starting at the top, each row left-to-right.
1986             // Backward-looking neighborhood distance from target pixel X
1987             // with chamfer 5-7-11 looks like:
1988             //
1989             // +--+--+--+--+--+
1990             // |  |11|  |11|  |   ----+
1991             // +--+--+--+--+--+      /
1992             // |11| 7| 5| 7|11|     /
1993             // +--+--+--+--+--+    /
1994             // |  | 5| X|  |  |   +-->
1995             // +--+--+--+--+--+
1996             //
1997             // X should be set to the minimum of MAX_MARGIN_5X and the
1998             // values of all of the numbered neighbors summed with the
1999             // value in that chamfer cell.
2000             MOZ_ASSERT(index - (wEx * 2) - 1 < (iSize * bSize) &&
2001                            index - wEx - 2 < (iSize * bSize),
2002                        "Our distance field most extreme indices should be "
2003                        "in-bounds.");
2004 
2005             // clang-format off
2006             df[index] = std::min<dfType>(MAX_MARGIN_5X,
2007                         std::min<dfType>(df[index - (wEx * 2) - 1] + 11,
2008                         std::min<dfType>(df[index - (wEx * 2) + 1] + 11,
2009                         std::min<dfType>(df[index - wEx - 2] + 11,
2010                         std::min<dfType>(df[index - wEx - 1] + 7,
2011                         std::min<dfType>(df[index - wEx] + 5,
2012                         std::min<dfType>(df[index - wEx + 1] + 7,
2013                         std::min<dfType>(df[index - wEx + 2] + 11,
2014                                          df[index - 1] + 5))))))));
2015             // clang-format on
2016           }
2017         }
2018       }
2019     }
2020 
2021     // Okay, time for the second pass. This pass is in reverse order from
2022     // the first pass. All of our opaque pixels have been set to 0, and all
2023     // of our expanded region pixels have been set to MAX_MARGIN_5X. Other
2024     // pixels have been set to some value between those two (inclusive) but
2025     // this hasn't yet taken into account the neighbors that were processed
2026     // after them in the first pass. This time we reverse iterate so we can
2027     // apply the forward-looking chamfer.
2028 
2029     // This time, we constrain our outer and inner loop to ignore the
2030     // expanded region pixels. For each pixel we iterate, we set the df value
2031     // to the minimum forward-looking neighborhood distance value, computed
2032     // with a 5-7-11 chamfer. We also check each df value against the
2033     // usedMargin5X threshold, and use that to set the iMin and iMax values
2034     // for the interval we'll create for that block axis value (b).
2035 
2036     // At the end of each row (or column in vertical writing modes),
2037     // if any of the other pixels had a value less than usedMargin5X,
2038     // we create an interval. Note: "bSize - kExpansionPerSide - 1" is the
2039     // index of the final row of pixels before the trailing expanded region.
2040     for (uint32_t b = bSize - kExpansionPerSide - 1; b >= kExpansionPerSide;
2041          --b) {
2042       // iMin tracks the first df pixel and iMax the last df pixel whose
2043       // df[] value is less than usedMargin5X. Set iMin and iMax in
2044       // preparation for this row or column.
2045       int32_t iMin = iSize;
2046       int32_t iMax = -1;
2047 
2048       // Note: "iSize - kExpansionPerSide - 1" is the index of the final row
2049       // of pixels before the trailing expanded region.
2050       for (uint32_t i = iSize - kExpansionPerSide - 1; i >= kExpansionPerSide;
2051            --i) {
2052         const uint32_t col = aWM.IsVertical() ? b : i;
2053         const uint32_t row = aWM.IsVertical() ? i : b;
2054         const uint32_t index = col + row * wEx;
2055         MOZ_ASSERT(index < (wEx * hEx),
2056                    "Our distance field index should be in-bounds.");
2057 
2058         // Only apply the chamfer calculation if the df value is not
2059         // already 0, since the chamfer can only reduce the value.
2060         if (df[index]) {
2061           if (aWM.IsVertical()) {
2062             // Column-by-column, starting at the right, each column
2063             // bottom-to-top.
2064             // Forward-looking neighborhood distance from target pixel X
2065             // with chamfer 5-7-11 looks like:
2066             //
2067             // +--+--+--+
2068             // |  |11|  |        +
2069             // +--+--+--+       /|
2070             // |  | 7|11|   A  / |
2071             // +--+--+--+   | /  |
2072             // | X| 5|  |   |/   |
2073             // +--+--+--+   +    |
2074             // | 5| 7|11|
2075             // +--+--+--+
2076             // |  |11|  |
2077             // +--+--+--+
2078             //
2079             // X should be set to the minimum of its current value and
2080             // the values of all of the numbered neighbors summed with
2081             // the value in that chamfer cell.
2082             MOZ_ASSERT(index + wEx + 2 < (wEx * hEx) &&
2083                            index + (wEx * 2) + 1 < (wEx * hEx) &&
2084                            index - (wEx * 2) + 1 < (wEx * hEx),
2085                        "Our distance field most extreme indices should be "
2086                        "in-bounds.");
2087 
2088             // clang-format off
2089             df[index] = std::min<dfType>(df[index],
2090                         std::min<dfType>(df[index + wEx + 2] + 11,
2091                         std::min<dfType>(df[index - wEx + 2] + 11,
2092                         std::min<dfType>(df[index + (wEx * 2) + 1] + 11,
2093                         std::min<dfType>(df[index + wEx + 1] + 7,
2094                         std::min<dfType>(df[index + 1] + 5,
2095                         std::min<dfType>(df[index - wEx + 1] + 7,
2096                         std::min<dfType>(df[index - (wEx * 2) + 1] + 11,
2097                                          df[index + wEx] + 5))))))));
2098             // clang-format on
2099           } else {
2100             // Row-by-row, starting at the bottom, each row right-to-left.
2101             // Forward-looking neighborhood distance from target pixel X
2102             // with chamfer 5-7-11 looks like:
2103             //
2104             // +--+--+--+--+--+
2105             // |  |  | X| 5|  |    <--+
2106             // +--+--+--+--+--+      /
2107             // |11| 7| 5| 7|11|     /
2108             // +--+--+--+--+--+    /
2109             // |  |11|  |11|  |   +----
2110             // +--+--+--+--+--+
2111             //
2112             // X should be set to the minimum of its current value and
2113             // the values of all of the numbered neighbors summed with
2114             // the value in that chamfer cell.
2115             MOZ_ASSERT(index + (wEx * 2) + 1 < (wEx * hEx) &&
2116                            index + wEx + 2 < (wEx * hEx),
2117                        "Our distance field most extreme indices should be "
2118                        "in-bounds.");
2119 
2120             // clang-format off
2121             df[index] = std::min<dfType>(df[index],
2122                         std::min<dfType>(df[index + (wEx * 2) + 1] + 11,
2123                         std::min<dfType>(df[index + (wEx * 2) - 1] + 11,
2124                         std::min<dfType>(df[index + wEx + 2] + 11,
2125                         std::min<dfType>(df[index + wEx + 1] + 7,
2126                         std::min<dfType>(df[index + wEx] + 5,
2127                         std::min<dfType>(df[index + wEx - 1] + 7,
2128                         std::min<dfType>(df[index + wEx - 2] + 11,
2129                                          df[index + 1] + 5))))))));
2130             // clang-format on
2131           }
2132         }
2133 
2134         // Finally, we can check the df value and see if it's less than
2135         // or equal to the usedMargin5X value.
2136         if (df[index] <= usedMargin5X) {
2137           if (iMax == -1) {
2138             iMax = i;
2139           }
2140           MOZ_ASSERT(iMin > (int32_t)i);
2141           iMin = i;
2142         }
2143       }
2144 
2145       if (iMax != -1) {
2146         // Our interval values, iMin, iMax, and b are all calculated from
2147         // the expanded region, which is based on the margin rect. To create
2148         // our interval, we have to subtract kExpansionPerSide from (iMin,
2149         // iMax, and b) to account for the expanded region edges. This
2150         // produces coords that are relative to our margin-rect, so we pass
2151         // in aMarginRect.TopLeft() to make CreateInterval convert to our
2152         // container's coordinate space.
2153         CreateInterval(iMin - kExpansionPerSide, iMax - kExpansionPerSide,
2154                        b - kExpansionPerSide, aAppUnitsPerDevPixel,
2155                        aMarginRect.TopLeft(), aWM, aContainerSize);
2156       }
2157     }
2158 
2159     if (!aWM.IsVerticalRL()) {
2160       // Anything other than vertical-rl or sideways-rl.
2161       // Because we assembled our intervals on the bottom-up pass,
2162       // they are reversed for most writing modes. Reverse them to
2163       // keep the array sorted on the block direction.
2164       mIntervals.Reverse();
2165     }
2166   }
2167 
2168   if (!mIntervals.IsEmpty()) {
2169     mBStart = mIntervals[0].Y();
2170     mBEnd = mIntervals.LastElement().YMost();
2171   }
2172 }
2173 
CreateInterval(int32_t aIMin,int32_t aIMax,int32_t aB,int32_t aAppUnitsPerDevPixel,const nsPoint & aOffsetFromContainer,WritingMode aWM,const nsSize & aContainerSize)2174 void nsFloatManager::ImageShapeInfo::CreateInterval(
2175     int32_t aIMin, int32_t aIMax, int32_t aB, int32_t aAppUnitsPerDevPixel,
2176     const nsPoint& aOffsetFromContainer, WritingMode aWM,
2177     const nsSize& aContainerSize) {
2178   // Store an interval as an nsRect with our inline axis values stored in x
2179   // and our block axis values stored in y. The position is dependent on
2180   // the writing mode, but the size is the same for all writing modes.
2181 
2182   // Size is the difference in inline axis edges stored as x, and one
2183   // block axis pixel stored as y. For the inline axis, we add 1 to aIMax
2184   // because we want to capture the far edge of the last pixel.
2185   nsSize size(((aIMax + 1) - aIMin) * aAppUnitsPerDevPixel,
2186               aAppUnitsPerDevPixel);
2187 
2188   // Since we started our scanning of the image pixels from the top left,
2189   // the interval position starts from the origin of the content rect,
2190   // converted to logical coordinates.
2191   nsPoint origin =
2192       ConvertToFloatLogical(aOffsetFromContainer, aWM, aContainerSize);
2193 
2194   // Depending on the writing mode, we now move the origin.
2195   if (aWM.IsVerticalRL()) {
2196     // vertical-rl or sideways-rl.
2197     // These writing modes proceed from the top right, and each interval
2198     // moves in a positive inline direction and negative block direction.
2199     // That means that the intervals will be reversed after all have been
2200     // constructed. We add 1 to aB to capture the end of the block axis pixel.
2201     origin.MoveBy(aIMin * aAppUnitsPerDevPixel,
2202                   (aB + 1) * -aAppUnitsPerDevPixel);
2203   } else if (aWM.IsSidewaysLR()) {
2204     // This writing mode proceeds from the bottom left, and each interval
2205     // moves in a negative inline direction and a positive block direction.
2206     // We add 1 to aIMax to capture the end of the inline axis pixel.
2207     origin.MoveBy((aIMax + 1) * -aAppUnitsPerDevPixel,
2208                   aB * aAppUnitsPerDevPixel);
2209   } else {
2210     // horizontal-tb or vertical-lr.
2211     // These writing modes proceed from the top left and each interval
2212     // moves in a positive step in both inline and block directions.
2213     origin.MoveBy(aIMin * aAppUnitsPerDevPixel, aB * aAppUnitsPerDevPixel);
2214   }
2215 
2216   mIntervals.AppendElement(nsRect(origin, size));
2217 }
2218 
LineLeft(const nscoord aBStart,const nscoord aBEnd) const2219 nscoord nsFloatManager::ImageShapeInfo::LineLeft(const nscoord aBStart,
2220                                                  const nscoord aBEnd) const {
2221   return LineEdge(mIntervals, aBStart, aBEnd, true);
2222 }
2223 
LineRight(const nscoord aBStart,const nscoord aBEnd) const2224 nscoord nsFloatManager::ImageShapeInfo::LineRight(const nscoord aBStart,
2225                                                   const nscoord aBEnd) const {
2226   return LineEdge(mIntervals, aBStart, aBEnd, false);
2227 }
2228 
Translate(nscoord aLineLeft,nscoord aBlockStart)2229 void nsFloatManager::ImageShapeInfo::Translate(nscoord aLineLeft,
2230                                                nscoord aBlockStart) {
2231   for (nsRect& interval : mIntervals) {
2232     interval.MoveBy(aLineLeft, aBlockStart);
2233   }
2234 
2235   mBStart += aBlockStart;
2236   mBEnd += aBlockStart;
2237 }
2238 
2239 /////////////////////////////////////////////////////////////////////////////
2240 // FloatInfo
2241 
FloatInfo(nsIFrame * aFrame,nscoord aLineLeft,nscoord aBlockStart,const LogicalRect & aMarginRect,WritingMode aWM,const nsSize & aContainerSize)2242 nsFloatManager::FloatInfo::FloatInfo(nsIFrame* aFrame, nscoord aLineLeft,
2243                                      nscoord aBlockStart,
2244                                      const LogicalRect& aMarginRect,
2245                                      WritingMode aWM,
2246                                      const nsSize& aContainerSize)
2247     : mFrame(aFrame),
2248       mLeftBEnd(nscoord_MIN),
2249       mRightBEnd(nscoord_MIN),
2250       mRect(ShapeInfo::ConvertToFloatLogical(aMarginRect, aWM, aContainerSize) +
2251             nsPoint(aLineLeft, aBlockStart)) {
2252   MOZ_COUNT_CTOR(nsFloatManager::FloatInfo);
2253   using ShapeOutsideType = StyleShapeOutside::Tag;
2254 
2255   if (IsEmpty()) {
2256     // Per spec, a float area defined by a shape is clipped to the float’s
2257     // margin box. Therefore, no need to create a shape info if the float's
2258     // margin box is empty, since a float area can only be smaller than the
2259     // margin box.
2260 
2261     // https://drafts.csswg.org/css-shapes/#relation-to-box-model-and-float-behavior
2262     return;
2263   }
2264 
2265   const nsStyleDisplay* styleDisplay = mFrame->StyleDisplay();
2266   const auto& shapeOutside = styleDisplay->mShapeOutside;
2267 
2268   nscoord shapeMargin = shapeOutside.IsNone()
2269                             ? 0
2270                             : nsLayoutUtils::ResolveToLength<true>(
2271                                   styleDisplay->mShapeMargin,
2272                                   LogicalSize(aWM, aContainerSize).ISize(aWM));
2273 
2274   switch (shapeOutside.tag) {
2275     case ShapeOutsideType::None:
2276       // No need to create shape info.
2277       return;
2278 
2279     case ShapeOutsideType::Image: {
2280       float shapeImageThreshold = styleDisplay->mShapeImageThreshold;
2281       mShapeInfo = ShapeInfo::CreateImageShape(
2282           shapeOutside.AsImage(), shapeImageThreshold, shapeMargin, mFrame,
2283           aMarginRect, aWM, aContainerSize);
2284       if (!mShapeInfo) {
2285         // Image is not ready, or fails to load, etc.
2286         return;
2287       }
2288 
2289       break;
2290     }
2291 
2292     case ShapeOutsideType::Box: {
2293       // Initialize <shape-box>'s reference rect.
2294       LogicalRect shapeBoxRect = ShapeInfo::ComputeShapeBoxRect(
2295           shapeOutside.AsBox(), mFrame, aMarginRect, aWM);
2296       mShapeInfo = ShapeInfo::CreateShapeBox(mFrame, shapeMargin, shapeBoxRect,
2297                                              aWM, aContainerSize);
2298       break;
2299     }
2300 
2301     case ShapeOutsideType::Shape: {
2302       const auto& shape = *shapeOutside.AsShape()._0;
2303       // Initialize <shape-box>'s reference rect.
2304       LogicalRect shapeBoxRect = ShapeInfo::ComputeShapeBoxRect(
2305           shapeOutside.AsShape()._1, mFrame, aMarginRect, aWM);
2306       mShapeInfo =
2307           ShapeInfo::CreateBasicShape(shape, shapeMargin, mFrame, shapeBoxRect,
2308                                       aMarginRect, aWM, aContainerSize);
2309       break;
2310     }
2311   }
2312 
2313   MOZ_ASSERT(mShapeInfo,
2314              "All shape-outside values except none should have mShapeInfo!");
2315 
2316   // Translate the shape to the same origin as nsFloatManager.
2317   mShapeInfo->Translate(aLineLeft, aBlockStart);
2318 }
2319 
2320 #ifdef NS_BUILD_REFCNT_LOGGING
FloatInfo(FloatInfo && aOther)2321 nsFloatManager::FloatInfo::FloatInfo(FloatInfo&& aOther)
2322     : mFrame(std::move(aOther.mFrame)),
2323       mLeftBEnd(std::move(aOther.mLeftBEnd)),
2324       mRightBEnd(std::move(aOther.mRightBEnd)),
2325       mRect(std::move(aOther.mRect)),
2326       mShapeInfo(std::move(aOther.mShapeInfo)) {
2327   MOZ_COUNT_CTOR(nsFloatManager::FloatInfo);
2328 }
2329 
~FloatInfo()2330 nsFloatManager::FloatInfo::~FloatInfo() {
2331   MOZ_COUNT_DTOR(nsFloatManager::FloatInfo);
2332 }
2333 #endif
2334 
LineLeft(ShapeType aShapeType,const nscoord aBStart,const nscoord aBEnd) const2335 nscoord nsFloatManager::FloatInfo::LineLeft(ShapeType aShapeType,
2336                                             const nscoord aBStart,
2337                                             const nscoord aBEnd) const {
2338   if (aShapeType == ShapeType::Margin) {
2339     return LineLeft();
2340   }
2341 
2342   MOZ_ASSERT(aShapeType == ShapeType::ShapeOutside);
2343   if (!mShapeInfo) {
2344     return LineLeft();
2345   }
2346   // Clip the flow area to the margin-box because
2347   // https://drafts.csswg.org/css-shapes-1/#relation-to-box-model-and-float-behavior
2348   // says "When a shape is used to define a float area, the shape is clipped
2349   // to the float’s margin box."
2350   return std::max(LineLeft(), mShapeInfo->LineLeft(aBStart, aBEnd));
2351 }
2352 
LineRight(ShapeType aShapeType,const nscoord aBStart,const nscoord aBEnd) const2353 nscoord nsFloatManager::FloatInfo::LineRight(ShapeType aShapeType,
2354                                              const nscoord aBStart,
2355                                              const nscoord aBEnd) const {
2356   if (aShapeType == ShapeType::Margin) {
2357     return LineRight();
2358   }
2359 
2360   MOZ_ASSERT(aShapeType == ShapeType::ShapeOutside);
2361   if (!mShapeInfo) {
2362     return LineRight();
2363   }
2364   // Clip the flow area to the margin-box. See LineLeft().
2365   return std::min(LineRight(), mShapeInfo->LineRight(aBStart, aBEnd));
2366 }
2367 
BStart(ShapeType aShapeType) const2368 nscoord nsFloatManager::FloatInfo::BStart(ShapeType aShapeType) const {
2369   if (aShapeType == ShapeType::Margin) {
2370     return BStart();
2371   }
2372 
2373   MOZ_ASSERT(aShapeType == ShapeType::ShapeOutside);
2374   if (!mShapeInfo) {
2375     return BStart();
2376   }
2377   // Clip the flow area to the margin-box. See LineLeft().
2378   return std::max(BStart(), mShapeInfo->BStart());
2379 }
2380 
BEnd(ShapeType aShapeType) const2381 nscoord nsFloatManager::FloatInfo::BEnd(ShapeType aShapeType) const {
2382   if (aShapeType == ShapeType::Margin) {
2383     return BEnd();
2384   }
2385 
2386   MOZ_ASSERT(aShapeType == ShapeType::ShapeOutside);
2387   if (!mShapeInfo) {
2388     return BEnd();
2389   }
2390   // Clip the flow area to the margin-box. See LineLeft().
2391   return std::min(BEnd(), mShapeInfo->BEnd());
2392 }
2393 
IsEmpty(ShapeType aShapeType) const2394 bool nsFloatManager::FloatInfo::IsEmpty(ShapeType aShapeType) const {
2395   if (aShapeType == ShapeType::Margin) {
2396     return IsEmpty();
2397   }
2398 
2399   MOZ_ASSERT(aShapeType == ShapeType::ShapeOutside);
2400   if (!mShapeInfo) {
2401     return IsEmpty();
2402   }
2403   return mShapeInfo->IsEmpty();
2404 }
2405 
MayNarrowInBlockDirection(ShapeType aShapeType) const2406 bool nsFloatManager::FloatInfo::MayNarrowInBlockDirection(
2407     ShapeType aShapeType) const {
2408   // This function mirrors the cases of the three argument versions of
2409   // LineLeft() and LineRight(). This function returns true if and only if
2410   // either of those functions could possibly return "narrower" values with
2411   // increasing aBStart values. "Narrower" means closer to the far end of
2412   // the float shape.
2413   if (aShapeType == ShapeType::Margin) {
2414     return false;
2415   }
2416 
2417   MOZ_ASSERT(aShapeType == ShapeType::ShapeOutside);
2418   if (!mShapeInfo) {
2419     return false;
2420   }
2421 
2422   return mShapeInfo->MayNarrowInBlockDirection();
2423 }
2424 
2425 /////////////////////////////////////////////////////////////////////////////
2426 // ShapeInfo
2427 
2428 /* static */
ComputeShapeBoxRect(StyleShapeBox aBox,nsIFrame * const aFrame,const LogicalRect & aMarginRect,WritingMode aWM)2429 LogicalRect nsFloatManager::ShapeInfo::ComputeShapeBoxRect(
2430     StyleShapeBox aBox, nsIFrame* const aFrame, const LogicalRect& aMarginRect,
2431     WritingMode aWM) {
2432   LogicalRect rect = aMarginRect;
2433 
2434   switch (aBox) {
2435     case StyleShapeBox::ContentBox:
2436       rect.Deflate(aWM, aFrame->GetLogicalUsedPadding(aWM));
2437       [[fallthrough]];
2438     case StyleShapeBox::PaddingBox:
2439       rect.Deflate(aWM, aFrame->GetLogicalUsedBorder(aWM));
2440       [[fallthrough]];
2441     case StyleShapeBox::BorderBox:
2442       rect.Deflate(aWM, aFrame->GetLogicalUsedMargin(aWM));
2443       break;
2444     case StyleShapeBox::MarginBox:
2445       // Do nothing. rect is already a margin rect.
2446       break;
2447     default:
2448       MOZ_ASSERT_UNREACHABLE("Unknown shape box");
2449       break;
2450   }
2451 
2452   return rect;
2453 }
2454 
2455 /* static */ UniquePtr<nsFloatManager::ShapeInfo>
CreateShapeBox(nsIFrame * const aFrame,nscoord aShapeMargin,const LogicalRect & aShapeBoxRect,WritingMode aWM,const nsSize & aContainerSize)2456 nsFloatManager::ShapeInfo::CreateShapeBox(nsIFrame* const aFrame,
2457                                           nscoord aShapeMargin,
2458                                           const LogicalRect& aShapeBoxRect,
2459                                           WritingMode aWM,
2460                                           const nsSize& aContainerSize) {
2461   nsRect logicalShapeBoxRect =
2462       ConvertToFloatLogical(aShapeBoxRect, aWM, aContainerSize);
2463 
2464   // Inflate logicalShapeBoxRect by aShapeMargin.
2465   logicalShapeBoxRect.Inflate(aShapeMargin);
2466 
2467   nscoord physicalRadii[8];
2468   bool hasRadii = aFrame->GetShapeBoxBorderRadii(physicalRadii);
2469   if (!hasRadii) {
2470     return MakeUnique<RoundedBoxShapeInfo>(logicalShapeBoxRect,
2471                                            UniquePtr<nscoord[]>());
2472   }
2473 
2474   // Add aShapeMargin to each of the radii.
2475   for (nscoord& r : physicalRadii) {
2476     r += aShapeMargin;
2477   }
2478 
2479   return MakeUnique<RoundedBoxShapeInfo>(
2480       logicalShapeBoxRect, ConvertToFloatLogical(physicalRadii, aWM));
2481 }
2482 
2483 /* static */ UniquePtr<nsFloatManager::ShapeInfo>
CreateBasicShape(const StyleBasicShape & aBasicShape,nscoord aShapeMargin,nsIFrame * const aFrame,const LogicalRect & aShapeBoxRect,const LogicalRect & aMarginRect,WritingMode aWM,const nsSize & aContainerSize)2484 nsFloatManager::ShapeInfo::CreateBasicShape(const StyleBasicShape& aBasicShape,
2485                                             nscoord aShapeMargin,
2486                                             nsIFrame* const aFrame,
2487                                             const LogicalRect& aShapeBoxRect,
2488                                             const LogicalRect& aMarginRect,
2489                                             WritingMode aWM,
2490                                             const nsSize& aContainerSize) {
2491   switch (aBasicShape.tag) {
2492     case StyleBasicShape::Tag::Polygon:
2493       return CreatePolygon(aBasicShape, aShapeMargin, aFrame, aShapeBoxRect,
2494                            aMarginRect, aWM, aContainerSize);
2495     case StyleBasicShape::Tag::Circle:
2496     case StyleBasicShape::Tag::Ellipse:
2497       return CreateCircleOrEllipse(aBasicShape, aShapeMargin, aFrame,
2498                                    aShapeBoxRect, aWM, aContainerSize);
2499     case StyleBasicShape::Tag::Inset:
2500       return CreateInset(aBasicShape, aShapeMargin, aFrame, aShapeBoxRect, aWM,
2501                          aContainerSize);
2502   }
2503   return nullptr;
2504 }
2505 
2506 /* static */ UniquePtr<nsFloatManager::ShapeInfo>
CreateInset(const StyleBasicShape & aBasicShape,nscoord aShapeMargin,nsIFrame * aFrame,const LogicalRect & aShapeBoxRect,WritingMode aWM,const nsSize & aContainerSize)2507 nsFloatManager::ShapeInfo::CreateInset(const StyleBasicShape& aBasicShape,
2508                                        nscoord aShapeMargin, nsIFrame* aFrame,
2509                                        const LogicalRect& aShapeBoxRect,
2510                                        WritingMode aWM,
2511                                        const nsSize& aContainerSize) {
2512   // Use physical coordinates to compute inset() because the top, right,
2513   // bottom and left offsets are physical.
2514   // https://drafts.csswg.org/css-shapes-1/#funcdef-inset
2515   nsRect physicalShapeBoxRect =
2516       aShapeBoxRect.GetPhysicalRect(aWM, aContainerSize);
2517   nsRect insetRect =
2518       ShapeUtils::ComputeInsetRect(aBasicShape, physicalShapeBoxRect);
2519 
2520   nsRect logicalInsetRect = ConvertToFloatLogical(
2521       LogicalRect(aWM, insetRect, aContainerSize), aWM, aContainerSize);
2522   nscoord physicalRadii[8];
2523   bool hasRadii = ShapeUtils::ComputeInsetRadii(
2524       aBasicShape, physicalShapeBoxRect, physicalRadii);
2525 
2526   // With a zero shape-margin, we will be able to use the fast constructor.
2527   if (aShapeMargin == 0) {
2528     if (!hasRadii) {
2529       return MakeUnique<RoundedBoxShapeInfo>(logicalInsetRect,
2530                                              UniquePtr<nscoord[]>());
2531     }
2532     return MakeUnique<RoundedBoxShapeInfo>(
2533         logicalInsetRect, ConvertToFloatLogical(physicalRadii, aWM));
2534   }
2535 
2536   // With a positive shape-margin, we might still be able to use the fast
2537   // constructor. With no radii, we can build a rounded box by inflating
2538   // logicalInsetRect, and supplying aShapeMargin as the radius for all
2539   // corners.
2540   if (!hasRadii) {
2541     logicalInsetRect.Inflate(aShapeMargin);
2542     auto logicalRadii = MakeUnique<nscoord[]>(8);
2543     for (int32_t i = 0; i < 8; ++i) {
2544       logicalRadii[i] = aShapeMargin;
2545     }
2546     return MakeUnique<RoundedBoxShapeInfo>(logicalInsetRect,
2547                                            std::move(logicalRadii));
2548   }
2549 
2550   // If we have radii, and they have balanced/equal corners, we can inflate
2551   // both logicalInsetRect and all the radii and use the fast constructor.
2552   if (RoundedBoxShapeInfo::EachCornerHasBalancedRadii(physicalRadii)) {
2553     logicalInsetRect.Inflate(aShapeMargin);
2554     for (nscoord& r : physicalRadii) {
2555       r += aShapeMargin;
2556     }
2557     return MakeUnique<RoundedBoxShapeInfo>(
2558         logicalInsetRect, ConvertToFloatLogical(physicalRadii, aWM));
2559   }
2560 
2561   // With positive shape-margin and elliptical radii, we have to use the
2562   // slow constructor.
2563   nsDeviceContext* dc = aFrame->PresContext()->DeviceContext();
2564   int32_t appUnitsPerDevPixel = dc->AppUnitsPerDevPixel();
2565   return MakeUnique<RoundedBoxShapeInfo>(
2566       logicalInsetRect, ConvertToFloatLogical(physicalRadii, aWM), aShapeMargin,
2567       appUnitsPerDevPixel);
2568 }
2569 
2570 /* static */ UniquePtr<nsFloatManager::ShapeInfo>
CreateCircleOrEllipse(const StyleBasicShape & aBasicShape,nscoord aShapeMargin,nsIFrame * const aFrame,const LogicalRect & aShapeBoxRect,WritingMode aWM,const nsSize & aContainerSize)2571 nsFloatManager::ShapeInfo::CreateCircleOrEllipse(
2572     const StyleBasicShape& aBasicShape, nscoord aShapeMargin,
2573     nsIFrame* const aFrame, const LogicalRect& aShapeBoxRect, WritingMode aWM,
2574     const nsSize& aContainerSize) {
2575   // Use physical coordinates to compute the center of circle() or ellipse()
2576   // since the <position> keywords such as 'left', 'top', etc. are physical.
2577   // https://drafts.csswg.org/css-shapes-1/#funcdef-ellipse
2578   nsRect physicalShapeBoxRect =
2579       aShapeBoxRect.GetPhysicalRect(aWM, aContainerSize);
2580   nsPoint physicalCenter = ShapeUtils::ComputeCircleOrEllipseCenter(
2581       aBasicShape, physicalShapeBoxRect);
2582   nsPoint logicalCenter =
2583       ConvertToFloatLogical(physicalCenter, aWM, aContainerSize);
2584 
2585   // Compute the circle or ellipse radii.
2586   nsSize radii;
2587   if (aBasicShape.IsCircle()) {
2588     nscoord radius = ShapeUtils::ComputeCircleRadius(
2589         aBasicShape, physicalCenter, physicalShapeBoxRect);
2590     // Circles can use the three argument, math constructor for
2591     // EllipseShapeInfo.
2592     radii = nsSize(radius, radius);
2593     return MakeUnique<EllipseShapeInfo>(logicalCenter, radii, aShapeMargin);
2594   }
2595 
2596   MOZ_ASSERT(aBasicShape.IsEllipse());
2597   nsSize physicalRadii = ShapeUtils::ComputeEllipseRadii(
2598       aBasicShape, physicalCenter, physicalShapeBoxRect);
2599   LogicalSize logicalRadii(aWM, physicalRadii);
2600   radii = nsSize(logicalRadii.ISize(aWM), logicalRadii.BSize(aWM));
2601 
2602   // If radii are close to the same value, or if aShapeMargin is small
2603   // enough (as specified in css pixels), then we can use the three argument
2604   // constructor for EllipseShapeInfo, which uses math for a more efficient
2605   // method of float area computation.
2606   if (EllipseShapeInfo::ShapeMarginIsNegligible(aShapeMargin) ||
2607       EllipseShapeInfo::RadiiAreRoughlyEqual(radii)) {
2608     return MakeUnique<EllipseShapeInfo>(logicalCenter, radii, aShapeMargin);
2609   }
2610 
2611   // We have to use the full constructor for EllipseShapeInfo. This
2612   // computes the float area using a rasterization method.
2613   nsDeviceContext* dc = aFrame->PresContext()->DeviceContext();
2614   int32_t appUnitsPerDevPixel = dc->AppUnitsPerDevPixel();
2615   return MakeUnique<EllipseShapeInfo>(logicalCenter, radii, aShapeMargin,
2616                                       appUnitsPerDevPixel);
2617 }
2618 
2619 /* static */ UniquePtr<nsFloatManager::ShapeInfo>
CreatePolygon(const StyleBasicShape & aBasicShape,nscoord aShapeMargin,nsIFrame * const aFrame,const LogicalRect & aShapeBoxRect,const LogicalRect & aMarginRect,WritingMode aWM,const nsSize & aContainerSize)2620 nsFloatManager::ShapeInfo::CreatePolygon(const StyleBasicShape& aBasicShape,
2621                                          nscoord aShapeMargin,
2622                                          nsIFrame* const aFrame,
2623                                          const LogicalRect& aShapeBoxRect,
2624                                          const LogicalRect& aMarginRect,
2625                                          WritingMode aWM,
2626                                          const nsSize& aContainerSize) {
2627   // Use physical coordinates to compute each (xi, yi) vertex because CSS
2628   // represents them using physical coordinates.
2629   // https://drafts.csswg.org/css-shapes-1/#funcdef-polygon
2630   nsRect physicalShapeBoxRect =
2631       aShapeBoxRect.GetPhysicalRect(aWM, aContainerSize);
2632 
2633   // Get physical vertices.
2634   nsTArray<nsPoint> vertices =
2635       ShapeUtils::ComputePolygonVertices(aBasicShape, physicalShapeBoxRect);
2636 
2637   // Convert all the physical vertices to logical.
2638   for (nsPoint& vertex : vertices) {
2639     vertex = ConvertToFloatLogical(vertex, aWM, aContainerSize);
2640   }
2641 
2642   if (aShapeMargin == 0) {
2643     return MakeUnique<PolygonShapeInfo>(std::move(vertices));
2644   }
2645 
2646   nsRect marginRect = ConvertToFloatLogical(aMarginRect, aWM, aContainerSize);
2647 
2648   // We have to use the full constructor for PolygonShapeInfo. This
2649   // computes the float area using a rasterization method.
2650   int32_t appUnitsPerDevPixel = aFrame->PresContext()->AppUnitsPerDevPixel();
2651   return MakeUnique<PolygonShapeInfo>(std::move(vertices), aShapeMargin,
2652                                       appUnitsPerDevPixel, marginRect);
2653 }
2654 
2655 /* static */ UniquePtr<nsFloatManager::ShapeInfo>
CreateImageShape(const StyleImage & aShapeImage,float aShapeImageThreshold,nscoord aShapeMargin,nsIFrame * const aFrame,const LogicalRect & aMarginRect,WritingMode aWM,const nsSize & aContainerSize)2656 nsFloatManager::ShapeInfo::CreateImageShape(const StyleImage& aShapeImage,
2657                                             float aShapeImageThreshold,
2658                                             nscoord aShapeMargin,
2659                                             nsIFrame* const aFrame,
2660                                             const LogicalRect& aMarginRect,
2661                                             WritingMode aWM,
2662                                             const nsSize& aContainerSize) {
2663   MOZ_ASSERT(&aShapeImage == &aFrame->StyleDisplay()->mShapeOutside.AsImage(),
2664              "aFrame should be the frame that we got aShapeImage from");
2665 
2666   nsImageRenderer imageRenderer(aFrame, &aShapeImage,
2667                                 nsImageRenderer::FLAG_SYNC_DECODE_IMAGES);
2668 
2669   if (!imageRenderer.PrepareImage()) {
2670     // The image is not ready yet.  Boost its loading priority since it will
2671     // affect layout.
2672     if (imgRequestProxy* req = aShapeImage.GetImageRequest()) {
2673       req->BoostPriority(imgIRequest::CATEGORY_SIZE_QUERY);
2674     }
2675     return nullptr;
2676   }
2677 
2678   nsRect contentRect = aFrame->GetContentRect();
2679 
2680   // Create a draw target and draw shape image on it.
2681   nsDeviceContext* dc = aFrame->PresContext()->DeviceContext();
2682   int32_t appUnitsPerDevPixel = dc->AppUnitsPerDevPixel();
2683   LayoutDeviceIntSize contentSizeInDevPixels =
2684       LayoutDeviceIntSize::FromAppUnitsRounded(contentRect.Size(),
2685                                                appUnitsPerDevPixel);
2686 
2687   // Use empty CSSSizeOrRatio to force set the preferred size as the frame's
2688   // content box size.
2689   imageRenderer.SetPreferredSize(CSSSizeOrRatio(), contentRect.Size());
2690 
2691   RefPtr<gfx::DrawTarget> drawTarget =
2692       gfxPlatform::GetPlatform()->CreateOffscreenCanvasDrawTarget(
2693           contentSizeInDevPixels.ToUnknownSize(), gfx::SurfaceFormat::A8);
2694   if (!drawTarget) {
2695     return nullptr;
2696   }
2697 
2698   RefPtr<gfxContext> context = gfxContext::CreateOrNull(drawTarget);
2699   MOZ_ASSERT(context);  // already checked the target above
2700 
2701   ImgDrawResult result =
2702       imageRenderer.DrawShapeImage(aFrame->PresContext(), *context);
2703 
2704   if (result != ImgDrawResult::SUCCESS) {
2705     return nullptr;
2706   }
2707 
2708   // Retrieve the pixel image buffer to create the image shape info.
2709   RefPtr<SourceSurface> sourceSurface = drawTarget->Snapshot();
2710   RefPtr<DataSourceSurface> dataSourceSurface = sourceSurface->GetDataSurface();
2711   DataSourceSurface::ScopedMap map(dataSourceSurface, DataSourceSurface::READ);
2712 
2713   if (!map.IsMapped()) {
2714     return nullptr;
2715   }
2716 
2717   MOZ_ASSERT(sourceSurface->GetSize() == contentSizeInDevPixels.ToUnknownSize(),
2718              "Who changes the size?");
2719 
2720   nsRect marginRect = aMarginRect.GetPhysicalRect(aWM, aContainerSize);
2721 
2722   uint8_t* alphaPixels = map.GetData();
2723   int32_t stride = map.GetStride();
2724 
2725   // NOTE: ImageShapeInfo constructor does not keep a persistent copy of
2726   // alphaPixels; it's only used during the constructor to compute pixel ranges.
2727   return MakeUnique<ImageShapeInfo>(alphaPixels, stride, contentSizeInDevPixels,
2728                                     appUnitsPerDevPixel, aShapeImageThreshold,
2729                                     aShapeMargin, contentRect, marginRect, aWM,
2730                                     aContainerSize);
2731 }
2732 
2733 /* static */
ComputeEllipseLineInterceptDiff(const nscoord aShapeBoxBStart,const nscoord aShapeBoxBEnd,const nscoord aBStartCornerRadiusL,const nscoord aBStartCornerRadiusB,const nscoord aBEndCornerRadiusL,const nscoord aBEndCornerRadiusB,const nscoord aBandBStart,const nscoord aBandBEnd)2734 nscoord nsFloatManager::ShapeInfo::ComputeEllipseLineInterceptDiff(
2735     const nscoord aShapeBoxBStart, const nscoord aShapeBoxBEnd,
2736     const nscoord aBStartCornerRadiusL, const nscoord aBStartCornerRadiusB,
2737     const nscoord aBEndCornerRadiusL, const nscoord aBEndCornerRadiusB,
2738     const nscoord aBandBStart, const nscoord aBandBEnd) {
2739   // An example for the band intersecting with the top right corner of an
2740   // ellipse with writing-mode horizontal-tb.
2741   //
2742   //                             lineIntercept lineDiff
2743   //                                    |       |
2744   //  +---------------------------------|-------|-+---- aShapeBoxBStart
2745   //  |                ##########^      |       | |
2746   //  |            ##############|####  |       | |
2747   //  +---------#################|######|-------|-+---- aBandBStart
2748   //  |       ###################|######|##     | |
2749   //  |     aBStartCornerRadiusB |######|###    | |
2750   //  |    ######################|######|#####  | |
2751   //  +---#######################|<-----------><->^---- aBandBEnd
2752   //  |  ########################|##############  |
2753   //  |  ########################|##############  |---- b
2754   //  | #########################|############### |
2755   //  | ######################## v<-------------->v
2756   //  |###################### aBStartCornerRadiusL|
2757   //  |###########################################|
2758   //  |###########################################|
2759   //  |###########################################|
2760   //  |###########################################|
2761   //  | ######################################### |
2762   //  | ######################################### |
2763   //  |  #######################################  |
2764   //  |  #######################################  |
2765   //  |   #####################################   |
2766   //  |    ###################################    |
2767   //  |      ###############################      |
2768   //  |       #############################       |
2769   //  |         #########################         |
2770   //  |            ###################            |
2771   //  |                ###########                |
2772   //  +-------------------------------------------+----- aShapeBoxBEnd
2773 
2774   NS_ASSERTION(aShapeBoxBStart <= aShapeBoxBEnd, "Bad shape box coordinates!");
2775   NS_ASSERTION(aBandBStart <= aBandBEnd, "Bad band coordinates!");
2776 
2777   nscoord lineDiff = 0;
2778 
2779   // If the band intersects both the block-start and block-end corners, we
2780   // don't need to enter either branch because the correct lineDiff is 0.
2781   if (aBStartCornerRadiusB > 0 && aBandBEnd >= aShapeBoxBStart &&
2782       aBandBEnd <= aShapeBoxBStart + aBStartCornerRadiusB) {
2783     // The band intersects only the block-start corner.
2784     nscoord b = aBStartCornerRadiusB - (aBandBEnd - aShapeBoxBStart);
2785     nscoord lineIntercept =
2786         XInterceptAtY(b, aBStartCornerRadiusL, aBStartCornerRadiusB);
2787     lineDiff = aBStartCornerRadiusL - lineIntercept;
2788   } else if (aBEndCornerRadiusB > 0 &&
2789              aBandBStart >= aShapeBoxBEnd - aBEndCornerRadiusB &&
2790              aBandBStart <= aShapeBoxBEnd) {
2791     // The band intersects only the block-end corner.
2792     nscoord b = aBEndCornerRadiusB - (aShapeBoxBEnd - aBandBStart);
2793     nscoord lineIntercept =
2794         XInterceptAtY(b, aBEndCornerRadiusL, aBEndCornerRadiusB);
2795     lineDiff = aBEndCornerRadiusL - lineIntercept;
2796   }
2797 
2798   return lineDiff;
2799 }
2800 
2801 /* static */
XInterceptAtY(const nscoord aY,const nscoord aRadiusX,const nscoord aRadiusY)2802 nscoord nsFloatManager::ShapeInfo::XInterceptAtY(const nscoord aY,
2803                                                  const nscoord aRadiusX,
2804                                                  const nscoord aRadiusY) {
2805   // Solve for x in the ellipse equation (x/radiusX)^2 + (y/radiusY)^2 = 1.
2806   MOZ_ASSERT(aRadiusY > 0);
2807   return aRadiusX * std::sqrt(1 - (aY * aY) / double(aRadiusY * aRadiusY));
2808 }
2809 
2810 /* static */
ConvertToFloatLogical(const nsPoint & aPoint,WritingMode aWM,const nsSize & aContainerSize)2811 nsPoint nsFloatManager::ShapeInfo::ConvertToFloatLogical(
2812     const nsPoint& aPoint, WritingMode aWM, const nsSize& aContainerSize) {
2813   LogicalPoint logicalPoint(aWM, aPoint, aContainerSize);
2814   return nsPoint(logicalPoint.LineRelative(aWM, aContainerSize),
2815                  logicalPoint.B(aWM));
2816 }
2817 
2818 /* static */ UniquePtr<nscoord[]>
ConvertToFloatLogical(const nscoord aRadii[8],WritingMode aWM)2819 nsFloatManager::ShapeInfo::ConvertToFloatLogical(const nscoord aRadii[8],
2820                                                  WritingMode aWM) {
2821   UniquePtr<nscoord[]> logicalRadii(new nscoord[8]);
2822 
2823   // Get the physical side for line-left and line-right since border radii
2824   // are on the physical axis.
2825   Side lineLeftSide =
2826       aWM.PhysicalSide(aWM.LogicalSideForLineRelativeDir(eLineRelativeDirLeft));
2827   logicalRadii[eCornerTopLeftX] =
2828       aRadii[SideToHalfCorner(lineLeftSide, true, false)];
2829   logicalRadii[eCornerTopLeftY] =
2830       aRadii[SideToHalfCorner(lineLeftSide, true, true)];
2831   logicalRadii[eCornerBottomLeftX] =
2832       aRadii[SideToHalfCorner(lineLeftSide, false, false)];
2833   logicalRadii[eCornerBottomLeftY] =
2834       aRadii[SideToHalfCorner(lineLeftSide, false, true)];
2835 
2836   Side lineRightSide = aWM.PhysicalSide(
2837       aWM.LogicalSideForLineRelativeDir(eLineRelativeDirRight));
2838   logicalRadii[eCornerTopRightX] =
2839       aRadii[SideToHalfCorner(lineRightSide, false, false)];
2840   logicalRadii[eCornerTopRightY] =
2841       aRadii[SideToHalfCorner(lineRightSide, false, true)];
2842   logicalRadii[eCornerBottomRightX] =
2843       aRadii[SideToHalfCorner(lineRightSide, true, false)];
2844   logicalRadii[eCornerBottomRightY] =
2845       aRadii[SideToHalfCorner(lineRightSide, true, true)];
2846 
2847   if (aWM.IsLineInverted()) {
2848     // When IsLineInverted() is true, i.e. aWM is vertical-lr,
2849     // line-over/line-under are inverted from block-start/block-end. So the
2850     // relationship reverses between which corner comes first going
2851     // clockwise, and which corner is block-start versus block-end. We need
2852     // to swap the values stored in top and bottom corners.
2853     std::swap(logicalRadii[eCornerTopLeftX], logicalRadii[eCornerBottomLeftX]);
2854     std::swap(logicalRadii[eCornerTopLeftY], logicalRadii[eCornerBottomLeftY]);
2855     std::swap(logicalRadii[eCornerTopRightX],
2856               logicalRadii[eCornerBottomRightX]);
2857     std::swap(logicalRadii[eCornerTopRightY],
2858               logicalRadii[eCornerBottomRightY]);
2859   }
2860 
2861   return logicalRadii;
2862 }
2863 
2864 /* static */
MinIntervalIndexContainingY(const nsTArray<nsRect> & aIntervals,const nscoord aTargetY)2865 size_t nsFloatManager::ShapeInfo::MinIntervalIndexContainingY(
2866     const nsTArray<nsRect>& aIntervals, const nscoord aTargetY) {
2867   // Perform a binary search to find the minimum index of an interval
2868   // that contains aTargetY. If no such interval exists, return a value
2869   // equal to the number of intervals.
2870   size_t startIdx = 0;
2871   size_t endIdx = aIntervals.Length();
2872   while (startIdx < endIdx) {
2873     size_t midIdx = startIdx + (endIdx - startIdx) / 2;
2874     if (aIntervals[midIdx].ContainsY(aTargetY)) {
2875       return midIdx;
2876     }
2877     nscoord midY = aIntervals[midIdx].Y();
2878     if (midY < aTargetY) {
2879       startIdx = midIdx + 1;
2880     } else {
2881       endIdx = midIdx;
2882     }
2883   }
2884 
2885   return endIdx;
2886 }
2887 
2888 /* static */
LineEdge(const nsTArray<nsRect> & aIntervals,const nscoord aBStart,const nscoord aBEnd,bool aIsLineLeft)2889 nscoord nsFloatManager::ShapeInfo::LineEdge(const nsTArray<nsRect>& aIntervals,
2890                                             const nscoord aBStart,
2891                                             const nscoord aBEnd,
2892                                             bool aIsLineLeft) {
2893   MOZ_ASSERT(aBStart <= aBEnd,
2894              "The band's block start is greater than its block end?");
2895 
2896   // Find all the intervals whose rects overlap the aBStart to
2897   // aBEnd range, and find the most constraining inline edge
2898   // depending on the value of aLeft.
2899 
2900   // Since the intervals are stored in block-axis order, we need
2901   // to find the first interval that overlaps aBStart and check
2902   // succeeding intervals until we get past aBEnd.
2903 
2904   nscoord lineEdge = aIsLineLeft ? nscoord_MAX : nscoord_MIN;
2905 
2906   size_t intervalCount = aIntervals.Length();
2907   for (size_t i = MinIntervalIndexContainingY(aIntervals, aBStart);
2908        i < intervalCount; ++i) {
2909     // We can always get the bCoord from the intervals' mLineLeft,
2910     // since the y() coordinate is duplicated in both points in the
2911     // interval.
2912     auto& interval = aIntervals[i];
2913     nscoord bCoord = interval.Y();
2914     if (bCoord >= aBEnd) {
2915       break;
2916     }
2917     // Get the edge from the interval point indicated by aLeft.
2918     if (aIsLineLeft) {
2919       lineEdge = std::min(lineEdge, interval.X());
2920     } else {
2921       lineEdge = std::max(lineEdge, interval.XMost());
2922     }
2923   }
2924 
2925   return lineEdge;
2926 }
2927 
2928 /* static */ nsFloatManager::ShapeInfo::dfType
CalcUsedShapeMargin5X(nscoord aShapeMargin,int32_t aAppUnitsPerDevPixel)2929 nsFloatManager::ShapeInfo::CalcUsedShapeMargin5X(nscoord aShapeMargin,
2930                                                  int32_t aAppUnitsPerDevPixel) {
2931   // Our distance field has to be able to hold values equal to the
2932   // maximum shape-margin value that we care about faithfully rendering,
2933   // times 5. A 16-bit unsigned int can represent up to ~ 65K which means
2934   // we can handle a margin up to ~ 13K device pixels. That's good enough
2935   // for practical usage. Any supplied shape-margin value higher than this
2936   // maximum will be clamped.
2937   static const float MAX_MARGIN_5X_FLOAT = (float)MAX_MARGIN_5X;
2938 
2939   // Convert aShapeMargin to dev pixels, convert that into 5x-dev-pixel
2940   // space, then clamp to MAX_MARGIN_5X_FLOAT.
2941   float shapeMarginDevPixels5X =
2942       5.0f * NSAppUnitsToFloatPixels(aShapeMargin, aAppUnitsPerDevPixel);
2943   NS_WARNING_ASSERTION(shapeMarginDevPixels5X <= MAX_MARGIN_5X_FLOAT,
2944                        "shape-margin is too large and is being clamped.");
2945 
2946   // We calculate a minimum in float space, which takes care of any overflow
2947   // or infinity that may have occurred earlier from multiplication of
2948   // too-large aShapeMargin values.
2949   float usedMargin5XFloat =
2950       std::min(shapeMarginDevPixels5X, MAX_MARGIN_5X_FLOAT);
2951   return (dfType)NSToIntRound(usedMargin5XFloat);
2952 }
2953 
2954 //----------------------------------------------------------------------
2955 
~nsAutoFloatManager()2956 nsAutoFloatManager::~nsAutoFloatManager() {
2957   // Restore the old float manager in the reflow input if necessary.
2958   if (mNew) {
2959 #ifdef DEBUG
2960     if (nsBlockFrame::gNoisyFloatManager) {
2961       printf("restoring old float manager %p\n", mOld);
2962     }
2963 #endif
2964 
2965     mReflowInput.mFloatManager = mOld;
2966 
2967 #ifdef DEBUG
2968     if (nsBlockFrame::gNoisyFloatManager) {
2969       if (mOld) {
2970         mReflowInput.mFrame->ListTag(stdout);
2971         printf(": float manager %p after reflow\n", mOld);
2972         mOld->List(stdout);
2973       }
2974     }
2975 #endif
2976   }
2977 }
2978 
CreateFloatManager(nsPresContext * aPresContext)2979 void nsAutoFloatManager::CreateFloatManager(nsPresContext* aPresContext) {
2980   MOZ_ASSERT(!mNew, "Redundant call to CreateFloatManager!");
2981 
2982   // Create a new float manager and install it in the reflow
2983   // input. `Remember' the old float manager so we can restore it
2984   // later.
2985   mNew = MakeUnique<nsFloatManager>(aPresContext->PresShell(),
2986                                     mReflowInput.GetWritingMode());
2987 
2988 #ifdef DEBUG
2989   if (nsBlockFrame::gNoisyFloatManager) {
2990     printf("constructed new float manager %p (replacing %p)\n", mNew.get(),
2991            mReflowInput.mFloatManager);
2992   }
2993 #endif
2994 
2995   // Set the float manager in the existing reflow input.
2996   mOld = mReflowInput.mFloatManager;
2997   mReflowInput.mFloatManager = mNew.get();
2998 }
2999