Bug 1265342 Part 5a: Implement shape-margin for shape-outside: circle and ellipse (ellipse only for shape-margin: 0). draft
authorBrad Werth <bwerth@mozilla.com>
Mon, 26 Feb 2018 13:13:03 -0800
changeset 787950 26e6bb2ebd8612506a7052601686cb952c733b81
parent 787949 1e6b11d7440889cfd5a80a1b1d45e04f634b92a2
child 787951 007cf6da59bc29b0209af30d6455207065d7a456
push id107854
push userbwerth@mozilla.com
push dateWed, 25 Apr 2018 18:14:35 +0000
bugs1265342
milestone61.0a1
Bug 1265342 Part 5a: Implement shape-margin for shape-outside: circle and ellipse (ellipse only for shape-margin: 0). MozReview-Commit-ID: HeipoUTkqUE
layout/generic/nsFloatManager.cpp
--- a/layout/generic/nsFloatManager.cpp
+++ b/layout/generic/nsFloatManager.cpp
@@ -554,28 +554,32 @@ public:
   static UniquePtr<ShapeInfo> CreateShapeBox(
     nsIFrame* const aFrame,
     const LogicalRect& aShapeBoxRect,
     WritingMode aWM,
     const nsSize& aContainerSize);
 
   static UniquePtr<ShapeInfo> CreateBasicShape(
     const UniquePtr<StyleBasicShape>& aBasicShape,
+    nscoord aShapeMargin,
+    nsIFrame* const aFrame,
     const LogicalRect& aShapeBoxRect,
     WritingMode aWM,
     const nsSize& aContainerSize);
 
   static UniquePtr<ShapeInfo> CreateInset(
     const UniquePtr<StyleBasicShape>& aBasicShape,
     const LogicalRect& aShapeBoxRect,
     WritingMode aWM,
     const nsSize& aContainerSize);
 
   static UniquePtr<ShapeInfo> CreateCircleOrEllipse(
     const UniquePtr<StyleBasicShape>& aBasicShape,
+    nscoord aShapeMargin,
+    nsIFrame* const aFrame,
     const LogicalRect& aShapeBoxRect,
     WritingMode aWM,
     const nsSize& aContainerSize);
 
   static UniquePtr<ShapeInfo> CreatePolygon(
     const UniquePtr<StyleBasicShape>& aBasicShape,
     const LogicalRect& aShapeBoxRect,
     WritingMode aWM,
@@ -705,66 +709,162 @@ nsFloatManager::RoundedBoxShapeInfo::Lin
 /////////////////////////////////////////////////////////////////////////////
 // EllipseShapeInfo
 //
 // Implements shape-outside: circle() and shape-outside: ellipse().
 //
 class nsFloatManager::EllipseShapeInfo final : public nsFloatManager::ShapeInfo
 {
 public:
+  // Construct the float area using math to calculate the shape boundary.
+  // This is the fast path and should be used when shape-margin is negligible,
+  // or when the two values of aRadii are roughly equal. Those two conditions
+  // are defined by ShapeMarginIsNegligible() and RadiiAreRoughlyEqual(). In
+  // those cases, we can conveniently represent the entire float area using
+  // an ellipse.
   EllipseShapeInfo(const nsPoint& aCenter,
-                   const nsSize& aRadii)
-    : mCenter(aCenter)
-    , mRadii(aRadii)
-  {}
+                   const nsSize& aRadii,
+                   nscoord aShapeMargin);
+
+  // Construct the float area using rasterization to calculate the shape
+  // boundary. This constructor accounts for the fact that applying
+  // 'shape-margin' to an ellipse produces a shape that is not mathematically
+  // representable as an ellipse.
+  EllipseShapeInfo(const nsPoint& aCenter,
+                   const nsSize& aRadii,
+                   nscoord aShapeMargin,
+                   int32_t aAppUnitsPerDevPixel);
 
+  static bool ShapeMarginIsNegligible(nscoord aShapeMargin) {
+    // For now, only return true for a shape-margin of 0. In the future, if
+    // we want to enable use of the fast-path constructor more often, this
+    // limit could be increased;
+    static const nscoord SHAPE_MARGIN_NEGLIGIBLE_MAX(0);
+    return aShapeMargin <= SHAPE_MARGIN_NEGLIGIBLE_MAX;
+  }
+
+  static bool RadiiAreRoughlyEqual(const nsSize& aRadii) {
+    // For now, only return true when we are exactly equal. In the future, if
+    // we want to enable use of the fast-path constructor more often, this
+    // could be generalized to allow radii that are in some close proportion
+    // to each other.
+    return aRadii.width == aRadii.height;
+  }
   nscoord LineLeft(const nscoord aBStart,
                    const nscoord aBEnd) const override;
   nscoord LineRight(const nscoord aBStart,
                     const nscoord aBEnd) const override;
-  nscoord BStart() const override { return mCenter.y - mRadii.height; }
-  nscoord BEnd() const override { return mCenter.y + mRadii.height; }
-  bool IsEmpty() const override { return mRadii.IsEmpty(); };
+  nscoord BStart() const override {
+    return mCenter.y - mRadii.height - mShapeMargin;
+  }
+  nscoord BEnd() const override {
+    return mCenter.y + mRadii.height + mShapeMargin;
+  }
+  bool IsEmpty() const override {
+    return mRadii.IsEmpty();
+  }
 
   void Translate(nscoord aLineLeft, nscoord aBlockStart) override
   {
     mCenter.MoveBy(aLineLeft, aBlockStart);
+
+    for (nsRect& interval : mIntervals) {
+      interval.MoveBy(aLineLeft, aBlockStart);
+    }
   }
 
 private:
   // The position of the center of the ellipse. The coordinate space is the
   // same as FloatInfo::mRect.
   nsPoint mCenter;
   // The radii of the ellipse in app units. The width and height represent
   // the line-axis and block-axis radii of the ellipse.
   nsSize mRadii;
+  // The shape-margin of the ellipse in app units. If this value is greater
+  // than zero, then we calculate the bounds of the ellipse + margin using
+  // numerical methods and store the values in mIntervals.
+  nscoord mShapeMargin;
+
+  // An interval is slice of the float area defined by this EllipseShapeInfo.
+  // Each interval is a rectangle that is one pixel deep in the block
+  // axis. The values are stored as block edges in the y coordinates,
+  // and inline edges as the x coordinates.
+
+  // The intervals are stored in ascending order on y.
+  nsTArray<nsRect> mIntervals;
 };
 
