Bug 1265342 Part 5a: Implement shape-margin for shape-outside: circle and ellipse (ellipse only for shape-margin: 0). r=dholbert
authorBrad Werth <bwerth@mozilla.com>
Mon, 26 Feb 2018 13:13:03 -0800
changeset 415559 c24818678398ed0d911a9d20958673e17a6b8899
parent 415558 546464551e6411c46b85203a43375ad205827bbb
child 415560 a672de34a369105c2d453d34146fb877911abd61
push id33900
push userdluca@mozilla.com
push dateThu, 26 Apr 2018 04:51:04 +0000
treeherdermozilla-central@76f35d0ecaa6 [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 5a: Implement shape-margin for shape-outside: circle and ellipse (ellipse only for shape-margin: 0). r=dholbert 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)