Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / third_party / skia / src / core / SkStroke.cpp
index 1e4ae79..c562c2e 100644 (file)
@@ -9,6 +9,48 @@
 #include "SkGeometry.h"
 #include "SkPath.h"
 
+#if QUAD_STROKE_APPROXIMATION
+
+    enum {
+        kTangent_RecursiveLimit,
+        kCubic_RecursiveLimit,
+        kQuad_RecursiveLimit
+    };
+
+    // quads with extreme widths (e.g. (0,1) (1,6) (0,3) width=5e7) recurse to point of failure
+    // largest seen for normal cubics : 5, 26
+    // largest seen for normal quads : 11
+    static const int kRecursiveLimits[] = { 5*3, 26*3, 11*3 };  // 3x limits seen in practical tests
+
+    SK_COMPILE_ASSERT(SK_ARRAY_COUNT(kRecursiveLimits) == kQuad_RecursiveLimit + 1,
+            recursive_limits_mismatch);
+
+    #ifdef SK_DEBUG  // enables tweaking these values at runtime from SampleApp
+        bool gDebugStrokerErrorSet = false;
+        SkScalar gDebugStrokerError;
+
+        int gMaxRecursion[SK_ARRAY_COUNT(kRecursiveLimits)] = { 0 };
+    #endif
+    #ifndef DEBUG_QUAD_STROKER
+        #define DEBUG_QUAD_STROKER 0
+    #endif
+
+    #if DEBUG_QUAD_STROKER
+        /* Enable to show the decisions made in subdividing the curve -- helpful when the resulting
+           stroke has more than the optimal number of quadratics and lines */
+        #define STROKER_RESULT(resultType, depth, quadPts, format, ...) \
+                SkDebugf("[%d] %s " format "\n", depth, __FUNCTION__, __VA_ARGS__), \
+                SkDebugf("  " #resultType " t=(%g,%g)\n", quadPts->fStartT, quadPts->fEndT), \
+                resultType
+        #define STROKER_DEBUG_PARAMS(...) , __VA_ARGS__
+    #else
+        #define STROKER_RESULT(resultType, depth, quadPts, format, ...) \
+                resultType
+        #define STROKER_DEBUG_PARAMS(...)
+    #endif
+
+#endif
+
 #define kMaxQuadSubdivide   5
 #define kMaxCubicSubdivide  7
 
@@ -16,6 +58,7 @@ static inline bool degenerate_vector(const SkVector& v) {
     return !SkPoint::CanNormalize(v.fX, v.fY);
 }
 
+#if !QUAD_STROKE_APPROXIMATION
 static inline bool normals_too_curvy(const SkVector& norm0, SkVector& norm1) {
     /*  root2/2 is a 45-degree angle
         make this constant bigger for more subdivisions (but not >= 1)
@@ -43,6 +86,7 @@ static inline bool normals_too_pinchy(const SkVector& norm0, SkVector& norm1) {
     SkScalar dot = SkPoint::DotProduct(norm0, norm1);
     return dot <= kTooPinchyNormalDotProd;
 }
+#endif
 
 static bool set_normal_unitnormal(const SkPoint& before, const SkPoint& after,
                                   SkScalar radius,
@@ -67,12 +111,60 @@ static bool set_normal_unitnormal(const SkVector& vec,
 }
 
 ///////////////////////////////////////////////////////////////////////////////
+#if QUAD_STROKE_APPROXIMATION
+
+struct SkQuadConstruct {    // The state of the quad stroke under construction.
+    SkPoint fQuad[3];       // the stroked quad parallel to the original curve
+    SkPoint fTangentStart;  // a point tangent to fQuad[0]
+    SkPoint fTangentEnd;    // a point tangent to fQuad[2]
+    SkScalar fStartT;       // a segment of the original curve
+    SkScalar fMidT;         //              "
+    SkScalar fEndT;         //              "
+    bool fStartSet;         // state to share common points across structs
+    bool fEndSet;           //                     "
+
+    // return false if start and end are too close to have a unique middle
+    bool init(SkScalar start, SkScalar end) {
+        fStartT = start;
+        fMidT = (start + end) * SK_ScalarHalf;
+        fEndT = end;
+        fStartSet = fEndSet = false;
+        return fStartT < fMidT && fMidT < fEndT;
+    }
+
+    bool initWithStart(SkQuadConstruct* parent) {
+        if (!init(parent->fStartT, parent->fMidT)) {
+            return false;
+        }
+        fQuad[0] = parent->fQuad[0];
+        fTangentStart = parent->fTangentStart;
+        fStartSet = true;
+        return true;
+    }
+
+    bool initWithEnd(SkQuadConstruct* parent) {
+        if (!init(parent->fMidT, parent->fEndT)) {
+            return false;
+        }
+        fQuad[2] = parent->fQuad[2];
+        fTangentEnd = parent->fTangentEnd;
+        fEndSet = true;
+        return true;
+   }
+};
+#endif
 
 class SkPathStroker {
 public:
+#if QUAD_STROKE_APPROXIMATION
+    SkPathStroker(const SkPath& src,
+                  SkScalar radius, SkScalar miterLimit, SkScalar error, SkPaint::Cap cap,
+                  SkPaint::Join join);
+#else
     SkPathStroker(const SkPath& src,
                   SkScalar radius, SkScalar miterLimit, SkPaint::Cap cap,
                   SkPaint::Join join);
+#endif
 
     void moveTo(const SkPoint&);
     void lineTo(const SkPoint&);
@@ -87,6 +179,9 @@ public:
     }
 
 private:
+#if QUAD_STROKE_APPROXIMATION
+    SkScalar    fError;
+#endif
     SkScalar    fRadius;
     SkScalar    fInvMiterLimit;
 
@@ -102,6 +197,67 @@ private:
     SkPath  fInner, fOuter; // outer is our working answer, inner is temp
     SkPath  fExtra;         // added as extra complete contours
 
+#if QUAD_STROKE_APPROXIMATION
+    enum StrokeType {
+        kOuter_StrokeType = 1,      // use sign-opposite values later to flip perpendicular axis
+        kInner_StrokeType = -1
+    } fStrokeType;
+
+    enum ResultType {
+        kSplit_ResultType,          // the caller should split the quad stroke in two
+        kDegenerate_ResultType,     // the caller should add a line
+        kQuad_ResultType,           // the caller should (continue to try to) add a quad stroke
+        kNormalError_ResultType,    // the cubic's normal couldn't be computed -- abort
+    };
+
+    enum ReductionType {
+        kPoint_ReductionType,       // all curve points are practically identical
+        kLine_ReductionType,        // the control point is on the line between the ends
+        kQuad_ReductionType,        // the control point is outside the line between the ends
+        kDegenerate_ReductionType,  // the control point is on the line but outside the ends
+        kDegenerate2_ReductionType, // two control points are on the line but outside ends (cubic)
+        kDegenerate3_ReductionType, // three areas of max curvature found (for cubic)
+    };
+
+    enum IntersectRayType {
+        kCtrlPt_RayType,
+        kResultType_RayType,
+    };
+
+    int fRecursionDepth;            // track stack depth to abort if numerics run amok
+    bool fFoundTangents;            // do less work until tangents meet (cubic)
+
+    void addDegenerateLine(const SkQuadConstruct* );
+    ReductionType CheckCubicLinear(const SkPoint cubic[4], SkPoint reduction[3],
+                                   const SkPoint** tanPtPtr);
+    ReductionType CheckQuadLinear(const SkPoint quad[3], SkPoint* reduction);
+    ResultType compareQuadCubic(const SkPoint cubic[4], SkQuadConstruct* );
+    ResultType compareQuadQuad(const SkPoint quad[3], SkQuadConstruct* );
+    bool cubicMidOnLine(const SkPoint cubic[4], const SkQuadConstruct* ) const;
+    bool cubicPerpRay(const SkPoint cubic[4], SkScalar t, SkPoint* tPt, SkPoint* onPt,
+                      SkPoint* tangent) const;
+    bool cubicQuadEnds(const SkPoint cubic[4], SkQuadConstruct* );
+    bool cubicQuadMid(const SkPoint cubic[4], const SkQuadConstruct* , SkPoint* mid) const;
+    bool cubicStroke(const SkPoint cubic[4], SkQuadConstruct* );
+    void init(StrokeType strokeType, SkQuadConstruct* , SkScalar tStart, SkScalar tEnd);
+    ResultType intersectRay(SkQuadConstruct* , IntersectRayType  STROKER_DEBUG_PARAMS(int) ) const;
+    bool ptInQuadBounds(const SkPoint quad[3], const SkPoint& pt) const;
+    void quadPerpRay(const SkPoint quad[3], SkScalar t, SkPoint* tPt, SkPoint* onPt,
+                     SkPoint* tangent) const;
+    bool quadStroke(const SkPoint quad[3], SkQuadConstruct* );
+    void setCubicEndNormal(const SkPoint cubic[4],
+                           const SkVector& normalAB, const SkVector& unitNormalAB,
+                           SkVector* normalCD, SkVector* unitNormalCD);
+    void setQuadEndNormal(const SkPoint quad[3],
+                          const SkVector& normalAB, const SkVector& unitNormalAB,
+                          SkVector* normalBC, SkVector* unitNormalBC);
+    void setRayPts(const SkPoint& tPt, SkVector* dxy, SkPoint* onPt, SkPoint* tangent) const;
+    static bool SlightAngle(SkQuadConstruct* );
+    ResultType strokeCloseEnough(const SkPoint stroke[3], const SkPoint ray[2],
+                                 SkQuadConstruct*  STROKER_DEBUG_PARAMS(int depth) ) const;
+    ResultType tangentsMeet(const SkPoint cubic[4], SkQuadConstruct* );
+#endif
+
     void    finishContour(bool close, bool isLine);
     void    preJoinTo(const SkPoint&, SkVector* normal, SkVector* unitNormal,
                       bool isLine);
@@ -109,6 +265,7 @@ private:
                        const SkVector& unitNormal);
 
     void    line_to(const SkPoint& currPt, const SkVector& normal);
+#if !QUAD_STROKE_APPROXIMATION
     void    quad_to(const SkPoint pts[3],
                     const SkVector& normalAB, const SkVector& unitNormalAB,
                     SkVector* normalBC, SkVector* unitNormalBC,
@@ -117,6 +274,7 @@ private:
                     const SkVector& normalAB, const SkVector& unitNormalAB,
                     SkVector* normalCD, SkVector* unitNormalCD,
                     int subDivide);
+#endif
 };
 
 ///////////////////////////////////////////////////////////////////////////////
@@ -187,9 +345,15 @@ void SkPathStroker::finishContour(bool close, bool currIsLine) {
 
 ///////////////////////////////////////////////////////////////////////////////
 
+#if QUAD_STROKE_APPROXIMATION
+SkPathStroker::SkPathStroker(const SkPath& src,
+                             SkScalar radius, SkScalar miterLimit, SkScalar error,
+                             SkPaint::Cap cap, SkPaint::Join join)
+#else
 SkPathStroker::SkPathStroker(const SkPath& src,
                              SkScalar radius, SkScalar miterLimit,
                              SkPaint::Cap cap, SkPaint::Join join)
+#endif
         : fRadius(radius) {
 
     /*  This is only used when join is miter_join, but we initialize it here
@@ -216,7 +380,20 @@ SkPathStroker::SkPathStroker(const SkPath& src,
     // 3x for result == inner + outer + join (swag)
     // 1x for inner == 'wag' (worst contour length would be better guess)
     fOuter.incReserve(src.countPoints() * 3);
+    fOuter.setIsVolatile(true);
     fInner.incReserve(src.countPoints());
+    fInner.setIsVolatile(true);
+#if QUAD_STROKE_APPROXIMATION
+#ifdef SK_DEBUG
+    if (!gDebugStrokerErrorSet) {
+        gDebugStrokerError = error;
+    }
+    fError = gDebugStrokerError;
+#else
+    fError = error;
+#endif
+    fRecursionDepth = 0;
+#endif
 }
 
 void SkPathStroker::moveTo(const SkPoint& pt) {
@@ -243,6 +420,7 @@ void SkPathStroker::lineTo(const SkPoint& currPt) {
     this->postJoinTo(currPt, normal, unitNormal);
 }
 
+#if !QUAD_STROKE_APPROXIMATION
 void SkPathStroker::quad_to(const SkPoint pts[3],
                       const SkVector& normalAB, const SkVector& unitNormalAB,
                       SkVector* normalBC, SkVector* unitNormalBC,
@@ -278,6 +456,203 @@ void SkPathStroker::quad_to(const SkPoint pts[3],
                         pts[2].fX - normalBC->fX, pts[2].fY - normalBC->fY);
     }
 }
+#endif
+
+#if QUAD_STROKE_APPROXIMATION
+void SkPathStroker::setQuadEndNormal(const SkPoint quad[3], const SkVector& normalAB,
+        const SkVector& unitNormalAB, SkVector* normalBC, SkVector* unitNormalBC) {
+    if (!set_normal_unitnormal(quad[1], quad[2], fRadius, normalBC, unitNormalBC)) {
+        *normalBC = normalAB;
+        *unitNormalBC = unitNormalAB;
+    }
+}
+
+void SkPathStroker::setCubicEndNormal(const SkPoint cubic[4], const SkVector& normalAB,
+        const SkVector& unitNormalAB, SkVector* normalCD, SkVector* unitNormalCD) {
+    SkVector    ab = cubic[1] - cubic[0];
+    SkVector    cd = cubic[3] - cubic[2];
+
+    bool    degenerateAB = degenerate_vector(ab);
+    bool    degenerateCD = degenerate_vector(cd);
+
+    if (degenerateAB && degenerateCD) {
+        goto DEGENERATE_NORMAL;
+    }
+
+    if (degenerateAB) {
+        ab = cubic[2] - cubic[0];
+        degenerateAB = degenerate_vector(ab);
+    }
+    if (degenerateCD) {
+        cd = cubic[3] - cubic[1];
+        degenerateCD = degenerate_vector(cd);
+    }
+    if (degenerateAB || degenerateCD) {
+DEGENERATE_NORMAL:
+        *normalCD = normalAB;
+        *unitNormalCD = unitNormalAB;
+        return;
+    }
+    SkAssertResult(set_normal_unitnormal(cd, fRadius, normalCD, unitNormalCD));
+}
+
+void SkPathStroker::init(StrokeType strokeType, SkQuadConstruct* quadPts, SkScalar tStart,
+        SkScalar tEnd) {
+    fStrokeType = strokeType;
+    fFoundTangents = false;
+    quadPts->init(tStart, tEnd);
+}
+
+// returns the distance squared from the point to the line
+static SkScalar pt_to_line(const SkPoint& pt, const SkPoint& lineStart, const SkPoint& lineEnd) {
+    SkVector dxy = lineEnd - lineStart;
+    if (degenerate_vector(dxy)) {
+        return pt.distanceToSqd(lineStart);
+    }
+    SkVector ab0 = pt - lineStart;
+    SkScalar numer = dxy.dot(ab0);
+    SkScalar denom = dxy.dot(dxy);
+    SkScalar t = numer / denom;
+    SkPoint hit;
+    hit.fX = lineStart.fX * (1 - t) + lineEnd.fX * t;
+    hit.fY = lineStart.fY * (1 - t) + lineEnd.fY * t;
+    return hit.distanceToSqd(pt);
+}
+
+/*  Given a cubic, determine if all four points are in a line.
+    Return true if the inner points is close to a line connecting the outermost points.
+
+    Find the outermost point by looking for the largest difference in X or Y.
+    Given the indices of the outermost points, and that outer_1 is greater than outer_2,
+    this table shows the index of the smaller of the remaining points:
+
+                      outer_2
+                  0    1    2    3
+      outer_1     ----------------
+         0     |  -    2    1    1
+         1     |  -    -    0    0
+         2     |  -    -    -    0
+         3     |  -    -    -    -
+
+    If outer_1 == 0 and outer_2 == 1, the smaller of the remaining indices (2 and 3) is 2.
+
+    This table can be collapsed to: (1 + (2 >> outer_2)) >> outer_1
+
+    Given three indices (outer_1 outer_2 mid_1) from 0..3, the remaining index is:
+
+               mid_2 == (outer_1 ^ outer_2 ^ mid_1)
+ */
+static bool cubic_in_line(const SkPoint cubic[4]) {
+    SkScalar ptMax = -1;
+    int outer1, outer2;
+    for (int index = 0; index < 3; ++index) {
+        for (int inner = index + 1; inner < 4; ++inner) {
+            SkVector testDiff = cubic[inner] - cubic[index];
+            SkScalar testMax = SkTMax(SkScalarAbs(testDiff.fX), SkScalarAbs(testDiff.fY));
+            if (ptMax < testMax) {
+                outer1 = index;
+                outer2 = inner;
+                ptMax = testMax;
+            }
+        }
+    }
+    SkASSERT(outer1 >= 0 && outer1 <= 2);
+    SkASSERT(outer2 >= 1 && outer2 <= 3);
+    SkASSERT(outer1 < outer2);
+    int mid1 = (1 + (2 >> outer2)) >> outer1;
+    SkASSERT(mid1 >= 0 && mid1 <= 2);
+    SkASSERT(outer1 != mid1 && outer2 != mid1);
+    int mid2 = outer1 ^ outer2 ^ mid1;
+    SkASSERT(mid2 >= 1 && mid2 <= 3);
+    SkASSERT(mid2 != outer1 && mid2 != outer2 && mid2 != mid1);
+    SkASSERT(((1 << outer1) | (1 << outer2) | (1 << mid1) | (1 << mid2)) == 0x0f);
+    SkScalar lineSlop = ptMax * ptMax * 0.00001f;  // this multiplier is pulled out of the air
+    return pt_to_line(cubic[mid1], cubic[outer1], cubic[outer2]) <= lineSlop
+            && pt_to_line(cubic[mid2], cubic[outer1], cubic[outer2]) <= lineSlop;
+}
+
+/* Given quad, see if all there points are in a line.
+   Return true if the inside point is close to a line connecting the outermost points.
+
+   Find the outermost point by looking for the largest difference in X or Y.
+   Since the XOR of the indices is 3  (0 ^ 1 ^ 2)
+   the missing index equals: outer_1 ^ outer_2 ^ 3
+ */
+static bool quad_in_line(const SkPoint quad[3]) {
+    SkScalar ptMax = -1;
+    int outer1, outer2;
+    for (int index = 0; index < 2; ++index) {
+        for (int inner = index + 1; inner < 3; ++inner) {
+            SkVector testDiff = quad[inner] - quad[index];
+            SkScalar testMax = SkTMax(SkScalarAbs(testDiff.fX), SkScalarAbs(testDiff.fY));
+            if (ptMax < testMax) {
+                outer1 = index;
+                outer2 = inner;
+                ptMax = testMax;
+            }
+        }
+    }
+    SkASSERT(outer1 >= 0 && outer1 <= 1);
+    SkASSERT(outer2 >= 1 && outer2 <= 2);
+    SkASSERT(outer1 < outer2);
+    int mid = outer1 ^ outer2 ^ 3;
+    SkScalar lineSlop =  ptMax * ptMax * 0.00001f;  // this multiplier is pulled out of the air
+    return pt_to_line(quad[mid], quad[outer1], quad[outer2]) <= lineSlop;
+}
+
+SkPathStroker::ReductionType SkPathStroker::CheckCubicLinear(const SkPoint cubic[4],
+        SkPoint reduction[3], const SkPoint** tangentPtPtr) {
+    bool degenerateAB = degenerate_vector(cubic[1] - cubic[0]);
+    bool degenerateBC = degenerate_vector(cubic[2] - cubic[1]);
+    bool degenerateCD = degenerate_vector(cubic[3] - cubic[2]);
+    if (degenerateAB & degenerateBC & degenerateCD) {
+        return kPoint_ReductionType;
+    }
+    if (degenerateAB + degenerateBC + degenerateCD == 2) {
+        return kLine_ReductionType;
+    }
+    if (!cubic_in_line(cubic)) {
+        *tangentPtPtr = degenerateAB ? &cubic[2] : &cubic[1];
+        return kQuad_ReductionType;
+    }
+    SkScalar tValues[3];
+    int count = SkFindCubicMaxCurvature(cubic, tValues);
+    if (count == 0) {
+        return kLine_ReductionType;
+    }
+    for (int index = 0; index < count; ++index) {
+        SkScalar t = tValues[index];
+        SkEvalCubicAt(cubic, t, &reduction[index], NULL, NULL);
+    }
+    SK_COMPILE_ASSERT(kQuad_ReductionType + 1 == kDegenerate_ReductionType, enum_out_of_whack);
+    SK_COMPILE_ASSERT(kQuad_ReductionType + 2 == kDegenerate2_ReductionType, enum_out_of_whack);
+    SK_COMPILE_ASSERT(kQuad_ReductionType + 3 == kDegenerate3_ReductionType, enum_out_of_whack);
+
+    return (ReductionType) (kQuad_ReductionType + count);
+}
+
+SkPathStroker::ReductionType SkPathStroker::CheckQuadLinear(const SkPoint quad[3],
+        SkPoint* reduction) {
+    bool degenerateAB = degenerate_vector(quad[1] - quad[0]);
+    bool degenerateBC = degenerate_vector(quad[2] - quad[1]);
+    if (degenerateAB & degenerateBC) {
+        return kPoint_ReductionType;
+    }
+    if (degenerateAB | degenerateBC) {
+        return kLine_ReductionType;
+    }
+    if (!quad_in_line(quad)) {
+        return kQuad_ReductionType;
+    }
+    SkScalar t = SkFindQuadMaxCurvature(quad);
+    if (0 == t) {
+        return kLine_ReductionType;
+    }
+    SkEvalQuadAt(quad, t, reduction, NULL);
+    return kDegenerate_ReductionType;
+}
+
+#else
 
 void SkPathStroker::cubic_to(const SkPoint pts[4],
                       const SkVector& normalAB, const SkVector& unitNormalAB,
@@ -362,8 +737,42 @@ DRAW_LINE:
                         pts[3].fX - normalCD->fX, pts[3].fY - normalCD->fY);
     }
 }
