Bug 1265342 Part 6b: Implement shape-margin for shape-outside: inset, for general case of shape-margin > 0. r=dholbert
authorBrad Werth <bwerth@mozilla.com>
Tue, 17 Apr 2018 11:39:50 -0700
changeset 469097 93dacd65e0093ba69fc376f0859a54be4e325388
parent 469096 8d8361873abe44ec51e097db1f463bb179e75684
child 469098 0cb6c9f7c6dbf71e5143b901de1dd3aa128f949d
push id9165
push userasasaki@mozilla.com
push dateThu, 26 Apr 2018 21:04:54 +0000
treeherdermozilla-beta@064c3804de2e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdholbert
bugs1265342
milestone61.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1265342 Part 6b: Implement shape-margin for shape-outside: inset, for general case of shape-margin > 0. r=dholbert MozReview-Commit-ID: 4pjALPSIBhI
layout/generic/nsFloatManager.cpp
--- a/layout/generic/nsFloatManager.cpp
+++ b/layout/generic/nsFloatManager.cpp
@@ -1030,74 +1030,176 @@ public:
                     const nscoord aBEnd) const override;
   nscoord BStart() const override { return mRect.y; }
   nscoord BEnd() const override { return mRect.YMost(); }
   bool IsEmpty() const override { return mRect.IsEmpty(); }
 
   void Translate(nscoord aLineLeft, nscoord aBlockStart) override
   {
     mRect.MoveBy(aLineLeft, aBlockStart);
+
+    if (mShapeMargin > 0) {
+      MOZ_ASSERT(mLogicalTopLeftCorner && mLogicalTopRightCorner &&
+                 mLogicalBottomLeftCorner && mLogicalBottomRightCorner,
+                 "If we have positive shape-margin, we should have corners.");
+      mLogicalTopLeftCorner->Translate(aLineLeft, aBlockStart);
+      mLogicalTopRightCorner->Translate(aLineLeft, aBlockStart);
+      mLogicalBottomLeftCorner->Translate(aLineLeft, aBlockStart);
+      mLogicalBottomRightCorner->Translate(aLineLeft, aBlockStart);
+    }
+  }
+
+  static bool EachCornerHasBalancedRadii(const nscoord* aRadii) {
+    return (aRadii[eCornerTopLeftX] == aRadii[eCornerTopLeftY] &&
+            aRadii[eCornerTopRightX] == aRadii[eCornerTopRightY] &&
+            aRadii[eCornerBottomLeftX] == aRadii[eCornerBottomLeftY] &&
+            aRadii[eCornerBottomRightX] == aRadii[eCornerBottomRightY]);
   }
 
 private:
   // The rect of the rounded box shape in the float manager's coordinate
   // space.
   nsRect mRect;
   // The half corner radii of the reference box. It's an nscoord[8] array
   // in the float manager's coordinate space. If there are no radii, it's
   // nullptr.
   const UniquePtr<nscoord[]> mRadii;
 
-  // A shape-margin value extends the boundaries of the float area.
+  // A shape-margin value extends the boundaries of the float area. When our
+  // first constructor is used, it is for the creation of rounded boxes that
+  // can ignore shape-margin -- either because it was specified as zero or
+  // because the box shape and radii can be inflated to account for it. When
+  // our second constructor is used, we store the shape-margin value here.
   const nscoord mShapeMargin;