+nsFloatManager::EllipseShapeInfo::EllipseShapeInfo(const nsPoint& aCenter,
+                                                   const nsSize& aRadii,
+                                                   nscoord aShapeMargin)
+  : mCenter(aCenter)
+  , mRadii(aRadii)
+  , mShapeMargin(0) // We intentionally ignore the value of aShapeMargin here.
+{
+  MOZ_ASSERT(RadiiAreRoughlyEqual(aRadii) ||
+             ShapeMarginIsNegligible(aShapeMargin),
+             "This constructor should only be called when margin is "
+             "negligible or radii are roughly equal.");
+
+  // We add aShapeMargin into the radii, and we earlier stored a mShapeMargin
+  // of zero.
+  mRadii.width += aShapeMargin;
+  mRadii.height += aShapeMargin;
+}
+
+nsFloatManager::EllipseShapeInfo::EllipseShapeInfo(const nsPoint& aCenter,
+                                                   const nsSize& aRadii,
+                                                   nscoord aShapeMargin,
+                                                   int32_t aAppUnitsPerDevPixel)
+  : mCenter(aCenter)
+  , mRadii(aRadii)
+  , mShapeMargin(aShapeMargin)
+{
+  if (RadiiAreRoughlyEqual(aRadii) || ShapeMarginIsNegligible(aShapeMargin)) {
+    // Mimic the behavior of the simple constructor, by adding aShapeMargin
+    // into the radii, and then storing mShapeMargin of zero.
+    mRadii.width += mShapeMargin;
+    mRadii.height += mShapeMargin;
+    mShapeMargin = 0;
+    return;
+  }
+
+  NS_ERROR("shape-margin > 0 not yet implemented for ellipse.");
+}
+
 nscoord
 nsFloatManager::EllipseShapeInfo::LineLeft(const nscoord aBStart,
                                            const nscoord aBEnd) const
 {
-  nscoord lineLeftDiff =
-    ComputeEllipseLineInterceptDiff(BStart(), BEnd(),
-                                    mRadii.width, mRadii.height,
-                                    mRadii.width, mRadii.height,
-                                    aBStart, aBEnd);
-  return mCenter.x - mRadii.width + lineLeftDiff;
+  if (mShapeMargin == 0) {
+    nscoord lineLeftDiff =
+      ComputeEllipseLineInterceptDiff(BStart(), BEnd(),
+                                      mRadii.width, mRadii.height,
+                                      mRadii.width, mRadii.height,
+                                      aBStart, aBEnd);
+    return mCenter.x - mRadii.width + lineLeftDiff;
+  }
+  NS_ERROR("shape-margin > 0 not yet implemented for ellipse.");
+  return 0;
 }
 
 nscoord
 nsFloatManager::EllipseShapeInfo::LineRight(const nscoord aBStart,
                                             const nscoord aBEnd) const
 {
-  nscoord lineRightDiff =
-    ComputeEllipseLineInterceptDiff(BStart(), BEnd(),
-                                    mRadii.width, mRadii.height,
-                                    mRadii.width, mRadii.height,
-                                    aBStart, aBEnd);
-  return mCenter.x + mRadii.width - lineRightDiff;
+  if (mShapeMargin == 0) {
+    nscoord lineRightDiff =
+      ComputeEllipseLineInterceptDiff(BStart(), BEnd(),
+                                      mRadii.width, mRadii.height,
+                                      mRadii.width, mRadii.height,
+                                      aBStart, aBEnd);
+    return mCenter.x + mRadii.width - lineRightDiff;
+  }
+  NS_ERROR("shape-margin > 0 not yet implemented for ellipse.");
+  return 0;
 }
 
 /////////////////////////////////////////////////////////////////////////////
 // PolygonShapeInfo
 //
 // Implements shape-outside: polygon().
 //
 class nsFloatManager::PolygonShapeInfo final : public nsFloatManager::ShapeInfo
