Bug 1075399, part 4 - Don't make an exception for arcs when simulating zero length SVG subpaths. r=longsonr
authorJonathan Watt <jwatt@jwatt.org>
Fri, 03 Oct 2014 09:50:43 +0100
changeset 208554 78b5cff9b699d6ba342d8ae2affc24e9ec95e71b
parent 208553 a6976dea9d5667acaf4f5bb743655c4d122e2490
child 208555 2f7b5e239d1e9d308e5635a7c337293babec8dfc
push idunknown
push userunknown
push dateunknown
reviewerslongsonr
bugs1075399
milestone35.0a1
Bug 1075399, part 4 - Don't make an exception for arcs when simulating zero length SVG subpaths. r=longsonr
content/svg/content/src/SVGPathData.cpp
layout/reftests/svg/stroke-linecap-round-w-zero-length-segs-01.svg
layout/reftests/svg/stroke-linecap-square-w-zero-length-segs-01.svg
--- a/content/svg/content/src/SVGPathData.cpp
+++ b/content/svg/content/src/SVGPathData.cpp
@@ -257,17 +257,16 @@ ApproximateZeroLengthSubpathSquareCaps(P
              "Make the caller check for this, or check it here");
 
   // The fraction of the stroke width that we choose for the length of the
   // line is rather arbitrary, other than being chosen to meet the requirements
   // described in the comment above.
 
   Float tinyLength = aStrokeWidth / 512;
 