+
+  // If our second constructor is called (which implies mShapeMargin > 0),
+  // we will construct EllipseShapeInfo objects for each corner. We use the
+  // float logical naming here, where LogicalTopLeftCorner means the BStart
+  // LineLeft corner, and similarly for the other corners.
+  UniquePtr<EllipseShapeInfo> mLogicalTopLeftCorner;
+  UniquePtr<EllipseShapeInfo> mLogicalTopRightCorner;
+  UniquePtr<EllipseShapeInfo> mLogicalBottomLeftCorner;
+  UniquePtr<EllipseShapeInfo> mLogicalBottomRightCorner;
 };
 
 nsFloatManager::RoundedBoxShapeInfo::RoundedBoxShapeInfo(const nsRect& aRect,
   UniquePtr<nscoord[]> aRadii,
   nscoord aShapeMargin,
   int32_t aAppUnitsPerDevPixel)
   : mRect(aRect)
   , mRadii(Move(aRadii))
   , mShapeMargin(aShapeMargin)
 {
+  MOZ_ASSERT(mShapeMargin > 0 && !EachCornerHasBalancedRadii(mRadii.get()),
+             "Slow constructor should only be used for for shape-margin > 0 "
+             "and radii with elliptical corners.");
 
+  // Before we inflate mRect by mShapeMargin, construct each of our corners.
+  // If we do it in this order, it's a bit simpler to calculate the center
+  // of each of the corners.
+  mLogicalTopLeftCorner = MakeUnique<EllipseShapeInfo>(
+    nsPoint(mRect.X() + mRadii[eCornerTopLeftX],
+            mRect.Y() + mRadii[eCornerTopLeftY]),
+    nsSize(mRadii[eCornerTopLeftX], mRadii[eCornerTopLeftY]),
+    mShapeMargin, aAppUnitsPerDevPixel);
+
+  mLogicalTopRightCorner = MakeUnique<EllipseShapeInfo>(
+    nsPoint(mRect.XMost() - mRadii[eCornerTopRightX],
+            mRect.Y() + mRadii[eCornerTopRightY]),
+    nsSize(mRadii[eCornerTopRightX], mRadii[eCornerTopRightY]),
+    mShapeMargin, aAppUnitsPerDevPixel);
+
+  mLogicalBottomLeftCorner = MakeUnique<EllipseShapeInfo>(
+    nsPoint(mRect.X() + mRadii[eCornerBottomLeftX],
+            mRect.YMost() - mRadii[eCornerBottomLeftY]),
+    nsSize(mRadii[eCornerBottomLeftX], mRadii[eCornerBottomLeftY]),
+    mShapeMargin, aAppUnitsPerDevPixel);
+
+  mLogicalBottomRightCorner = MakeUnique<EllipseShapeInfo>(
+    nsPoint(mRect.XMost() - mRadii[eCornerBottomRightX],
+            mRect.YMost() - mRadii[eCornerBottomRightY]),
+    nsSize(mRadii[eCornerBottomRightX], mRadii[eCornerBottomRightY]),
+    mShapeMargin, aAppUnitsPerDevPixel);
+
+  // Now we inflate our mRect by mShapeMargin.
+  mRect.Inflate(mShapeMargin);
 }
 
 nscoord
 nsFloatManager::RoundedBoxShapeInfo::LineLeft(const nscoord aBStart,
                                               const nscoord aBEnd) const
 {
-  if (!mRadii) {
-    return mRect.x;
+  if (mShapeMargin == 0) {
+    if (!mRadii) {
+      return mRect.x;
+    }
+
+    nscoord lineLeftDiff =
+      ComputeEllipseLineInterceptDiff(
+        mRect.y, mRect.YMost(),
+        mRadii[eCornerTopLeftX], mRadii[eCornerTopLeftY],
+        mRadii[eCornerBottomLeftX], mRadii[eCornerBottomLeftY],
+        aBStart, aBEnd);
+    return mRect.x + lineLeftDiff;
   }
 
-  nscoord lineLeftDiff =
-    ComputeEllipseLineInterceptDiff(
-      mRect.y, mRect.YMost(),
-      mRadii[eCornerTopLeftX], mRadii[eCornerTopLeftY],
-      mRadii[eCornerBottomLeftX], mRadii[eCornerBottomLeftY],
-      aBStart, aBEnd);
-  return mRect.x + lineLeftDiff;
+  MOZ_ASSERT(mLogicalTopLeftCorner && mLogicalBottomLeftCorner,
+             "If we have positive shape-margin, we should have corners.");
+
+  // Determine if aBEnd is within our top corner.
+  if (aBEnd < mLogicalTopLeftCorner->BEnd()) {
+    return mLogicalTopLeftCorner->LineLeft(aBStart, aBEnd);
+  }
+
+  // Determine if aBStart is within our bottom corner.
+  if (aBStart >= mLogicalBottomLeftCorner->BStart()) {
+    return mLogicalBottomLeftCorner->LineLeft(aBStart, aBEnd);
+  }
+
+  // Either aBStart or aBEnd or both are within the flat part of our left
+  // edge. Because we've already inflated our mRect to encompass our
+  // mShapeMargin, we can just return the edge.
+  return mRect.X();
 }
 
 nscoord
 nsFloatManager::RoundedBoxShapeInfo::LineRight(const nscoord aBStart,
                                                const nscoord aBEnd) const
 {
-  if (!mRadii) {
-    return mRect.XMost();
+  if (mShapeMargin == 0) {
+    if (!mRadii) {
+      return mRect.XMost();
+    }
+
+    nscoord lineRightDiff =
+      ComputeEllipseLineInterceptDiff(
+        mRect.y, mRect.YMost(),
+        mRadii[eCornerTopRightX], mRadii[eCornerTopRightY],
+        mRadii[eCornerBottomRightX], mRadii[eCornerBottomRightY],
+        aBStart, aBEnd);
+    return mRect.XMost() - lineRightDiff;
   }
 
-  nscoord lineRightDiff =
-    ComputeEllipseLineInterceptDiff(
-      mRect.y, mRect.YMost(),
-      mRadii[eCornerTopRightX], mRadii[eCornerTopRightY],
-      mRadii[eCornerBottomRightX], mRadii[eCornerBottomRightY],
-      aBStart, aBEnd);
-  return mRect.XMost() - lineRightDiff;
+  MOZ_ASSERT(mLogicalTopRightCorner && mLogicalBottomRightCorner,
+             "If we have positive shape-margin, we should have corners.");
+
+  // Determine if aBEnd is within our top corner.
+  if (aBEnd < mLogicalTopRightCorner->BEnd()) {
+    return mLogicalTopRightCorner->LineRight(aBStart, aBEnd);
+  }
+
+  // Determine if aBStart is within our bottom corner.
+  if (aBStart >= mLogicalBottomRightCorner->BStart()) {
+    return mLogicalBottomRightCorner->LineRight(aBStart, aBEnd);
+  }
+
+  // Either aBStart or aBEnd or both are within the flat part of our right
+  // edge. Because we've already inflated our mRect to encompass our
+  // mShapeMargin, we can just return the edge.
+  return mRect.XMost();
 }
 
 /////////////////////////////////////////////////////////////////////////////
 // PolygonShapeInfo
 //
 // Implements shape-outside: polygon().
 //
 class nsFloatManager::PolygonShapeInfo final : public nsFloatManager::ShapeInfo
@@ -2136,32 +2238,29 @@ nsFloatManager::ShapeInfo::CreateInset(
     auto logicalRadii = MakeUnique<nscoord[]>(8);
     for (int32_t i = 0; i < 8; ++i) {
       logicalRadii[i] = aShapeMargin;
     }
     return MakeUnique<RoundedBoxShapeInfo>(logicalInsetRect,
                                            Move(logicalRadii));
   }
 
-  // If we have radii, and each pair is equal, we can inflate both
-  // logicalInsetRect and all the radii and use the fast constructor.
-  if (physicalRadii[0] == physicalRadii[1] &&
-      physicalRadii[2] == physicalRadii[3] &&
-      physicalRadii[4] == physicalRadii[5] &&
-      physicalRadii[6] == physicalRadii[7]) {
+  // If we have radii, and they have balanced/equal corners, we can inflate
+  // both logicalInsetRect and all the radii and use the fast constructor.
+  if (RoundedBoxShapeInfo::EachCornerHasBalancedRadii(physicalRadii)) {
     logicalInsetRect.Inflate(aShapeMargin);
     for (nscoord& r : physicalRadii) {
       r += aShapeMargin;
     }
     return MakeUnique<RoundedBoxShapeInfo>(logicalInsetRect,
                                            ConvertToFloatLogical(physicalRadii,
                                                                  aWM));
   }
 
-  // With positive shape-margin and unequal radii pairs, we have to use the
+  // With positive shape-margin and elliptical radii, we have to use the
   // slow constructor.
   nsDeviceContext* dc = aFrame->PresContext()->DeviceContext();
   int32_t appUnitsPerDevPixel = dc->AppUnitsPerDevPixel();
   return MakeUnique<RoundedBoxShapeInfo>(logicalInsetRect,
                                          ConvertToFloatLogical(physicalRadii,
                                                                aWM),
                                          aShapeMargin, appUnitsPerDevPixel);
 }