@@ -1554,20 +1654,24 @@ nsFloatManager::FloatInfo::FloatInfo(nsI
         ShapeInfo::ComputeShapeBoxRect(shapeOutside, mFrame, aMarginRect, aWM);
       mShapeInfo = ShapeInfo::CreateShapeBox(mFrame, shapeBoxRect, aWM,
                                              aContainerSize);
       break;
     }
 
     case StyleShapeSourceType::Shape: {
       const UniquePtr<StyleBasicShape>& basicShape = shapeOutside.GetBasicShape();
+      nscoord shapeMargin = nsLayoutUtils::ResolveToLength<true>(
+        mFrame->StyleDisplay()->mShapeMargin,
+        LogicalSize(aWM, aContainerSize).ISize(aWM));
       // Initialize <shape-box>'s reference rect.
       LogicalRect shapeBoxRect =
         ShapeInfo::ComputeShapeBoxRect(shapeOutside, mFrame, aMarginRect, aWM);
-      mShapeInfo = ShapeInfo::CreateBasicShape(basicShape, shapeBoxRect, aWM,
+      mShapeInfo = ShapeInfo::CreateBasicShape(basicShape, shapeMargin, mFrame,
+                                               shapeBoxRect, aWM,
                                                aContainerSize);
       break;
     }
   }
 
   MOZ_ASSERT(mShapeInfo,
              "All shape-outside values except none should have mShapeInfo!");
 