+#endif
 
 void SkPathStroker::quadTo(const SkPoint& pt1, const SkPoint& pt2) {
+#if QUAD_STROKE_APPROXIMATION
+    const SkPoint quad[3] = { fPrevPt, pt1, pt2 };
+    SkPoint reduction;
+    ReductionType reductionType = CheckQuadLinear(quad, &reduction);
+    if (kPoint_ReductionType == reductionType) {
+        return;
+    }
+    if (kLine_ReductionType == reductionType) {
+        this->lineTo(pt2);
+        return;
+    }
+    if (kDegenerate_ReductionType == reductionType) {
+        this->lineTo(reduction);
+        SkStrokerPriv::JoinProc saveJoiner = fJoiner;
+        fJoiner = SkStrokerPriv::JoinFactory(SkPaint::kRound_Join);
+        this->lineTo(pt2);
+        fJoiner = saveJoiner;
+        return;
+    }
+    SkASSERT(kQuad_ReductionType == reductionType);
+    SkVector normalAB, unitAB, normalBC, unitBC;
+    this->preJoinTo(pt1, &normalAB, &unitAB, false);
+    SkQuadConstruct quadPts;
+    this->init(kOuter_StrokeType, &quadPts, 0, 1);
+    if (!this->quadStroke(quad, &quadPts)) {
+        return;
+    }
+    this->init(kInner_StrokeType, &quadPts, 0, 1);
+    if (!this->quadStroke(quad, &quadPts)) {
+        return;
+    }
+    this->setQuadEndNormal(quad, normalAB, unitAB, &normalBC, &unitBC);
+#else
     bool    degenerateAB = SkPath::IsLineDegenerate(fPrevPt, pt1);
     bool    degenerateBC = SkPath::IsLineDegenerate(pt1, pt2);
 
@@ -414,12 +823,450 @@ void SkPathStroker::quadTo(const SkPoint& pt1, const SkPoint& pt2) {
                           kMaxQuadSubdivide);
         }
     }