-  aPB->MoveTo(aPoint);
   aPB->LineTo(aPoint + Point(tinyLength, 0));
   aPB->MoveTo(aPoint);
 }
 
 static void
 ApproximateZeroLengthSubpathSquareCaps(const gfxPoint &aPoint, gfxContext *aCtx)
 {
   // Cairo's fixed point fractional part is 8 bits wide, so its device space
@@ -280,17 +279,18 @@ ApproximateZeroLengthSubpathSquareCaps(c
   aCtx->MoveTo(aPoint);
   aCtx->LineTo(aPoint + gfxPoint(tinyAdvance.width, tinyAdvance.height));
   aCtx->MoveTo(aPoint);
 }
 
 #define MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS_TO_DT               \
   do {                                                                        \
     if (!subpathHasLength && hasLineCaps && aStrokeWidth > 0 &&               \
-        subpathContainsNonArc && SVGPathSegUtils::IsValidType(prevSegType) && \
+        subpathContainsNonMoveTo &&                                           \
+        SVGPathSegUtils::IsValidType(prevSegType) &&                          \
         (!IsMoveto(prevSegType) || segType == PATHSEG_CLOSEPATH)) {           \
       ApproximateZeroLengthSubpathSquareCaps(builder, segStart, aStrokeWidth);\
     }                                                                         \
   } while(0)
 
 #define MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS                     \
   do {                                                                        \
     if (capsAreSquare && !subpathHasLength && subpathContainsNonArc &&        \
@@ -307,17 +307,17 @@ SVGPathData::BuildPath(PathBuilder* buil
                        Float aStrokeWidth) const
 {
   if (mData.IsEmpty() || !IsMoveto(SVGPathSegUtils::DecodeType(mData[0]))) {
     return nullptr; // paths without an initial moveto are invalid
   }
 
   bool hasLineCaps = aStrokeLineCap != NS_STYLE_STROKE_LINECAP_BUTT;
   bool subpathHasLength = false;  // visual length
-  bool subpathContainsNonArc = false;
+  bool subpathContainsNonMoveTo = false;
 
   uint32_t segType     = PATHSEG_UNKNOWN;
   uint32_t prevSegType = PATHSEG_UNKNOWN;
   Point pathStart(0.0, 0.0); // start point of [sub]path
   Point segStart(0.0, 0.0);
   Point segEnd;
   Point cp1, cp2;            // previous bezier's control points
   Point tcp1, tcp2;          // temporaries
@@ -330,102 +330,94 @@ SVGPathData::BuildPath(PathBuilder* buil
   while (i < mData.Length()) {
     segType = SVGPathSegUtils::DecodeType(mData[i++]);
     uint32_t argCount = SVGPathSegUtils::ArgCountForType(segType);
 
     switch (segType)
     {
     case PATHSEG_CLOSEPATH:
       // set this early to allow drawing of square caps for "M{x},{y} Z":
-      subpathContainsNonArc = true;
+      subpathContainsNonMoveTo = true;
       MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS_TO_DT;
       segEnd = pathStart;
       builder->Close();
       break;
 
     case PATHSEG_MOVETO_ABS:
       MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS_TO_DT;
       pathStart = segEnd = Point(mData[i], mData[i+1]);
       builder->MoveTo(segEnd);
       subpathHasLength = false;
-      subpathContainsNonArc = false;
       break;
 
     case PATHSEG_MOVETO_REL:
       MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS_TO_DT;
       pathStart = segEnd = segStart + Point(mData[i], mData[i+1]);
       builder->MoveTo(segEnd);
       subpathHasLength = false;
-      subpathContainsNonArc = false;
       break;
 
     case PATHSEG_LINETO_ABS:
       segEnd = Point(mData[i], mData[i+1]);
       if (segEnd != segStart) {
         subpathHasLength = true;
         builder->LineTo(segEnd);
       }
-      subpathContainsNonArc = true;
       break;
 
     case PATHSEG_LINETO_REL:
       segEnd = segStart + Point(mData[i], mData[i+1]);
       if (segEnd != segStart) {
         subpathHasLength = true;
         builder->LineTo(segEnd);
       }
-      subpathContainsNonArc = true;
       break;
 
     case PATHSEG_CURVETO_CUBIC_ABS:
       cp1 = Point(mData[i], mData[i+1]);
       cp2 = Point(mData[i+2], mData[i+3]);
       segEnd = Point(mData[i+4], mData[i+5]);
       if (segEnd != segStart || segEnd != cp1 || segEnd != cp2) {
         subpathHasLength = true;
         builder->BezierTo(cp1, cp2, segEnd);
       }
-      subpathContainsNonArc = true;
       break;
 
     case PATHSEG_CURVETO_CUBIC_REL:
       cp1 = segStart + Point(mData[i], mData[i+1]);
       cp2 = segStart + Point(mData[i+2], mData[i+3]);
       segEnd = segStart + Point(mData[i+4], mData[i+5]);
       if (segEnd != segStart || segEnd != cp1 || segEnd != cp2) {
         subpathHasLength = true;
         builder->BezierTo(cp1, cp2, segEnd);
       }
-      subpathContainsNonArc = true;
       break;
 
     case PATHSEG_CURVETO_QUADRATIC_ABS:
       cp1 = Point(mData[i], mData[i+1]);
       // Convert quadratic curve to cubic curve:
       tcp1 = segStart + (cp1 - segStart) * 2 / 3;
       segEnd = Point(mData[i+2], mData[i+3]); // set before setting tcp2!
       tcp2 = cp1 + (segEnd - cp1) / 3;
       if (segEnd != segStart || segEnd != cp1) {
         subpathHasLength = true;
         builder->BezierTo(tcp1, tcp2, segEnd);
       }
-      subpathContainsNonArc = true;
       break;
 
     case PATHSEG_CURVETO_QUADRATIC_REL:
       cp1 = segStart + Point(mData[i], mData[i+1]);
       // Convert quadratic curve to cubic curve:
       tcp1 = segStart + (cp1 - segStart) * 2 / 3;
       segEnd = segStart + Point(mData[i+2], mData[i+3]); // set before setting tcp2!
       tcp2 = cp1 + (segEnd - cp1) / 3;
       if (segEnd != segStart || segEnd != cp1) {
         subpathHasLength = true;
         builder->BezierTo(tcp1, tcp2, segEnd);
       }
-      subpathContainsNonArc = true;
       break;
 
     case PATHSEG_ARC_ABS:
     case PATHSEG_ARC_REL:
     {
       Point radii(mData[i], mData[i+1]);
       segEnd = Point(mData[i+5], mData[i+6]);
       if (segType == PATHSEG_ARC_REL) {
@@ -447,98 +439,93 @@ SVGPathData::BuildPath(PathBuilder* buil
     }
 
     case PATHSEG_LINETO_HORIZONTAL_ABS:
       segEnd = Point(mData[i], segStart.y);
       if (segEnd != segStart) {
         subpathHasLength = true;
         builder->LineTo(segEnd);
       }
-      subpathContainsNonArc = true;
       break;
 
     case PATHSEG_LINETO_HORIZONTAL_REL:
       segEnd = segStart + Point(mData[i], 0.0f);
       if (segEnd != segStart) {
         subpathHasLength = true;
         builder->LineTo(segEnd);
       }
-      subpathContainsNonArc = true;
       break;
 
     case PATHSEG_LINETO_VERTICAL_ABS:
       segEnd = Point(segStart.x, mData[i]);
       if (segEnd != segStart) {
         subpathHasLength = true;
         builder->LineTo(segEnd);
       }
-      subpathContainsNonArc = true;
       break;
 
     case PATHSEG_LINETO_VERTICAL_REL:
       segEnd = segStart + Point(0.0f, mData[i]);
       if (segEnd != segStart) {
         subpathHasLength = true;
         builder->LineTo(segEnd);
       }
-      subpathContainsNonArc = true;
       break;
 
     case PATHSEG_CURVETO_CUBIC_SMOOTH_ABS:
       cp1 = SVGPathSegUtils::IsCubicType(prevSegType) ? segStart * 2 - cp2 : segStart;
       cp2 = Point(mData[i],   mData[i+1]);
       segEnd = Point(mData[i+2], mData[i+3]);
       if (segEnd != segStart || segEnd != cp1 || segEnd != cp2) {
         subpathHasLength = true;
         builder->BezierTo(cp1, cp2, segEnd);
       }
-      subpathContainsNonArc = true;
       break;
 
     case PATHSEG_CURVETO_CUBIC_SMOOTH_REL:
       cp1 = SVGPathSegUtils::IsCubicType(prevSegType) ? segStart * 2 - cp2 : segStart;
       cp2 = segStart + Point(mData[i], mData[i+1]);
       segEnd = segStart + Point(mData[i+2], mData[i+3]);
       if (segEnd != segStart || segEnd != cp1 || segEnd != cp2) {
         subpathHasLength = true;
         builder->BezierTo(cp1, cp2, segEnd);
       }
-      subpathContainsNonArc = true;
       break;
 
     case PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS:
       cp1 = SVGPathSegUtils::IsQuadraticType(prevSegType) ? segStart * 2 - cp1 : segStart;
       // Convert quadratic curve to cubic curve:
       tcp1 = segStart + (cp1 - segStart) * 2 / 3;
       segEnd = Point(mData[i], mData[i+1]); // set before setting tcp2!
       tcp2 = cp1 + (segEnd - cp1) / 3;
       if (segEnd != segStart || segEnd != cp1) {
         subpathHasLength = true;
         builder->BezierTo(tcp1, tcp2, segEnd);
       }
-      subpathContainsNonArc = true;
       break;
 
     case PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL:
       cp1 = SVGPathSegUtils::IsQuadraticType(prevSegType) ? segStart * 2 - cp1 : segStart;
       // Convert quadratic curve to cubic curve:
       tcp1 = segStart + (cp1 - segStart) * 2 / 3;
       segEnd = segStart + Point(mData[i], mData[i+1]); // changed before setting tcp2!
       tcp2 = cp1 + (segEnd - cp1) / 3;
       if (segEnd != segStart || segEnd != cp1) {
         subpathHasLength = true;
         builder->BezierTo(tcp1, tcp2, segEnd);
       }
-      subpathContainsNonArc = true;
       break;
 
     default:
       NS_NOTREACHED("Bad path segment type");
       return nullptr; // according to spec we'd use everything up to the bad seg anyway
     }
+
+    subpathContainsNonMoveTo = segType != PATHSEG_MOVETO_ABS &&
+                               segType != PATHSEG_MOVETO_REL;
     i += argCount;
     prevSegType = segType;
     segStart = segEnd;
   }
 
   NS_ABORT_IF_FALSE(i == mData.Length(), "Very, very bad - mData corrupt");
   NS_ABORT_IF_FALSE(prevSegType == segType,
                     "prevSegType should be left at the final segType");
--- a/layout/reftests/svg/stroke-linecap-round-w-zero-length-segs-01.svg
+++ b/layout/reftests/svg/stroke-linecap-round-w-zero-length-segs-01.svg
@@ -61,18 +61,20 @@ path.coverer {
   <g transform="translate(25,75)">
     <circle cx="0" cy="0" r="8"/>
     <circle cx="50" cy="50" r="8"/>
     <circle cx="100" cy="100" r="8"/>
     <path class="circles-expected" d="M0,0 C0,0 0,0 0,0  M20,20 L30,30  M50,50 C50,50 50,50 50,50  M70,70 L80,80  M100,100 C100,100 100,100 100,100"/>
   </g>
 
   <g transform="translate(25,125)">
-    <path class="circles-not-expected" d="M0,0 A0,10 0 0 0 0,0  M20,20 L30,30  M50,50 A0,10 0 0 0 50,50  M70,70 L80,80  M100,100 A0,10 0 0 0 100,100"/>
-    <path class="coverer" d="M20,20 L30,30 M70,70 L80,80"/>
+    <circle cx="0" cy="0" r="8"/>
+    <circle cx="50" cy="50" r="8"/>
+    <circle cx="100" cy="100" r="8"/>
+    <path class="circles-expected" d="M0,0 A0,10 0 0 0 0,0  M20,20 L30,30  M50,50 A0,10 0 0 0 50,50  M70,70 L80,80  M100,100 A0,10 0 0 0 100,100"/>
   </g>
 
   <g transform="translate(25,175)">
     <circle cx="0" cy="0" r="8"/>
     <circle cx="50" cy="50" r="8"/>
     <circle cx="100" cy="100" r="8"/>
     <path class="circles-expected" d="M0,0 Z  M20,20 L30,30  M50,50 Z  M70,70 L80,80  M100,100 Z"/>
   </g>
@@ -90,18 +92,20 @@ path.coverer {
   <g transform="translate(177,75)">
     <circle cx="0" cy="0" r="8"/>
     <circle cx="50" cy="50" r="8"/>
     <circle cx="100" cy="100" r="8"/>
     <path class="circles-expected" d="M0,0 C0,0 0,0 0,0 C0,0 0,0 0,0  M20,20 L30,30  M50,50 C50,50 50,50 50,50 C50,50 50,50 50,50  M70,70 L80,80  M100,100 C100,100 100,100 100,100 C100,100 100,100 100,100"/>
   </g>
 
   <g transform="translate(175,125)">
-    <path class="circles-not-expected" d="M0,0 A0,10 0 0 0 0,0 A0,10 0 0 0 0,0  M20,20 L30,30  M50,50 A0,10 0 0 0 50,50 A0,10 0 0 0 50,50  M70,70 L80,80  M100,100 A0,10 0 0 0 100,100 A0,10 0 0 0 100,100"/>
-    <path class="coverer" d="M20,20 L30,30 M70,70 L80,80"/>
+    <circle cx="0" cy="0" r="8"/>
+    <circle cx="50" cy="50" r="8"/>
+    <circle cx="100" cy="100" r="8"/>
+    <path class="circles-expected" d="M0,0 A0,10 0 0 0 0,0 A0,10 0 0 0 0,0  M20,20 L30,30  M50,50 A0,10 0 0 0 50,50 A0,10 0 0 0 50,50  M70,70 L80,80  M100,100 A0,10 0 0 0 100,100 A0,10 0 0 0 100,100"/>
   </g>
 
   <g transform="translate(175,175)">
     <circle cx="0" cy="0" r="8"/>
     <circle cx="50" cy="50" r="8"/>
     <circle cx="100" cy="100" r="8"/>
     <path class="circles-expected" d="M0,0 Z Z  M20,20 L30,30  M50,50 Z Z  M70,70 L80,80  M100,100 Z Z"/>
   </g>
--- a/layout/reftests/svg/stroke-linecap-square-w-zero-length-segs-01.svg
+++ b/layout/reftests/svg/stroke-linecap-square-w-zero-length-segs-01.svg
@@ -61,18 +61,20 @@ path.coverer {
   <g transform="translate(25,75)">
     <rect x="-9" y="-9" width="18" height="18"/>
     <rect x="41" y="41" width="18" height="18"/>
     <rect x="91" y="91" width="18" height="18"/>
     <path class="squares-expected" d="M0,0 C0,0 0,0 0,0  M20,20 L30,30  M50,50 C50,50 50,50 50,50  M70,70 L80,80  M100,100 C100,100 100,100 100,100"/>
   </g>
 
   <g transform="translate(25,125)">
-    <path class="squares-not-expected" d="M0,0 A0,10 0 0 0 0,0  M20,20 L30,30  M50,50 A0,10 0 0 0 50,50  M70,70 L80,80  M100,100 A0,10 0 0 0 100,100"/>
-    <path class="coverer" d="M20,20 L30,30 M70,70 L80,80"/>
+    <rect x="-9" y="-9" width="18" height="18"/>
+    <rect x="41" y="41" width="18" height="18"/>
+    <rect x="91" y="91" width="18" height="18"/>
+    <path class="squares-expected" d="M0,0 A0,10 0 0 0 0,0  M20,20 L30,30  M50,50 A0,10 0 0 0 50,50  M70,70 L80,80  M100,100 A0,10 0 0 0 100,100"/>
   </g>
 
   <g transform="translate(25,175)">
     <rect x="-9" y="-9" width="18" height="18"/>
     <rect x="41" y="41" width="18" height="18"/>
     <rect x="91" y="91" width="18" height="18"/>
     <path class="squares-expected" d="M0,0 Z  M20,20 L30,30  M50,50 Z  M70,70 L80,80  M100,100 Z"/>
   </g>
@@ -90,18 +92,20 @@ path.coverer {
   <g transform="translate(177,75)">
     <rect x="-9" y="-9" width="18" height="18"/>
     <rect x="41" y="41" width="18" height="18"/>
     <rect x="91" y="91" width="18" height="18"/>
     <path class="squares-expected" d="M0,0 C0,0 0,0 0,0 C0,0 0,0 0,0  M20,20 L30,30  M50,50 C50,50 50,50 50,50 C50,50 50,50 50,50  M70,70 L80,80  M100,100 C100,100 100,100 100,100 C100,100 100,100 100,100"/>
   </g>
 
   <g transform="translate(175,125)">
-    <path class="squares-not-expected" d="M0,0 A0,10 0 0 0 0,0 A0,10 0 0 0 0,0  M20,20 L30,30  M50,50 A0,10 0 0 0 50,50 A0,10 0 0 0 50,50  M70,70 L80,80  M100,100 A0,10 0 0 0 100,100 A0,10 0 0 0 100,100"/>
-    <path class="coverer" d="M20,20 L30,30 M70,70 L80,80"/>
+    <rect x="-9" y="-9" width="18" height="18"/>
+    <rect x="41" y="41" width="18" height="18"/>
+    <rect x="91" y="91" width="18" height="18"/>
+    <path class="squares-expected" d="M0,0 A0,10 0 0 0 0,0 A0,10 0 0 0 0,0  M20,20 L30,30  M50,50 A0,10 0 0 0 50,50 A0,10 0 0 0 50,50  M70,70 L80,80  M100,100 A0,10 0 0 0 100,100 A0,10 0 0 0 100,100"/>
   </g>
 
   <g transform="translate(175,175)">
     <rect x="-9" y="-9" width="18" height="18"/>
     <rect x="41" y="41" width="18" height="18"/>
     <rect x="91" y="91" width="18" height="18"/>
     <path class="squares-expected" d="M0,0 Z Z  M20,20 L30,30  M50,50 Z Z  M70,70 L80,80  M100,100 Z Z"/>
   </g>