@@ -1728,26 +1832,29 @@ nsFloatManager::ShapeInfo::CreateShapeBo
   return MakeUnique<RoundedBoxShapeInfo>(logicalShapeBoxRect,
                                          ConvertToFloatLogical(physicalRadii,
                                                                aWM));
 }
 
 /* static */ UniquePtr<nsFloatManager::ShapeInfo>
 nsFloatManager::ShapeInfo::CreateBasicShape(
   const UniquePtr<StyleBasicShape>& aBasicShape,
+  nscoord aShapeMargin,
+  nsIFrame* const aFrame,
   const LogicalRect& aShapeBoxRect,
   WritingMode aWM,
   const nsSize& aContainerSize)
 {
   switch (aBasicShape->GetShapeType()) {
     case StyleBasicShapeType::Polygon:
       return CreatePolygon(aBasicShape, aShapeBoxRect, aWM, aContainerSize);
     case StyleBasicShapeType::Circle:
     case StyleBasicShapeType::Ellipse:
-      return CreateCircleOrEllipse(aBasicShape, aShapeBoxRect, aWM,
+      return CreateCircleOrEllipse(aBasicShape, aShapeMargin, aFrame,
+                                   aShapeBoxRect, aWM,
                                    aContainerSize);
     case StyleBasicShapeType::Inset:
       return CreateInset(aBasicShape, aShapeBoxRect, aWM, aContainerSize);
   }
   return nullptr;
 }
 
 /* static */ UniquePtr<nsFloatManager::ShapeInfo>
@@ -1780,16 +1887,18 @@ nsFloatManager::ShapeInfo::CreateInset(
   return MakeUnique<RoundedBoxShapeInfo>(logicalInsetRect,
                                          ConvertToFloatLogical(physicalRadii,
                                                                aWM));
 }
 
 /* static */ UniquePtr<nsFloatManager::ShapeInfo>
 nsFloatManager::ShapeInfo::CreateCircleOrEllipse(
   const UniquePtr<StyleBasicShape>& aBasicShape,
+  nscoord aShapeMargin,
+  nsIFrame* const aFrame,
   const LogicalRect& aShapeBoxRect,
   WritingMode aWM,
   const nsSize& aContainerSize)
 {
   // Use physical coordinates to compute the center of circle() or ellipse()
   // since the <position> keywords such as 'left', 'top', etc. are physical.
   // https://drafts.csswg.org/css-shapes-1/#funcdef-ellipse
   nsRect physicalShapeBoxRect =
@@ -1800,27 +1909,44 @@ nsFloatManager::ShapeInfo::CreateCircleO
     ConvertToFloatLogical(physicalCenter, aWM, aContainerSize);
 
   // Compute the circle or ellipse radii.
   nsSize radii;
   StyleBasicShapeType type = aBasicShape->GetShapeType();
   if (type == StyleBasicShapeType::Circle) {
     nscoord radius = ShapeUtils::ComputeCircleRadius(aBasicShape, physicalCenter,
                                                      physicalShapeBoxRect);
+    // Circles can use the three argument, math constructor for
+    // EllipseShapeInfo.
     radii = nsSize(radius, radius);
-  } else {
-    MOZ_ASSERT(type == StyleBasicShapeType::Ellipse);
-    nsSize physicalRadii =
-      ShapeUtils::ComputeEllipseRadii(aBasicShape, physicalCenter,
-                                      physicalShapeBoxRect);
-    LogicalSize logicalRadii(aWM, physicalRadii);
-    radii = nsSize(logicalRadii.ISize(aWM), logicalRadii.BSize(aWM));
+    return MakeUnique<EllipseShapeInfo>(logicalCenter, radii, aShapeMargin);
   }
 
-  return MakeUnique<EllipseShapeInfo>(logicalCenter, radii);
+  MOZ_ASSERT(type == StyleBasicShapeType::Ellipse);
+  nsSize physicalRadii =
+    ShapeUtils::ComputeEllipseRadii(aBasicShape, physicalCenter,
+                                    physicalShapeBoxRect);
+  LogicalSize logicalRadii(aWM, physicalRadii);
+  radii = nsSize(logicalRadii.ISize(aWM), logicalRadii.BSize(aWM));
+
+  // If radii are close to the same value, or if aShapeMargin is small
+  // enough (as specified in css pixels), then we can use the three argument
+  // constructor for EllipseShapeInfo, which uses math for a more efficient
+  // method of float area computation.
+  if (EllipseShapeInfo::ShapeMarginIsNegligible(aShapeMargin) ||
+      EllipseShapeInfo::RadiiAreRoughlyEqual(radii)) {
+    return MakeUnique<EllipseShapeInfo>(logicalCenter, radii, aShapeMargin);
+  }
+
+  // We have to use the full constructor for EllipseShapeInfo. This
+  // computes the float area using a rasterization method.
+  nsDeviceContext* dc = aFrame->PresContext()->DeviceContext();
+  int32_t appUnitsPerDevPixel = dc->AppUnitsPerDevPixel();
+  return MakeUnique<EllipseShapeInfo>(logicalCenter, radii, aShapeMargin,
+                                      appUnitsPerDevPixel);
 }
 
 /* static */ UniquePtr<nsFloatManager::ShapeInfo>
 nsFloatManager::ShapeInfo::CreatePolygon(
   const UniquePtr<StyleBasicShape>& aBasicShape,
   const LogicalRect& aShapeBoxRect,
   WritingMode aWM,
   const nsSize& aContainerSize)