+#endif
 
     this->postJoinTo(pt2, normalBC, unitBC);
 }
 
+#if QUAD_STROKE_APPROXIMATION
+// Given a point on the curve and its derivative, scale the derivative by the radius, and
+// compute the perpendicular point and its tangent.
+void SkPathStroker::setRayPts(const SkPoint& tPt, SkVector* dxy, SkPoint* onPt,
+        SkPoint* tangent) const {
+    if (!dxy->setLength(fRadius)) {  // consider moving double logic into SkPoint::setLength
+        double xx = dxy->fX;
+        double yy = dxy->fY;
+        double dscale = fRadius / sqrt(xx * xx + yy * yy);
+        dxy->fX = SkDoubleToScalar(xx * dscale);
+        dxy->fY = SkDoubleToScalar(yy * dscale);
+    }
+    SkScalar axisFlip = SkIntToScalar(fStrokeType);  // go opposite ways for outer, inner
+    onPt->fX = tPt.fX + axisFlip * dxy->fY;
+    onPt->fY = tPt.fY - axisFlip * dxy->fX;
+    if (tangent) {
+        tangent->fX = onPt->fX + dxy->fX;
+        tangent->fY = onPt->fY + dxy->fY;
+    }
+}
+
+// Given a cubic and t, return the point on curve, its perpendicular, and the perpendicular tangent.
+// Returns false if the perpendicular could not be computed (because the derivative collapsed to 0)
+bool SkPathStroker::cubicPerpRay(const SkPoint cubic[4], SkScalar t, SkPoint* tPt, SkPoint* onPt,
+        SkPoint* tangent) const {
+    SkVector dxy;
+    SkEvalCubicAt(cubic, t, tPt, &dxy, NULL);
+    if (dxy.fX == 0 && dxy.fY == 0) {
+        if (SkScalarNearlyZero(t)) {
+            dxy = cubic[2] - cubic[0];
+        } else if (SkScalarNearlyZero(1 - t)) {
+            dxy = cubic[3] - cubic[1];
+        } else {
+            return false;
+        }
+        if (dxy.fX == 0 && dxy.fY == 0) {
+            dxy = cubic[3] - cubic[0];
+        }
+    }
+    setRayPts(*tPt, &dxy, onPt, tangent);
+    return true;
+}
+
+// Given a cubic and a t range, find the start and end if they haven't been found already.
+bool SkPathStroker::cubicQuadEnds(const SkPoint cubic[4], SkQuadConstruct* quadPts) {
+    if (!quadPts->fStartSet) {
+        SkPoint cubicStartPt;
+        if (!this->cubicPerpRay(cubic, quadPts->fStartT, &cubicStartPt, &quadPts->fQuad[0],
+                &quadPts->fTangentStart)) {
+            return false;
+        }
+        quadPts->fStartSet = true;
+    }
+    if (!quadPts->fEndSet) {
+        SkPoint cubicEndPt;
+        if (!this->cubicPerpRay(cubic, quadPts->fEndT, &cubicEndPt, &quadPts->fQuad[2],
+                &quadPts->fTangentEnd)) {
+            return false;
+        }
+        quadPts->fEndSet = true;
+    }
+    return true;
+}
+
+bool SkPathStroker::cubicQuadMid(const SkPoint cubic[4], const SkQuadConstruct* quadPts,
+        SkPoint* mid) const {
+    SkPoint cubicMidPt;
+    return this->cubicPerpRay(cubic, quadPts->fMidT, &cubicMidPt, mid, NULL);
+}
+
+// Given a quad and t, return the point on curve, its perpendicular, and the perpendicular tangent.
+void SkPathStroker::quadPerpRay(const SkPoint quad[3], SkScalar t, SkPoint* tPt, SkPoint* onPt,
+        SkPoint* tangent) const {
+    SkVector dxy;
+    SkEvalQuadAt(quad, t, tPt, &dxy);
+    if (dxy.fX == 0 && dxy.fY == 0) {
+        dxy = quad[2] - quad[0];
+    }
+    setRayPts(*tPt, &dxy, onPt, tangent);
+}
+
+// Find the intersection of the stroke tangents to construct a stroke quad.
+// Return whether the stroke is a degenerate (a line), a quad, or must be split.
+// Optionally compute the quad's control point.
+SkPathStroker::ResultType SkPathStroker::intersectRay(SkQuadConstruct* quadPts,
+        IntersectRayType intersectRayType  STROKER_DEBUG_PARAMS(int depth)) const {
+    const SkPoint& start = quadPts->fQuad[0];
+    const SkPoint& end = quadPts->fQuad[2];
+    SkVector aLen = quadPts->fTangentStart - start;
+    SkVector bLen = quadPts->fTangentEnd - end;
+    SkScalar denom = aLen.cross(bLen);
+    SkVector ab0 = start - end;
+    SkScalar numerA = bLen.cross(ab0);
+    SkScalar numerB = aLen.cross(ab0);
+    if (!SkScalarNearlyZero(denom)) {
+        // if the perpendicular distances from the quad points to the opposite tangent line
+        // are small, a straight line is good enough
+        SkScalar dist1 = pt_to_line(start, end, quadPts->fTangentEnd);
+        SkScalar dist2 = pt_to_line(end, start, quadPts->fTangentStart);
+        if (SkTMax(dist1, dist2) <= fError * fError) {
+            return STROKER_RESULT(kDegenerate_ResultType, depth, quadPts,
+                    "SkTMax(dist1=%g, dist2=%g) <= fError * fError", dist1, dist2);
+        }
+        if ((numerA >= 0) != (numerB >= 0)) {
+            if (kCtrlPt_RayType == intersectRayType) {
+                numerA /= denom;
+                SkPoint* ctrlPt = &quadPts->fQuad[1];
+                ctrlPt->fX = start.fX * (1 - numerA) + quadPts->fTangentStart.fX * numerA;
+                ctrlPt->fY = start.fY * (1 - numerA) + quadPts->fTangentStart.fY * numerA;
+            }
+            return STROKER_RESULT(kQuad_ResultType, depth, quadPts,
+                    "(numerA=%g >= 0) != (numerB=%g >= 0)", numerA, numerB);
+        }
+        return STROKER_RESULT(kSplit_ResultType, depth, quadPts,
+                "(numerA=%g >= 0) == (numerB=%g >= 0)", numerA, numerB);
+    } else { // if the lines are parallel, straight line is good enough
+        return STROKER_RESULT(kDegenerate_ResultType, depth, quadPts,
+                "SkScalarNearlyZero(denom=%g)", denom);
+    }
+}
+
+// Given a cubic and a t-range, determine if the stroke can be described by a quadratic.
+SkPathStroker::ResultType SkPathStroker::tangentsMeet(const SkPoint cubic[4],
+        SkQuadConstruct* quadPts) {
+    if (!this->cubicQuadEnds(cubic, quadPts)) {
+        return kNormalError_ResultType;
+    }
+    return intersectRay(quadPts, kResultType_RayType  STROKER_DEBUG_PARAMS(fRecursionDepth));
+}
+
+// Intersect the line with the quad and return the t values on the quad where the line crosses.
+static int intersect_quad_ray(const SkPoint line[2], const SkPoint quad[3], SkScalar roots[2]) {
+    SkVector vec = line[1] - line[0];
+    SkScalar r[3];
+    for (int n = 0; n < 3; ++n) {
+        r[n] = (quad[n].fY - line[0].fY) * vec.fX - (quad[n].fX - line[0].fX) * vec.fY;
+    }
+    SkScalar A = r[2];
+    SkScalar B = r[1];
+    SkScalar C = r[0];
+    A += C - 2 * B;  // A = a - 2*b + c
+    B -= C;  // B = -(b - c)
+    return SkFindUnitQuadRoots(A, 2 * B, C, roots);
+}
+
+// Return true if the point is close to the bounds of the quad. This is used as a quick reject.
+bool SkPathStroker::ptInQuadBounds(const SkPoint quad[3], const SkPoint& pt) const {
+    SkScalar xMin = SkTMin(SkTMin(quad[0].fX, quad[1].fX), quad[2].fX);
+    if (pt.fX + fError < xMin) {
+        return false;
+    }
+    SkScalar xMax = SkTMax(SkTMax(quad[0].fX, quad[1].fX), quad[2].fX);
+    if (pt.fX - fError > xMax) {
+        return false;
+    }
+    SkScalar yMin = SkTMin(SkTMin(quad[0].fY, quad[1].fY), quad[2].fY);
+    if (pt.fY + fError < yMin) {
+        return false;
+    }
+    SkScalar yMax = SkTMax(SkTMax(quad[0].fY, quad[1].fY), quad[2].fY);
+    if (pt.fY - fError > yMax) {
+        return false;
+    }
+    return true;
+}
+
+static bool points_within_dist(const SkPoint& nearPt, const SkPoint& farPt, SkScalar limit) {
+    return nearPt.distanceToSqd(farPt) <= limit * limit;
+}
+
+static bool sharp_angle(const SkPoint quad[3]) {
+    SkVector smaller = quad[1] - quad[0];
+    SkVector larger = quad[1] - quad[2];
+    SkScalar smallerLen = smaller.lengthSqd();
+    SkScalar largerLen = larger.lengthSqd();
+    if (smallerLen > largerLen) {
+        SkTSwap(smaller, larger);
+        largerLen = smallerLen;
+    }
+    if (!smaller.setLength(largerLen)) {
+        return false;
+    }
+    SkScalar dot = smaller.dot(larger);
+    return dot > 0;
+}
+
+SkPathStroker::ResultType SkPathStroker::strokeCloseEnough(const SkPoint stroke[3],
+        const SkPoint ray[2], SkQuadConstruct* quadPts  STROKER_DEBUG_PARAMS(int depth)) const {
+    SkPoint strokeMid;
+    SkEvalQuadAt(stroke, SK_ScalarHalf, &strokeMid);
+    // measure the distance from the curve to the quad-stroke midpoint, compare to radius
+    if (points_within_dist(ray[0], strokeMid, fError)) {  // if the difference is small
+        if (sharp_angle(quadPts->fQuad)) {
+            return STROKER_RESULT(kSplit_ResultType, depth, quadPts,
+                    "sharp_angle (1) =%g,%g, %g,%g, %g,%g",
+                    quadPts->fQuad[0].fX, quadPts->fQuad[0].fY,
+                    quadPts->fQuad[1].fX, quadPts->fQuad[1].fY,
+                    quadPts->fQuad[2].fX, quadPts->fQuad[2].fY);
+        }
+        return STROKER_RESULT(kQuad_ResultType, depth, quadPts,
+                "points_within_dist(ray[0]=%g,%g, strokeMid=%g,%g, fError)",
+                ray[0].fX, ray[0].fY, strokeMid.fX, strokeMid.fY);
+    }
+    // measure the distance to quad's bounds (quick reject)
+        // an alternative : look for point in triangle
+    if (!ptInQuadBounds(stroke, ray[0])) {  // if far, subdivide
+        return STROKER_RESULT(kSplit_ResultType, depth, quadPts,
+                "!pt_in_quad_bounds(stroke=(%g,%g %g,%g %g,%g), ray[0]=%g,%g)",
+                stroke[0].fX, stroke[0].fY, stroke[1].fX, stroke[1].fY, stroke[2].fX, stroke[2].fY,
+                ray[0].fX, ray[0].fY);
+    }
+    // measure the curve ray distance to the quad-stroke
+    SkScalar roots[2];
+    int rootCount = intersect_quad_ray(ray, stroke, roots);
+    if (rootCount != 1) {
+        return STROKER_RESULT(kSplit_ResultType, depth, quadPts,
+                "rootCount=%d != 1", rootCount);
+    }
+    SkPoint quadPt;
+    SkEvalQuadAt(stroke, roots[0], &quadPt);
+    SkScalar error = fError * (SK_Scalar1 - SkScalarAbs(roots[0] - 0.5f) * 2);
+    if (points_within_dist(ray[0], quadPt, error)) {  // if the difference is small, we're done
+        if (sharp_angle(quadPts->fQuad)) {
+            return STROKER_RESULT(kSplit_ResultType, depth, quadPts,
+                    "sharp_angle (2) =%g,%g, %g,%g, %g,%g",
+                    quadPts->fQuad[0].fX, quadPts->fQuad[0].fY,
+                    quadPts->fQuad[1].fX, quadPts->fQuad[1].fY,
+                    quadPts->fQuad[2].fX, quadPts->fQuad[2].fY);
+        }
+        return STROKER_RESULT(kQuad_ResultType, depth, quadPts,
+                "points_within_dist(ray[0]=%g,%g, quadPt=%g,%g, error=%g)",
+                ray[0].fX, ray[0].fY, quadPt.fX, quadPt.fY, error);
+    }
+    // otherwise, subdivide
+    return STROKER_RESULT(kSplit_ResultType, depth, quadPts, "%s", "fall through");
+}
+
+SkPathStroker::ResultType SkPathStroker::compareQuadCubic(const SkPoint cubic[4],
+        SkQuadConstruct* quadPts) {
+    // get the quadratic approximation of the stroke
+    if (!this->cubicQuadEnds(cubic, quadPts)) {
+        return kNormalError_ResultType;
+    }
+    ResultType resultType = intersectRay(quadPts, kCtrlPt_RayType
+            STROKER_DEBUG_PARAMS(fRecursionDepth) );
+    if (resultType != kQuad_ResultType) {
+        return resultType;
+    }
+    // project a ray from the curve to the stroke
+    SkPoint ray[2];  // point near midpoint on quad, midpoint on cubic
+    if (!this->cubicPerpRay(cubic, quadPts->fMidT, &ray[1], &ray[0], NULL)) {
+        return kNormalError_ResultType;
+    }
+    return strokeCloseEnough(quadPts->fQuad, ray, quadPts  STROKER_DEBUG_PARAMS(fRecursionDepth));
+}
+
+// if false is returned, caller splits quadratic approximation
+SkPathStroker::ResultType SkPathStroker::compareQuadQuad(const SkPoint quad[3],
+        SkQuadConstruct* quadPts) {
+    // get the quadratic approximation of the stroke
+    if (!quadPts->fStartSet) {
+        SkPoint quadStartPt;
+        this->quadPerpRay(quad, quadPts->fStartT, &quadStartPt, &quadPts->fQuad[0],
+                &quadPts->fTangentStart);
+        quadPts->fStartSet = true;
+    }
+    if (!quadPts->fEndSet) {
+        SkPoint quadEndPt;
+        this->quadPerpRay(quad, quadPts->fEndT, &quadEndPt, &quadPts->fQuad[2],
+                &quadPts->fTangentEnd);
+        quadPts->fEndSet = true;
+    }
+    ResultType resultType = intersectRay(quadPts, kCtrlPt_RayType
+            STROKER_DEBUG_PARAMS(fRecursionDepth));
+    if (resultType != kQuad_ResultType) {
+        return resultType;
+    }
+    // project a ray from the curve to the stroke
+    SkPoint ray[2];
+    this->quadPerpRay(quad, quadPts->fMidT, &ray[1], &ray[0], NULL);
+    return strokeCloseEnough(quadPts->fQuad, ray, quadPts  STROKER_DEBUG_PARAMS(fRecursionDepth));
+}
+
+void SkPathStroker::addDegenerateLine(const SkQuadConstruct* quadPts) {
+    const SkPoint* quad = quadPts->fQuad;
+    SkPath* path = fStrokeType == kOuter_StrokeType ? &fOuter : &fInner;
+    path->lineTo(quad[2].fX, quad[2].fY);
+}
+
+bool SkPathStroker::cubicMidOnLine(const SkPoint cubic[4], const SkQuadConstruct* quadPts) const {
+    SkPoint strokeMid;
+    if (!cubicQuadMid(cubic, quadPts, &strokeMid)) {
+        return false;
+    }
+    SkScalar dist = pt_to_line(strokeMid, quadPts->fQuad[0], quadPts->fQuad[2]);
+    return dist < fError * fError;
+}
+
+bool SkPathStroker::cubicStroke(const SkPoint cubic[4], SkQuadConstruct* quadPts) {
+    if (!fFoundTangents) {
+        ResultType resultType = this->tangentsMeet(cubic, quadPts);
+        if (kQuad_ResultType != resultType) {
+            if (kNormalError_ResultType == resultType) {
+                return false;
+            }
+            if ((kDegenerate_ResultType == resultType
+                    || points_within_dist(quadPts->fQuad[0], quadPts->fQuad[2], fError))
+                    && cubicMidOnLine(cubic, quadPts)) {
+                addDegenerateLine(quadPts);
+                return true;
+            }
+        } else {
+            fFoundTangents = true;
+        }
+    }
+    if (fFoundTangents) {
+        ResultType resultType = this->compareQuadCubic(cubic, quadPts);
+        if (kQuad_ResultType == resultType) {
+            SkPath* path = fStrokeType == kOuter_StrokeType ? &fOuter : &fInner;
+            const SkPoint* stroke = quadPts->fQuad;
+            path->quadTo(stroke[1].fX, stroke[1].fY, stroke[2].fX, stroke[2].fY);
+            return true;
+        }
+        if (kDegenerate_ResultType == resultType) {
+            addDegenerateLine(quadPts);
+            return true;
+        }
+        if (kNormalError_ResultType == resultType) {
+            return false;
+        }
+    }
+    if (!SkScalarIsFinite(quadPts->fQuad[2].fX) || !SkScalarIsFinite(quadPts->fQuad[2].fY)) {
+        return false;  // just abort if projected quad isn't representable
+    }
+    SkDEBUGCODE(gMaxRecursion[fFoundTangents] = SkTMax(gMaxRecursion[fFoundTangents],
+            fRecursionDepth + 1));
+    if (++fRecursionDepth > kRecursiveLimits[fFoundTangents]) {
+        return false;  // just abort if projected quad isn't representable
+    }
+    SkQuadConstruct half;
+    if (!half.initWithStart(quadPts)) {
+        addDegenerateLine(quadPts);
+        return true;
+    }
+    if (!this->cubicStroke(cubic, &half)) {
+        return false;
+    }
+    if (!half.initWithEnd(quadPts)) {
+        addDegenerateLine(quadPts);
+        return true;
+    }
+    if (!this->cubicStroke(cubic, &half)) {
+        return false;
+    }
+    --fRecursionDepth;
+    return true;
+}
+
+bool SkPathStroker::quadStroke(const SkPoint quad[3], SkQuadConstruct* quadPts) {
+    ResultType resultType = this->compareQuadQuad(quad, quadPts);
+    if (kQuad_ResultType == resultType) {
+        const SkPoint* stroke = quadPts->fQuad;
+        SkPath* path = fStrokeType == kOuter_StrokeType ? &fOuter : &fInner;
+        path->quadTo(stroke[1].fX, stroke[1].fY, stroke[2].fX, stroke[2].fY);
+        return true;
+    }
+    if (kDegenerate_ResultType == resultType) {
+        addDegenerateLine(quadPts);
+        return true;
+    }
+    SkDEBUGCODE(gMaxRecursion[kQuad_RecursiveLimit] = SkTMax(gMaxRecursion[kQuad_RecursiveLimit],
+            fRecursionDepth + 1));
+    if (++fRecursionDepth > kRecursiveLimits[kQuad_RecursiveLimit]) {
+        return false;  // just abort if projected quad isn't representable
+    }
+    SkQuadConstruct half;
+    (void) half.initWithStart(quadPts);
+    if (!this->quadStroke(quad, &half)) {
+        return false;
+    }
+    (void) half.initWithEnd(quadPts);
+    if (!this->quadStroke(quad, &half)) {
+        return false;
+    }
+    --fRecursionDepth;
+    return true;
+}
+
+#endif
+
 void SkPathStroker::cubicTo(const SkPoint& pt1, const SkPoint& pt2,
                             const SkPoint& pt3) {
+#if QUAD_STROKE_APPROXIMATION
+    const SkPoint cubic[4] = { fPrevPt, pt1, pt2, pt3 };
+    SkPoint reduction[3];
+    const SkPoint* tangentPt;
+    ReductionType reductionType = CheckCubicLinear(cubic, reduction, &tangentPt);
+    if (kPoint_ReductionType == reductionType) {
+        return;
+    }
+    if (kLine_ReductionType == reductionType) {
+        this->lineTo(pt3);
+        return;
+    }
+    if (kDegenerate_ReductionType <= reductionType && kDegenerate3_ReductionType >= reductionType) {
+        this->lineTo(reduction[0]);
+        SkStrokerPriv::JoinProc saveJoiner = fJoiner;
+        fJoiner = SkStrokerPriv::JoinFactory(SkPaint::kRound_Join);
+        if (kDegenerate2_ReductionType <= reductionType) {
+            this->lineTo(reduction[1]);
+        }
+        if (kDegenerate3_ReductionType == reductionType) {
+            this->lineTo(reduction[2]);
+        }
+        this->lineTo(pt3);
+        fJoiner = saveJoiner;
+        return;
+    }
+    SkASSERT(kQuad_ReductionType == reductionType);
+    SkVector normalAB, unitAB, normalCD, unitCD;
+    this->preJoinTo(*tangentPt, &normalAB, &unitAB, false);
+    SkScalar tValues[2];
+    int count = SkFindCubicInflections(cubic, tValues);
+    SkScalar lastT = 0;
+    for (int index = 0; index <= count; ++index) {
+        SkScalar nextT = index < count ? tValues[index] : 1;
+        SkQuadConstruct quadPts;
+        this->init(kOuter_StrokeType, &quadPts, lastT, nextT);
+        if (!this->cubicStroke(cubic, &quadPts)) {
+            return;
+        }
+        this->init(kInner_StrokeType, &quadPts, lastT, nextT);
+        if (!this->cubicStroke(cubic, &quadPts)) {
+            return;
+        }
+        lastT = nextT;
+    }
+    this->setCubicEndNormal(cubic, normalAB, unitAB, &normalCD, &unitCD);
+#else
     bool    degenerateAB = SkPath::IsLineDegenerate(fPrevPt, pt1);
     bool    degenerateBC = SkPath::IsLineDegenerate(pt1, pt2);
     bool    degenerateCD = SkPath::IsLineDegenerate(pt2, pt3);
@@ -465,6 +1312,7 @@ void SkPathStroker::cubicTo(const SkPoint& pt1, const SkPoint& pt2,
 
         }
     }
