Bug 1303094 - remove the StrokeRect path for drawing dotted borders. r=mstange
authorLee Salzman <lsalzman@mozilla.com>
Fri, 24 Feb 2017 17:02:46 -0500
changeset 373926 87cfe603d44d3f82e5af3eefaf5944fd128e591a
parent 373925 a932a0a9cffe540eb552d662ad5dd033bdfd43e5
child 373927 39be2cc943a7c3b48104811065ae0768de8eeaf6
push id10863
push userjlorenzo@mozilla.com
push dateMon, 06 Mar 2017 23:02:23 +0000
treeherdermozilla-aurora@0931190cd725 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmstange
bugs1303094
milestone54.0a1
Bug 1303094 - remove the StrokeRect path for drawing dotted borders. r=mstange MozReview-Commit-ID: 7zooPqCocco
layout/painting/nsCSSRenderingBorders.cpp
layout/painting/nsCSSRenderingBorders.h
layout/reftests/border-dotted/border-dotted-radius.html
--- a/layout/painting/nsCSSRenderingBorders.cpp
+++ b/layout/painting/nsCSSRenderingBorders.cpp
@@ -672,17 +672,18 @@ nsCSSBorderRenderer::GetSideClipSubPath(
   builder->LineTo(start[1]);
   builder->Close();
   return builder->Finish();
 }
 
 Point
 nsCSSBorderRenderer::GetStraightBorderPoint(mozilla::Side aSide,
                                             Corner aCorner,
-                                            bool* aIsUnfilled)
+                                            bool* aIsUnfilled,
+                                            Float aDotOffset)
 {
   // Calculate the end point of the side for dashed/dotted border, that is also
   // the end point of the corner curve.  The point is specified by aSide and
   // aCorner. (e.g. eSideTop and C_TL means the left end of border-top)
   //
   //
   //  aCorner        aSide
   //         +--------------------
@@ -731,16 +732,25 @@ nsCSSBorderRenderer::GetStraightBorderPo
                                  : NEXT_SIDE(aSide);
   uint8_t otherStyle = mBorderStyles[otherSide];
   Float otherBorderWidth = mBorderWidths[otherSide];
   Size radius = mBorderRadii[aCorner];
   if (IsZeroSize(radius)) {
     radius.width = 0.0f;
     radius.height = 0.0f;
   }
+  if (style == NS_STYLE_BORDER_STYLE_DOTTED) {
+    // Offset the dot's location along the side toward the corner by a
+    // multiple of its width.
+    if (isHorizontal) {
+      P.x -= signs[0] * aDotOffset * borderWidth;
+    } else {
+      P.y -= signs[1] * aDotOffset * borderWidth;
+    }
+  }
   if (style == NS_STYLE_BORDER_STYLE_DOTTED &&
       otherStyle == NS_STYLE_BORDER_STYLE_DOTTED) {
     if (borderWidth == otherBorderWidth) {
       if (radius.width < borderWidth / 2.0f &&
           radius.height < borderWidth / 2.0f) {
         // Two dots are merged into one and placed at the corner.
         //
         //  borderWidth / 2.0
@@ -1762,19 +1772,20 @@ nsCSSBorderRenderer::SetupDashedOptions(
   aDash[0] = fullDash;
   aDash[1] = fullDash;
 
   if (style == NS_STYLE_BORDER_STYLE_DASHED && fullDash > 1.0f) {
     if (!fullStart) {
       // Draw half segments on both ends.
       aStrokeOptions->mDashOffset = halfDash;
     }
-  } else if (isCorner) {
+  } else if (style != NS_STYLE_BORDER_STYLE_DOTTED && isCorner) {
     // If side ends with filled full segment, corner should start with unfilled
-    // full segment.
+    // full segment. Not needed for dotted corners, as they overlap one dot with
+    // the side's end.
     //
     //     corner            side
     //   ------------>|<---------------------------
     //                |
     //          __+---+---+---+---+---+---+---+---+
     //       _+-  |   |###|###|   |   |###|###|   |
     //     /##|   |   |###|###|   |   |###|###|   |
     //    +####|   |  |###|###|   |   |###|###|   |
@@ -1848,18 +1859,20 @@ nsCSSBorderRenderer::DrawDashedOrDottedS
   if (mBorderStyles[aSide] == NS_STYLE_BORDER_STYLE_DOTTED &&
       borderWidth > 2.0f) {
     DrawDottedSideSlow(aSide);
     return;
   }
 
   nscolor borderColor = mBorderColors[aSide];
   bool ignored;
-  Point start = GetStraightBorderPoint(aSide, GetCCWCorner(aSide), &ignored);
-  Point end = GetStraightBorderPoint(aSide, GetCWCorner(aSide), &ignored);
+  // Get the start and end points of the side, ensuring that any dot origins get
+  // pushed outward to account for stroking.
+  Point start = GetStraightBorderPoint(aSide, GetCCWCorner(aSide), &ignored, 0.5f);
+  Point end = GetStraightBorderPoint(aSide, GetCWCorner(aSide), &ignored, 0.5f);
   if (borderWidth < 2.0f) {
     // Round start to draw dot on each pixel.
     if (IsHorizontalSide(aSide)) {
       start.x = round(start.x);
     } else {
       start.y = round(start.y);
     }
   }
@@ -1868,19 +1881,54 @@ nsCSSBorderRenderer::DrawDashedOrDottedS
   if (borderLength < 0.0f) {
     return;
   }
 
   StrokeOptions strokeOptions(borderWidth);
   Float dash[2];
   SetupDashedOptions(&strokeOptions, dash, aSide, borderLength, false);
 
+  // For dotted sides that can merge with their prior dotted sides, advance the
+  // dash offset to measure the distance around the combined path. This prevents
+  // two dots from bunching together at a corner.
+  mozilla::Side mergeSide = aSide;
+  while (IsCornerMergeable(GetCCWCorner(mergeSide))) {
+    mergeSide = PREV_SIDE(mergeSide);
+    // If we looped all the way around, measure starting at the top side, since
+    // we need to pick a fixed location to start measuring distance from still.
+    if (mergeSide == aSide) {
+      mergeSide = eSideTop;
+      break;
+    }
+  }
+  while (mergeSide != aSide) {
+    // Measure the length of the merged side starting from a possibly unmergeable
+    // corner up to the merged corner. A merged corner effectively has no border
+    // radius, so we can just use the cheaper AtCorner to find the end point.
+    Float mergeLength =
+      GetBorderLength(mergeSide,
+                      GetStraightBorderPoint(mergeSide, GetCCWCorner(mergeSide), &ignored, 0.5f),
+                      mOuterRect.AtCorner(GetCWCorner(mergeSide)));
+    // Add in the merged side length. Also offset the dash progress by an extra
+    // dot's width to avoid drawing a dot that would overdraw where the merged side
+    // would have ended in a gap, i.e. O_O_
+    //                                    O
+    strokeOptions.mDashOffset += mergeLength + borderWidth;
+    mergeSide = NEXT_SIDE(mergeSide);
+  }
+
+  DrawOptions drawOptions;
+  if (mBorderStyles[aSide] == NS_STYLE_BORDER_STYLE_DOTTED) {
+    drawOptions.mAntialiasMode = AntialiasMode::NONE;
+  }
+
   mDrawTarget->StrokeLine(start, end,
                           ColorPattern(ToDeviceColor(borderColor)),
-                          strokeOptions);
+                          strokeOptions,
+                          drawOptions);
 }
 
 void
 nsCSSBorderRenderer::DrawDottedSideSlow(mozilla::Side aSide)
 {
   // Draw each circles separately for dotted with borderWidth > 2.0.
   // Dashed line with CapStyle::ROUND doesn't render perfect circles.
 
@@ -2330,18 +2378,21 @@ nsCSSBorderRenderer::DrawDashedOrDottedC
       DrawDashedCornerSlow(aSide, aCorner);
     }
     return;
   }
 
   nscolor borderColor = mBorderColors[aSide];
   Point points[4];
   bool ignored;
-  points[0] = GetStraightBorderPoint(sideH, aCorner, &ignored);
-  points[3] = GetStraightBorderPoint(sideV, aCorner, &ignored);
+  // Get the start and end points of the corner arc, ensuring that any dot
+  // origins get pushed backwards towards the edges of the corner rect to
+  // account for stroking.
+  points[0] = GetStraightBorderPoint(sideH, aCorner, &ignored, -0.5f);
+  points[3] = GetStraightBorderPoint(sideV, aCorner, &ignored, -0.5f);
   // Round points to draw dot on each pixel.
   if (borderWidthH < 2.0f) {
     points[0].x = round(points[0].x);
   }
   if (borderWidthV < 2.0f) {
     points[3].y = round(points[3].y);
   }
   points[1] = points[0];
@@ -3208,39 +3259,16 @@ nsCSSBorderRenderer::DrawBorders()
     Rect rect = mOuterRect;
     rect.Deflate(mBorderWidths[0] / 2.0);
     mDrawTarget->StrokeRect(rect, color, strokeOptions);
     return;
   }
 
   if (allBordersSame &&
       mCompositeColors[0] == nullptr &&
-      allBordersSameWidth &&
-      mBorderStyles[0] == NS_STYLE_BORDER_STYLE_DOTTED &&
-      mBorderWidths[0] < 3 &&
-      mNoBorderRadius &&
-      !mAvoidStroke)
-  {
-    // Very simple case. We draw this rectangular dotted borner without
-    // antialiasing. The dots should be pixel aligned.
-    Rect rect = mOuterRect;
-    rect.Deflate(mBorderWidths[0] / 2.0);
-    Float dash = mBorderWidths[0];
-    strokeOptions.mDashPattern = &dash;
-    strokeOptions.mDashLength = 1;
-    strokeOptions.mDashOffset = 0.5f * dash;
-    DrawOptions drawOptions;
-    drawOptions.mAntialiasMode = AntialiasMode::NONE;
-    mDrawTarget->StrokeRect(rect, color, strokeOptions, drawOptions);
-    return;
-  }
-
-
-  if (allBordersSame &&
-      mCompositeColors[0] == nullptr &&
       mBorderStyles[0] == NS_STYLE_BORDER_STYLE_SOLID &&
       !mAvoidStroke &&
       !mNoBorderRadius)
   {
     // Relatively simple case.
     gfxRect outerRect = ThebesRect(mOuterRect);
     RoundedRect borderInnerRect(outerRect, mBorderRadii);
     borderInnerRect.Deflate(mBorderWidths[eSideTop],
--- a/layout/painting/nsCSSRenderingBorders.h
+++ b/layout/painting/nsCSSRenderingBorders.h
@@ -175,17 +175,18 @@ private:
   // This code needs to make sure that the individual pieces
   // don't ever (mathematically) overlap; the pixel overlap
   // is taken care of by the ADD compositing.
   already_AddRefed<Path> GetSideClipSubPath(mozilla::Side aSide);
 
   // Return start or end point for dashed/dotted side
   Point GetStraightBorderPoint(mozilla::Side aSide,
                                mozilla::Corner aCorner,
-                               bool* aIsUnfilled);
+                               bool* aIsUnfilled,
+                               Float aDotOffset = 0.0f);
 
   // Return bezier control points for the outer and the inner curve for given
   // corner
   void GetOuterAndInnerBezier(Bezier* aOuterBezier,
                               Bezier* aInnerBezier,
                               mozilla::Corner aCorner);
 
   // Given a set of sides to fill and a color, do so in the fastest way.
--- a/layout/reftests/border-dotted/border-dotted-radius.html
+++ b/layout/reftests/border-dotted/border-dotted-radius.html
@@ -24,16 +24,23 @@ td {
   border-style: dotted;
   border-radius: 24px;
   border-color: black;
   height: 120px;
   width: 120px;
   box-sizing: border-box;
 }
 
+/* Bug 1303094 - Disable unreliable 1px and 2px tests. */
+.no-fill {
+  border-style: solid;
+}
+.no-unfill {
+  border-style: none;
+}
 .A {
   border-width: 1px;
 }
 .B {
   border-width: 2px;
 }
 .C {
   border-width: 6px;
@@ -56,25 +63,25 @@ td {
 .I {
   border-width: 60px;
 }
     </style>
   </head>
   <body>
     <div class="box">
       <table>
-        <tr><td class="A"></td><td class="B"></td><td class="C"></td></tr>
+        <tr><td class="A no-fill"></td><td class="B no-fill"></td><td class="C"></td></tr>
         <tr><td class="D"></td><td class="E"></td><td class="F"></td></tr>
         <tr><td class="G"></td><td class="H"></td><td class="I"></td></tr>
       </table>
       <img class="mask" src="border-dotted-radius-filled-mask.png">
     </div>
     <div class="box">
       <table>
-        <tr><td class="A"></td><td class="B"></td><td class="C"></td></tr>
+        <tr><td class="A no-unfill"></td><td class="B no-unfill"></td><td class="C"></td></tr>
         <tr><td class="D"></td><td class="E"></td><td class="F"></td></tr>
         <tr><td class="G"></td><td class="H"></td><td class="I"></td></tr>
       </table>
       <img class="mask" src="border-dotted-radius-unfilled-mask.png">
     </div>
   </body>
 </html>