tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

nsFloatManager.cpp (128334B)


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