Bug 1265342 Part 5: Implement shape-margin for shape-outside: circle and ellipse. draft
authorBrad Werth <bwerth@mozilla.com>
Mon, 26 Feb 2018 13:13:03 -0800
changeset 767050 48eb103ff6780378e020c78e0989f01ed4a4d61b
parent 767049 cfca91346fdf97eaeb01b5331a52411f48b5073e
child 767051 c9623fe3361a19800d1d61bc6f5ef3d728a76a0b
push id102494
push userbwerth@mozilla.com
push dateTue, 13 Mar 2018 20:51:39 +0000
bugs1265342
milestone61.0a1
Bug 1265342 Part 5: Implement shape-margin for shape-outside: circle and ellipse. MozReview-Commit-ID: HeipoUTkqUE
layout/generic/nsFloatManager.cpp
--- a/layout/generic/nsFloatManager.cpp
+++ b/layout/generic/nsFloatManager.cpp
@@ -554,28 +554,30 @@ 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,
     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,
     const LogicalRect& aShapeBoxRect,
     WritingMode aWM,
     const nsSize& aContainerSize);
 
   static UniquePtr<ShapeInfo> CreatePolygon(
     const UniquePtr<StyleBasicShape>& aBasicShape,
     const LogicalRect& aShapeBoxRect,
     WritingMode aWM,
@@ -1059,22 +1061,23 @@ nsFloatManager::ImageShapeInfo::ImageSha
       // min and max store the start and end of the float area for the row
       // or column represented by this iteration of the outer loop.
       int32_t min = -1;
       int32_t max = -1;
 
       for (int32_t inner = 0; inner < innerLimit; ++inner) {
         const int32_t col = aWM.IsVertical() ? outer : inner;
         const int32_t row = aWM.IsVertical() ? inner : outer;
+        const int32_t index = col + row * aStride;
 
         // Determine if the alpha pixel at this row and column has a value
         // greater than the threshold. If it does, update our min and max values
         // to track the edges of the float area for this row or column.
         // https://drafts.csswg.org/css-shapes-1/#valdef-shape-image-threshold-number
-        const uint8_t alpha = aAlphaPixels[col + row * aStride];
+        const uint8_t alpha = aAlphaPixels[index];
         if (alpha > threshold) {
           if (min == -1) {
             min = inner;
           }
           MOZ_ASSERT(max < inner);
           max = inner;
         }
       }
@@ -1138,170 +1141,154 @@ nsFloatManager::ImageShapeInfo::ImageSha
     const CSSIntPoint dfOffset =
       CSSPixel::FromAppUnitsRounded(aContentRect.TopLeft());
     const CSSIntRect margin = CSSPixel::FromAppUnitsRounded(aMarginRect);
     const int32_t wEx = margin.width + 4;
     const int32_t hEx = margin.width + 4;
     const int32_t dfSize = wEx * hEx;
     auto df = MakeUnique<dfType[]>(dfSize);
 
+    const int32_t outerLimit = aWM.IsVertical() ? wEx : hEx;
+    const int32_t innerLimit = aWM.IsVertical() ? hEx : wEx;
+
     // First pass setting difference field, top-to-bottom, three cases:
     // 1) Expanded region pixel: set to MAX_MARGIN_5X.
     // 2) Image pixel with alpha greater than threshold: set to 0.
     // 3) Other pixel: set to minimum backward-looking neighborhood
     //                 distance value, computed with 5-7-11 chamfer.
-    for (int32_t index = 0; index < dfSize; ++index) {
-      const int32_t col = aWM.IsVertical() ? index / hEx : index % wEx;
-      const int32_t row = aWM.IsVertical() ? index % hEx : index / wEx;
 
-      // Handle our three cases, in order.
-      if (col < 2 ||
-          col >= wEx - 2 ||
-          row < 2 ||
-          row >= hEx - 2) {
-        // Case 1: Expanded pixel.
-        df[index] = MAX_MARGIN_5X;
-      } else if (col >= (dfOffset.x + 2) &&
-                 col < (dfOffset.x + 2 + w) &&
-                 row >= (dfOffset.y + 2) &&
-                 row < (dfOffset.y + 2 + h) &&
-                 aAlphaPixels[col - (dfOffset.x + 2) +
-                              (row - (dfOffset.y + 2)) * aStride] > threshold) {
-        // Case 2: Image pixel that is opaque.
-        df[index] = 0;
-      } else {
-        // Case 3: Other pixel.
+    // Scan the pixels in a double loop. For horizontal writing modes, we do
+    // this row by row, from top to bottom. For vertical writing modes, we do
+    // column by column, from left to right. We define the two loops
+    // generically, then figure out the rows and cols within the inner loop.
+    for (int32_t outer = 0; outer < outerLimit; ++outer) {
+      for (int32_t inner = 0; inner < innerLimit; ++inner) {
+        const int32_t col = aWM.IsVertical() ? outer : inner;
+        const int32_t row = aWM.IsVertical() ? inner : outer;
+        const int32_t index = col + row * wEx;
 
-        // Backward-looking neighborhood distance from target pixel X
-        // with chamfer 5-7-11 looks like:
-        //
-        // +--+--+--+--+--+
-        // |  |11|  |11|  |
-        // +--+--+--+--+--+
-        // |11| 7| 5| 7|11|
-        // +--+--+--+--+--+
-        // |  | 5| X|  |  |
-        // +--+--+--+--+--+
-        //
-        // X should be set to the minimum of MAX_MARGIN_5X and the
-        // values of all of the numbered neighbors summed with the
-        // value in that chamfer cell.
-        df[index] = std::min<dfType>(MAX_MARGIN_5X,
-                    std::min<dfType>(df[index - (wEx * 2) - 1] + 11,
-                    std::min<dfType>(df[index - (wEx * 2) + 1] + 11,
-                    std::min<dfType>(df[index - wEx - 2] + 11,
-                    std::min<dfType>(df[index - wEx - 1] + 7,
-                    std::min<dfType>(df[index - wEx] + 5,
-                    std::min<dfType>(df[index - wEx + 1] + 7,
-                    std::min<dfType>(df[index - wEx + 2] + 11,
-                                     df[index - 1] + 5))))))));
+        // Handle our three cases, in order.
+        if (col < 2 ||
+            col >= wEx - 2 ||
+            row < 2 ||
+            row >= hEx - 2) {
+          // Case 1: Expanded pixel.
+          df[index] = MAX_MARGIN_5X;
+        } else if (col >= (dfOffset.x + 2) &&
+                   col < (dfOffset.x + 2 + w) &&
+                   row >= (dfOffset.y + 2) &&
+                   row < (dfOffset.y + 2 + h) &&
+                   aAlphaPixels[col - (dfOffset.x + 2) +
+                                (row - (dfOffset.y + 2)) * aStride] > threshold) {
+          // Case 2: Image pixel that is opaque.
+          df[index] = 0;
+        } else {
+          // Case 3: Other pixel.
+
+          // Backward-looking neighborhood distance from target pixel X
+          // with chamfer 5-7-11 looks like:
+          //
+          // +--+--+--+--+--+
+          // |  |11|  |11|  |
+          // +--+--+--+--+--+
+          // |11| 7| 5| 7|11|
+          // +--+--+--+--+--+
+          // |  | 5| X|  |  |
+          // +--+--+--+--+--+
+          //
+          // X should be set to the minimum of MAX_MARGIN_5X and the
+          // values of all of the numbered neighbors summed with the
+          // value in that chamfer cell.
+          df[index] = std::min<dfType>(MAX_MARGIN_5X,
+                      std::min<dfType>(df[index - (wEx * 2) - 1] + 11,
+                      std::min<dfType>(df[index - (wEx * 2) + 1] + 11,
+                      std::min<dfType>(df[index - wEx - 2] + 11,
+                      std::min<dfType>(df[index - wEx - 1] + 7,
+                      std::min<dfType>(df[index - wEx] + 5,
+                      std::min<dfType>(df[index - wEx + 1] + 7,
+                      std::min<dfType>(df[index - wEx + 2] + 11,
+                                       df[index - 1] + 5))))))));
+        }
       }
     }
 
     // Okay, time for the second pass. This pass is bottom-to-top.
     // All of our opaque pixels have been set to 0, and all of our
     // expanded pixels have been set to MAX_MARGIN_5X. Other pixels
     // have been set to some value between those two (inclusive) but
     // this hasn't yet taken into account the neighbors below them.
     // This time we reverse iterate so we can apply the backward-
     // looking chamfer.
 
-    // This time, only two cases:
-    // 1) Expanded region pixel: skip.
-    // 2) Other pixel: set to minimum backward-looking neighborhood
-    //                 distance value, computed with 5-7-11 chamfer,
-    //                 then check against the usedMargin5X threshold.
+    // This time, we constrain our outer and inner loop to ignore
+    // the expanded region pixels. For each pixel we iterate, we
+    // set the df value to the minimum backward-looking neighborhood
+    // distance value, computed with 5-7-11 chamfer. We also check
+    // each df value against the usedMargin5X threshold, and use that
+    // to set the min and max values for that block axis value.
 
     // At the end of each row (or column in vertical writing modes),
     // if any of the other pixels had a value less than usedMargin5X,
     // we create an interval.
-
-    // Set min and max in preparation for the first row.
-    min = dfSize;
-    max = -1;
-
-    for (int32_t index = dfSize - 1; index >= 0; --index) {
-      const int32_t col = aWM.IsVertical() ? index / hEx : index % wEx;
-      const int32_t row = aWM.IsVertical() ? index % hEx : index / wEx;
-      const int32_t curr = aWM.IsVertical() ? row : col;
+    for (int32_t outer = outerLimit - 3; outer >= 2; --outer) {
+      // Set min and max in preparation for this row or column.
+      int32_t min = outerLimit;
+      int32_t max = -1;
 
-      // Check our two cases, in order.
-      // For expanded pixels, we can skip whole rows and columns of
-      // iteration, depending on the writing mode.
-      if (col < 2 || col >= wEx - 2) {
-        if (aWM.IsVertical()) {
-          index -= (hEx - 1);
-        }
-        continue;
-      }
-      if (row < 2 || row >= hEx - 2) {
-        if (!aWM.IsVertical()) {
-          index -= (wEx - 1);
-        }
-        continue;
-      }
-
-      // If we've gotten this far, we're in Case 2: Other pixel.
+      for (int32_t inner = innerLimit - 3; inner >= 2; --inner) {
+        const int32_t col = aWM.IsVertical() ? outer : inner;
+        const int32_t row = aWM.IsVertical() ? inner : outer;
+        const int32_t index = col + row * wEx;
 
-      // Only apply the chamfer calculation if the df value is not
-      // already 0, since the chamfer can only reduce the value.
-      if (df[index]) {
-        // Forward-looking neighborhood distance from target pixel X
-        // with chamfer 5-7-11 looks like:
-        //
-        // +--+--+--+--+--+
-        // |  |  | X| 5|  |
-        // +--+--+--+--+--+
-        // |11| 7| 5| 7|11|
-        // +--+--+--+--+--+
-        // |  |11|  |11|  |
-        // +--+--+--+--+--+
-        //
-        // X should be set to the minimum of its current value and
-        // the values of all of the numbered neighbors summed with
-        // the value in that chamfer cell.
-        df[index] = std::min<dfType>(df[index],
-                    std::min<dfType>(df[index + (wEx * 2) + 1] + 11,
-                    std::min<dfType>(df[index + (wEx * 2) - 1] + 11,
-                    std::min<dfType>(df[index + wEx + 2] + 11,
-                    std::min<dfType>(df[index + wEx + 1] + 7,
-                    std::min<dfType>(df[index + wEx] + 5,
-                    std::min<dfType>(df[index + wEx - 1] + 7,
-                    std::min<dfType>(df[index + wEx - 2] + 11,
-                                     df[index + 1] + 5))))))));
-      }
+        // Only apply the chamfer calculation if the df value is not
+        // already 0, since the chamfer can only reduce the value.
+        if (df[index]) {
+          // Forward-looking neighborhood distance from target pixel X
+          // with chamfer 5-7-11 looks like:
+          //
+          // +--+--+--+--+--+
+          // |  |  | X| 5|  |
+          // +--+--+--+--+--+
+          // |11| 7| 5| 7|11|
+          // +--+--+--+--+--+
+          // |  |11|  |11|  |
+          // +--+--+--+--+--+
+          //
+          // X should be set to the minimum of its current value and
+          // the values of all of the numbered neighbors summed with
+          // the value in that chamfer cell.
+          df[index] = std::min<dfType>(df[index],
+                      std::min<dfType>(df[index + (wEx * 2) + 1] + 11,
+                      std::min<dfType>(df[index + (wEx * 2) - 1] + 11,
+                      std::min<dfType>(df[index + wEx + 2] + 11,
+                      std::min<dfType>(df[index + wEx + 1] + 7,
+                      std::min<dfType>(df[index + wEx] + 5,
+                      std::min<dfType>(df[index + wEx - 1] + 7,
+                      std::min<dfType>(df[index + wEx - 2] + 11,
+                                       df[index + 1] + 5))))))));
+        }
 
-      // Finally, we can check the df value and see if its less than
-      // or equal to the usedMargin5X value.
-      if (df[index] <= usedMargin5X) {
-        if (max == -1) {
-          max = curr;
-        }
-        if (min > curr) {
-          min = curr;
+        // Finally, we can check the df value and see if its less than
+        // or equal to the usedMargin5X value.
+        if (df[index] <= usedMargin5X) {
+          if (max == -1) {
+            max = inner;
+          }
+          MOZ_ASSERT(min > inner);
+          min = inner;
         }
       }
 
-      // At the end of a row (or column if vertical).
-      if (curr == 2) {
-        // If we found something, create an interval.
-        if (max != -1) {
-          // Supply a zero offset for this interval, because our
-          // col and row are calculated from the margin rect.
-          // We likewise need to subtract 2 from both col and row
-          // because those indices are relative to the expanded
-          // pixel area.
-          CreateInterval(min, max, col - 2, row - 2, nsPoint(),
-                         aWM, aContainerSize);
-        }
-
-        // Reset min and max for the next row or column.
-        min = dfSize;
-        max = -1;
-      }
+      // Supply a zero offset for this interval, because our
+      // col and row are calculated from the margin rect.
+      // We likewise need to subtract 2 from min, max, and outer
+      // because those indices are relative to the expanded
+      // pixel area.
+      CreateInterval(min - 2, max - 2, outer - 2, nsPoint(),
+                     aWM, aContainerSize);
     }
 
     if (!aWM.IsVerticalRL()) {
       // Because we assembled our intervals on the bottom-up pass,
       // they are reversed for most writing modes. Reverse them to
       // keep the array sorted on the block direction.
       mIntervals.Reverse();
     }
@@ -1517,20 +1504,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 =
+        ::ResolveToDefiniteSize(mFrame->StyleDisplay()->mShapeMargin,
+                                aContainerSize.width);
       // 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,
+                                               shapeBoxRect, aWM,
                                                aContainerSize);
       break;
     }
   }
 
   MOZ_ASSERT(mShapeInfo,
              "All shape-outside values except none should have mShapeInfo!");
 
@@ -1691,26 +1682,28 @@ nsFloatManager::ShapeInfo::CreateShapeBo
   return MakeUnique<RoundedBoxShapeInfo>(logicalShapeBoxRect,
                                          ConvertToFloatLogical(physicalRadii,
                                                                aWM));
 }
 
 /* static */ UniquePtr<nsFloatManager::ShapeInfo>
 nsFloatManager::ShapeInfo::CreateBasicShape(
   const UniquePtr<StyleBasicShape>& aBasicShape,
+  nscoord aShapeMargin,
   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,
+                                   aShapeBoxRect, aWM,
                                    aContainerSize);
     case StyleBasicShapeType::Inset:
       return CreateInset(aBasicShape, aShapeBoxRect, aWM, aContainerSize);
   }
   return nullptr;
 }
 
 /* static */ UniquePtr<nsFloatManager::ShapeInfo>
@@ -1743,16 +1736,17 @@ nsFloatManager::ShapeInfo::CreateInset(
   return MakeUnique<RoundedBoxShapeInfo>(logicalInsetRect,
                                          ConvertToFloatLogical(physicalRadii,
                                                                aWM));
 }
 
 /* static */ UniquePtr<nsFloatManager::ShapeInfo>
 nsFloatManager::ShapeInfo::CreateCircleOrEllipse(
   const UniquePtr<StyleBasicShape>& aBasicShape,
+  nscoord aShapeMargin,
   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 =
@@ -1763,24 +1757,25 @@ 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);
-    radii = nsSize(radius, radius);
+    radii = nsSize(radius + aShapeMargin, radius + aShapeMargin);
   } 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));
+    radii = nsSize(logicalRadii.ISize(aWM) + aShapeMargin,
+                   logicalRadii.BSize(aWM) + aShapeMargin);
   }
 
   return MakeUnique<EllipseShapeInfo>(logicalCenter, radii);
 }
 
 /* static */ UniquePtr<nsFloatManager::ShapeInfo>
 nsFloatManager::ShapeInfo::CreatePolygon(
   const UniquePtr<StyleBasicShape>& aBasicShape,