Bug 930468 - Add a Moz2D version of SVGPathData::ConstructPath. r=heycam, r=Bas
☠☠ backed out by 1203ba8ffc0d ☠ ☠
authorJonathan Watt <jwatt@jwatt.org>
Thu, 24 Oct 2013 17:50:27 +0200
changeset 165799 075fc0110d9f2877cc9934b2598a50c79ab6bdaf
parent 165798 9cd10d6fb0de03ca81f23104a4f393017763c5c8
child 165800 4eaaad2e06f20b53c5229ddfb4d7f3e3a8a52c66
push id3066
push userakeybl@mozilla.com
push dateMon, 09 Dec 2013 19:58:46 +0000
treeherdermozilla-beta@a31a0dce83aa [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersheycam, Bas
bugs930468
milestone27.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 930468 - Add a Moz2D version of SVGPathData::ConstructPath. r=heycam, r=Bas
content/svg/content/src/SVGPathData.cpp
content/svg/content/src/SVGPathData.h
--- a/content/svg/content/src/SVGPathData.cpp
+++ b/content/svg/content/src/SVGPathData.cpp
@@ -2,17 +2,20 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "SVGPathData.h"
 
 #include "gfx2DGlue.h"
 #include "gfxPlatform.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/Types.h"
 #include "mozilla/gfx/Point.h"
+#include "mozilla/RefPtr.h"
 #include "nsError.h"
 #include "nsString.h"
 #include "nsSVGPathDataParser.h"
 #include "nsSVGPathGeometryElement.h" // for nsSVGMark
 #include <stdarg.h>
 #include "SVGContentUtils.h"
 #include "SVGPathSegUtils.h"
 #include "gfxContext.h"
@@ -236,26 +239,306 @@ ApproximateZeroLengthSubpathSquareCaps(c
 
   const gfxSize tinyAdvance = aCtx->DeviceToUser(gfxSize(2.0/256.0, 0.0));
 
   aCtx->MoveTo(aPoint);
   aCtx->LineTo(aPoint + gfxPoint(tinyAdvance.width, tinyAdvance.height));
   aCtx->MoveTo(aPoint);
 }
 
+static void
+ApproximateZeroLengthSubpathSquareCaps(const Point& aPoint,
+                                       DrawTarget* aDT,
+                                       PathBuilder* aPB)
+{
+  // Cairo's fixed point fractional part is 8 bits wide, so its device space
+  // coordinate granularity is 1/256 pixels. However, to prevent user space
+  // |aPoint| and |aPoint + tinyAdvance| being rounded to the same device
+  // coordinates, we double this for |tinyAdvance|:
+
+  Matrix currentTransform = aDT->GetTransform();
+  currentTransform.Invert();
+  Size tinyAdvance = currentTransform * Size(2.0/256.0, 0.0);
+
+  aPB->MoveTo(aPoint);
+  aPB->LineTo(aPoint + Point(tinyAdvance.width, tinyAdvance.height));
+  aPB->MoveTo(aPoint);
+}
+
+#define MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS_TO_DT               \
+  do {                                                                        \
+    if (capsAreSquare && !subpathHasLength && subpathContainsNonArc &&        \
+        SVGPathSegUtils::IsValidType(prevSegType) &&                          \
+        (!IsMoveto(prevSegType) ||                                            \
+         segType == PATHSEG_CLOSEPATH)) {                                     \
+      ApproximateZeroLengthSubpathSquareCaps(segStart, aDT, builder);         \
+    }                                                                         \
+  } while(0)
+
 #define MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS                     \
   do {                                                                        \
     if (capsAreSquare && !subpathHasLength && subpathContainsNonArc &&        \
         SVGPathSegUtils::IsValidType(prevSegType) &&                          \
         (!IsMoveto(prevSegType) ||                                            \
          segType == PATHSEG_CLOSEPATH)) {                                     \
       ApproximateZeroLengthSubpathSquareCaps(segStart, aCtx);                 \
     }                                                                         \
   } while(0)
 
+TemporaryRef<Path>
+SVGPathData::ConstructPath(DrawTarget *aDT,
+                           FillRule aFillRule,
+                           CapStyle aCapStyle) const
+{
+  if (mData.IsEmpty() || !IsMoveto(SVGPathSegUtils::DecodeType(mData[0]))) {
+    return nullptr; // paths without an initial moveto are invalid
+  }
+
+  RefPtr<PathBuilder> builder = aDT->CreatePathBuilder(aFillRule);
+
+  bool capsAreSquare = aCapStyle == CAP_SQUARE;
+  bool subpathHasLength = false;  // visual length
+  bool subpathContainsNonArc = 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
+
+  // Regarding cp1 and cp2: If the previous segment was a cubic bezier curve,
+  // then cp2 is its second control point. If the previous segment was a
+  // quadratic curve, then cp1 is its (only) control point.
+
+  uint32_t i = 0;
+  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;
+      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]);
+      builder->LineTo(segEnd);
+      if (!subpathHasLength) {
+        subpathHasLength = (segEnd != segStart);
+      }
+      subpathContainsNonArc = true;
+      break;
+
+    case PATHSEG_LINETO_REL:
+      segEnd = segStart + Point(mData[i], mData[i+1]);
+      builder->LineTo(segEnd);
+      if (!subpathHasLength) {
+        subpathHasLength = (segEnd != segStart);
+      }
+      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]);
+      builder->BezierTo(cp1, cp2, segEnd);
+      if (!subpathHasLength) {
+        subpathHasLength = (segEnd != segStart || segEnd != cp1 || segEnd != cp2);
+      }
+      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]);
+      builder->BezierTo(cp1, cp2, segEnd);
+      if (!subpathHasLength) {
+        subpathHasLength = (segEnd != segStart || segEnd != cp1 || segEnd != cp2);
+      }
+      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;
+      builder->BezierTo(tcp1, tcp2, segEnd);
+      if (!subpathHasLength) {
+        subpathHasLength = (segEnd != segStart || segEnd != cp1);
+      }
+      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;
+      builder->BezierTo(tcp1, tcp2, segEnd);
+      if (!subpathHasLength) {
+        subpathHasLength = (segEnd != segStart || segEnd != cp1);
+      }
+      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) {
+        segEnd += segStart;
+      }
+      if (segEnd != segStart) {
+        if (radii.x == 0.0f || radii.y == 0.0f) {
+          builder->LineTo(segEnd);
+        } else {
+          nsSVGArcConverter converter(segStart, segEnd, radii, mData[i+2],
+                                      mData[i+3] != 0, mData[i+4] != 0);
+          while (converter.GetNextSegment(&cp1, &cp2, &segEnd)) {
+            builder->BezierTo(cp1, cp2, segEnd);
+          }
+        }
+      }
+      if (!subpathHasLength) {
+        subpathHasLength = (segEnd != segStart);
+      }
+      break;
+    }
+
+    case PATHSEG_LINETO_HORIZONTAL_ABS:
+      segEnd = Point(mData[i], segStart.y);
+      builder->LineTo(segEnd);
+      if (!subpathHasLength) {
+        subpathHasLength = (segEnd != segStart);
+      }
+      subpathContainsNonArc = true;
+      break;
+
+    case PATHSEG_LINETO_HORIZONTAL_REL:
+      segEnd = segStart + Point(mData[i], 0.0f);
+      builder->LineTo(segEnd);
+      if (!subpathHasLength) {
+        subpathHasLength = (segEnd != segStart);
+      }
+      subpathContainsNonArc = true;
+      break;
+
+    case PATHSEG_LINETO_VERTICAL_ABS:
+      segEnd = Point(segStart.x, mData[i]);
+      builder->LineTo(segEnd);
+      if (!subpathHasLength) {
+        subpathHasLength = (segEnd != segStart);
+      }
+      subpathContainsNonArc = true;
+      break;
+
+    case PATHSEG_LINETO_VERTICAL_REL:
+      segEnd = segStart + Point(0.0f, mData[i]);
+      builder->LineTo(segEnd);
+      if (!subpathHasLength) {
+        subpathHasLength = (segEnd != segStart);
+      }
+      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]);
+      builder->BezierTo(cp1, cp2, segEnd);
+      if (!subpathHasLength) {
+        subpathHasLength = (segEnd != segStart || segEnd != cp1 || segEnd != cp2);
+      }
+      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]);
+      builder->BezierTo(cp1, cp2, segEnd);
+      if (!subpathHasLength) {
+        subpathHasLength = (segEnd != segStart || segEnd != cp1 || segEnd != cp2);
+      }
+      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;
+      builder->BezierTo(tcp1, tcp2, segEnd);
+      if (!subpathHasLength) {
+        subpathHasLength = (segEnd != segStart || segEnd != cp1);
+      }
+      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;
+      builder->BezierTo(tcp1, tcp2, segEnd);
+      if (!subpathHasLength) {
+        subpathHasLength = (segEnd != segStart || segEnd != cp1);
+      }
+      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
+    }
+    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");
+
+  MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS_TO_DT;
+
+  return builder->Finish();
+}
+
 void
 SVGPathData::ConstructPath(gfxContext *aCtx) const
 {
   if (mData.IsEmpty() || !IsMoveto(SVGPathSegUtils::DecodeType(mData[0]))) {
     return; // paths without an initial moveto are invalid
   }
 
   bool capsAreSquare = aCtx->CurrentLineCap() == gfxContext::LINE_CAP_SQUARE;
--- a/content/svg/content/src/SVGPathData.h
+++ b/content/svg/content/src/SVGPathData.h
@@ -6,16 +6,18 @@
 #ifndef MOZILLA_SVGPATHDATA_H__
 #define MOZILLA_SVGPATHDATA_H__
 
 #include "nsCOMPtr.h"
 #include "nsDebug.h"
 #include "nsIContent.h"
 #include "nsINode.h"
 #include "nsIWeakReferenceUtils.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/RefPtr.h"
 #include "nsSVGElement.h"
 #include "nsTArray.h"
 
 #include <string.h>
 
 class gfxContext;
 class gfxPath;
 class nsSVGPathDataParserToInternal; // IWYU pragma: keep
@@ -75,16 +77,21 @@ class SVGPathData
 {
   friend class SVGAnimatedPathSegList;
   friend class DOMSVGPathSegList;
   friend class DOMSVGPathSeg;
   friend class ::nsSVGPathDataParserToInternal;
   // nsSVGPathDataParserToInternal will not keep wrappers in sync, so consumers
   // are responsible for that!
 
+  typedef gfx::DrawTarget DrawTarget;
+  typedef gfx::Path Path;
+  typedef gfx::FillRule FillRule;
+  typedef gfx::CapStyle CapStyle;
+
 public:
   typedef const float* const_iterator;
 
   SVGPathData(){}
   ~SVGPathData(){}
 
   // Only methods that don't make/permit modification to this list are public.
   // Only our friend classes can access methods that may change us.
@@ -149,16 +156,19 @@ public:
    * Returns true, except on OOM, in which case returns false.
    */
   bool GetDistancesFromOriginToEndsOfVisibleSegments(nsTArray<double> *aArray) const;
 
   already_AddRefed<gfxPath>
   ToPath(const gfxMatrix& aMatrix) const;
 
   void ConstructPath(gfxContext *aCtx) const;
+  TemporaryRef<Path> ConstructPath(DrawTarget* aDT,
+                                   FillRule aFillRule,
+                                   CapStyle aCapStyle) const;
 
   const_iterator begin() const { return mData.Elements(); }
   const_iterator end() const { return mData.Elements() + mData.Length(); }
 
   // Access to methods that can modify objects of this type is deliberately
   // limited. This is to reduce the chances of someone modifying objects of
   // this type without taking the necessary steps to keep DOM wrappers in sync.
   // If you need wider access to these methods, consider adding a method to