+#endif
 
     this->postJoinTo(pt3, normalCD, unitCD);
 }
@@ -498,6 +1346,13 @@ SkStroke::SkStroke(const SkPaint& p, SkScalar width) {
     fDoFill     = SkToU8(p.getStyle() == SkPaint::kStrokeAndFill_Style);
 }
 
+#if QUAD_STROKE_APPROXIMATION
+void SkStroke::setError(SkScalar error) {
+    SkASSERT(error > 0);
+    fError = error;
+}
+#endif
+
 void SkStroke::setWidth(SkScalar width) {
     SkASSERT(width >= 0);
     fWidth = width;
@@ -575,8 +1430,13 @@ void SkStroke::strokePath(const SkPath& src, SkPath* dst) const {
     SkAutoConicToQuads converter;
     const SkScalar conicTol = SK_Scalar1 / 4;
 
+#if QUAD_STROKE_APPROXIMATION
+    SkPathStroker   stroker(src, radius, fMiterLimit, fError, this->getCap(),
+                            this->getJoin());
+#else
     SkPathStroker   stroker(src, radius, fMiterLimit, this->getCap(),
                             this->getJoin());
+#endif
     SkPath::Iter    iter(src, false);
     SkPath::Verb    lastSegment = SkPath::kMove_Verb;