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 344899 87cfe603d44d3f82e5af3eefaf5944fd128e591a
parent 344898 a932a0a9cffe540eb552d662ad5dd033bdfd43e5
child 344900 39be2cc943a7c3b48104811065ae0768de8eeaf6
push id31420
push userphilringnalda@gmail.com
push dateSat, 25 Feb 2017 18:35:21 +0000
treeherdermozilla-central@a08ec245fa24 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmstange
bugs1303094
milestone54.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 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>