Bug 1265342 Part 6b: Implement shape-margin for shape-outside: inset, for general case of shape-margin > 0. draft
authorBrad Werth <bwerth@mozilla.com>
Tue, 17 Apr 2018 11:39:50 -0700
changeset 787955 cd2ea0ddb67a5d4efcbbca0aa83544c82631ed3e
parent 787954 70a84de16cc8a89ffa4256dc6485d24df3f28afe
child 787956 52390d12e68722ea260604bdeb4337fb2f6df47b
push id107854
push userbwerth@mozilla.com
push dateWed, 25 Apr 2018 18:14:35 +0000
bugs1265342
milestone61.0a1
Bug 1265342 Part 6b: Implement shape-margin for shape-outside: inset, for general case of shape-margin > 0. 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);
 }