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