Home | History | Annotate | Download | only in core
      1 /*
      2  * Copyright 2008 The Android Open Source Project
      3  *
      4  * Use of this source code is governed by a BSD-style license that can be
      5  * found in the LICENSE file.
      6  */
      7 
      8 #include "SkStrokerPriv.h"
      9 #include "SkGeometry.h"
     10 #include "SkPathPriv.h"
     11 #include "SkPointPriv.h"
     12 
     13 enum {
     14     kTangent_RecursiveLimit,
     15     kCubic_RecursiveLimit,
     16     kConic_RecursiveLimit,
     17     kQuad_RecursiveLimit
     18 };
     19 
     20 // quads with extreme widths (e.g. (0,1) (1,6) (0,3) width=5e7) recurse to point of failure
     21 // largest seen for normal cubics : 5, 26
     22 // largest seen for normal quads : 11
     23 static const int kRecursiveLimits[] = { 5*3, 26*3, 11*3, 11*3 }; // 3x limits seen in practice
     24 
     25 static_assert(0 == kTangent_RecursiveLimit, "cubic_stroke_relies_on_tangent_equalling_zero");
     26 static_assert(1 == kCubic_RecursiveLimit, "cubic_stroke_relies_on_cubic_equalling_one");
     27 static_assert(SK_ARRAY_COUNT(kRecursiveLimits) == kQuad_RecursiveLimit + 1,
     28               "recursive_limits_mismatch");
     29 
     30 #if defined SK_DEBUG && QUAD_STROKE_APPROX_EXTENDED_DEBUGGING
     31     int gMaxRecursion[SK_ARRAY_COUNT(kRecursiveLimits)] = { 0 };
     32 #endif
     33 #ifndef DEBUG_QUAD_STROKER
     34     #define DEBUG_QUAD_STROKER 0
     35 #endif
     36 
     37 #if DEBUG_QUAD_STROKER
     38     /* Enable to show the decisions made in subdividing the curve -- helpful when the resulting
     39         stroke has more than the optimal number of quadratics and lines */
     40     #define STROKER_RESULT(resultType, depth, quadPts, format, ...) \
     41             SkDebugf("[%d] %s " format "\n", depth, __FUNCTION__, __VA_ARGS__), \
     42             SkDebugf("  " #resultType " t=(%g,%g)\n", quadPts->fStartT, quadPts->fEndT), \
     43             resultType
     44     #define STROKER_DEBUG_PARAMS(...) , __VA_ARGS__
     45 #else
     46     #define STROKER_RESULT(resultType, depth, quadPts, format, ...) \
     47             resultType
     48     #define STROKER_DEBUG_PARAMS(...)
     49 #endif
     50 
     51 static inline bool degenerate_vector(const SkVector& v) {
     52     return !SkPointPriv::CanNormalize(v.fX, v.fY);
     53 }
     54 
     55 static bool set_normal_unitnormal(const SkPoint& before, const SkPoint& after, SkScalar scale,
     56                                   SkScalar radius,
     57                                   SkVector* normal, SkVector* unitNormal) {
     58     if (!unitNormal->setNormalize((after.fX - before.fX) * scale,
     59                                   (after.fY - before.fY) * scale)) {
     60         return false;
     61     }
     62     SkPointPriv::RotateCCW(unitNormal);
     63     unitNormal->scale(radius, normal);
     64     return true;
     65 }
     66 
     67 static bool set_normal_unitnormal(const SkVector& vec,
     68                                   SkScalar radius,
     69                                   SkVector* normal, SkVector* unitNormal) {
     70     if (!unitNormal->setNormalize(vec.fX, vec.fY)) {
     71         return false;
     72     }
     73     SkPointPriv::RotateCCW(unitNormal);
     74     unitNormal->scale(radius, normal);
     75     return true;
     76 }
     77 
     78 ///////////////////////////////////////////////////////////////////////////////
     79 
     80 struct SkQuadConstruct {    // The state of the quad stroke under construction.
     81     SkPoint fQuad[3];       // the stroked quad parallel to the original curve
     82     SkPoint fTangentStart;  // a point tangent to fQuad[0]
     83     SkPoint fTangentEnd;    // a point tangent to fQuad[2]
     84     SkScalar fStartT;       // a segment of the original curve
     85     SkScalar fMidT;         //              "
     86     SkScalar fEndT;         //              "
     87     bool fStartSet;         // state to share common points across structs
     88     bool fEndSet;           //                     "
     89     bool fOppositeTangents; // set if coincident tangents have opposite directions
     90 
     91     // return false if start and end are too close to have a unique middle
     92     bool init(SkScalar start, SkScalar end) {
     93         fStartT = start;
     94         fMidT = (start + end) * SK_ScalarHalf;
     95         fEndT = end;
     96         fStartSet = fEndSet = false;
     97         return fStartT < fMidT && fMidT < fEndT;
     98     }
     99 
    100     bool initWithStart(SkQuadConstruct* parent) {
    101         if (!init(parent->fStartT, parent->fMidT)) {
    102             return false;
    103         }
    104         fQuad[0] = parent->fQuad[0];
    105         fTangentStart = parent->fTangentStart;
    106         fStartSet = true;
    107         return true;
    108     }
    109 
    110     bool initWithEnd(SkQuadConstruct* parent) {
    111         if (!init(parent->fMidT, parent->fEndT)) {
    112             return false;
    113         }
    114         fQuad[2] = parent->fQuad[2];
    115         fTangentEnd = parent->fTangentEnd;
    116         fEndSet = true;
    117         return true;
    118    }
    119 };
    120 
    121 class SkPathStroker {
    122 public:
    123     SkPathStroker(const SkPath& src,
    124                   SkScalar radius, SkScalar miterLimit, SkPaint::Cap,
    125                   SkPaint::Join, SkScalar resScale,
    126                   bool canIgnoreCenter);
    127 
    128     bool hasOnlyMoveTo() const { return 0 == fSegmentCount; }
    129     SkPoint moveToPt() const { return fFirstPt; }
    130 
    131     void moveTo(const SkPoint&);
    132     void lineTo(const SkPoint&, const SkPath::Iter* iter = nullptr);
    133     void quadTo(const SkPoint&, const SkPoint&);
    134     void conicTo(const SkPoint&, const SkPoint&, SkScalar weight);
    135     void cubicTo(const SkPoint&, const SkPoint&, const SkPoint&);
    136     void close(bool isLine) { this->finishContour(true, isLine); }
    137 
    138     void done(SkPath* dst, bool isLine) {
    139         this->finishContour(false, isLine);
    140         dst->swap(fOuter);
    141     }
    142 
    143     SkScalar getResScale() const { return fResScale; }
    144 
    145     bool isCurrentContourEmpty() const {
    146         return fInner.isZeroLengthSincePoint(0) &&
    147                fOuter.isZeroLengthSincePoint(fFirstOuterPtIndexInContour);
    148     }
    149 
    150 private:
    151     SkScalar    fRadius;
    152     SkScalar    fInvMiterLimit;
    153     SkScalar    fResScale;
    154     SkScalar    fInvResScale;
    155     SkScalar    fInvResScaleSquared;
    156 
    157     SkVector    fFirstNormal, fPrevNormal, fFirstUnitNormal, fPrevUnitNormal;
    158     SkPoint     fFirstPt, fPrevPt;  // on original path
    159     SkPoint     fFirstOuterPt;
    160     int         fFirstOuterPtIndexInContour;
    161     int         fSegmentCount;
    162     bool        fPrevIsLine;
    163     bool        fCanIgnoreCenter;
    164 
    165     SkStrokerPriv::CapProc  fCapper;
    166     SkStrokerPriv::JoinProc fJoiner;
    167 
    168     SkPath  fInner, fOuter; // outer is our working answer, inner is temp
    169 
    170     enum StrokeType {
    171         kOuter_StrokeType = 1,      // use sign-opposite values later to flip perpendicular axis
    172         kInner_StrokeType = -1
    173     } fStrokeType;
    174 
    175     enum ResultType {
    176         kSplit_ResultType,          // the caller should split the quad stroke in two
    177         kDegenerate_ResultType,     // the caller should add a line
    178         kQuad_ResultType,           // the caller should (continue to try to) add a quad stroke
    179     };
    180 
    181     enum ReductionType {
    182         kPoint_ReductionType,       // all curve points are practically identical
    183         kLine_ReductionType,        // the control point is on the line between the ends
    184         kQuad_ReductionType,        // the control point is outside the line between the ends
    185         kDegenerate_ReductionType,  // the control point is on the line but outside the ends
    186         kDegenerate2_ReductionType, // two control points are on the line but outside ends (cubic)
    187         kDegenerate3_ReductionType, // three areas of max curvature found (for cubic)
    188     };
    189 
    190     enum IntersectRayType {
    191         kCtrlPt_RayType,
    192         kResultType_RayType,
    193     };
    194 
    195     int fRecursionDepth;            // track stack depth to abort if numerics run amok
    196     bool fFoundTangents;            // do less work until tangents meet (cubic)
    197     bool fJoinCompleted;            // previous join was not degenerate
    198 
    199     void addDegenerateLine(const SkQuadConstruct* );
    200     static ReductionType CheckConicLinear(const SkConic& , SkPoint* reduction);
    201     static ReductionType CheckCubicLinear(const SkPoint cubic[4], SkPoint reduction[3],
    202                                    const SkPoint** tanPtPtr);
    203     static ReductionType CheckQuadLinear(const SkPoint quad[3], SkPoint* reduction);
    204     ResultType compareQuadConic(const SkConic& , SkQuadConstruct* ) const;
    205     ResultType compareQuadCubic(const SkPoint cubic[4], SkQuadConstruct* );
    206     ResultType compareQuadQuad(const SkPoint quad[3], SkQuadConstruct* );
    207     void conicPerpRay(const SkConic& , SkScalar t, SkPoint* tPt, SkPoint* onPt,
    208                       SkPoint* tangent) const;
    209     void conicQuadEnds(const SkConic& , SkQuadConstruct* ) const;
    210     bool conicStroke(const SkConic& , SkQuadConstruct* );
    211     bool cubicMidOnLine(const SkPoint cubic[4], const SkQuadConstruct* ) const;
    212     void cubicPerpRay(const SkPoint cubic[4], SkScalar t, SkPoint* tPt, SkPoint* onPt,
    213                       SkPoint* tangent) const;
    214     void cubicQuadEnds(const SkPoint cubic[4], SkQuadConstruct* );
    215     void cubicQuadMid(const SkPoint cubic[4], const SkQuadConstruct* , SkPoint* mid) const;
    216     bool cubicStroke(const SkPoint cubic[4], SkQuadConstruct* );
    217     void init(StrokeType strokeType, SkQuadConstruct* , SkScalar tStart, SkScalar tEnd);
    218     ResultType intersectRay(SkQuadConstruct* , IntersectRayType  STROKER_DEBUG_PARAMS(int) ) const;
    219     bool ptInQuadBounds(const SkPoint quad[3], const SkPoint& pt) const;
    220     void quadPerpRay(const SkPoint quad[3], SkScalar t, SkPoint* tPt, SkPoint* onPt,
    221                      SkPoint* tangent) const;
    222     bool quadStroke(const SkPoint quad[3], SkQuadConstruct* );
    223     void setConicEndNormal(const SkConic& ,
    224                            const SkVector& normalAB, const SkVector& unitNormalAB,
    225                            SkVector* normalBC, SkVector* unitNormalBC);
    226     void setCubicEndNormal(const SkPoint cubic[4],
    227                            const SkVector& normalAB, const SkVector& unitNormalAB,
    228                            SkVector* normalCD, SkVector* unitNormalCD);
    229     void setQuadEndNormal(const SkPoint quad[3],
    230                           const SkVector& normalAB, const SkVector& unitNormalAB,
    231                           SkVector* normalBC, SkVector* unitNormalBC);
    232     void setRayPts(const SkPoint& tPt, SkVector* dxy, SkPoint* onPt, SkPoint* tangent) const;
    233     static bool SlightAngle(SkQuadConstruct* );
    234     ResultType strokeCloseEnough(const SkPoint stroke[3], const SkPoint ray[2],
    235                                  SkQuadConstruct*  STROKER_DEBUG_PARAMS(int depth) ) const;
    236     ResultType tangentsMeet(const SkPoint cubic[4], SkQuadConstruct* );
    237 
    238     void    finishContour(bool close, bool isLine);
    239     bool    preJoinTo(const SkPoint&, SkVector* normal, SkVector* unitNormal,
    240                       bool isLine);
    241     void    postJoinTo(const SkPoint&, const SkVector& normal,
    242                        const SkVector& unitNormal);
    243 
    244     void    line_to(const SkPoint& currPt, const SkVector& normal);
    245 };
    246 
    247 ///////////////////////////////////////////////////////////////////////////////
    248 
    249 bool SkPathStroker::preJoinTo(const SkPoint& currPt, SkVector* normal,
    250                               SkVector* unitNormal, bool currIsLine) {
    251     SkASSERT(fSegmentCount >= 0);
    252 
    253     SkScalar    prevX = fPrevPt.fX;
    254     SkScalar    prevY = fPrevPt.fY;
    255 
    256     if (!set_normal_unitnormal(fPrevPt, currPt, fResScale, fRadius, normal, unitNormal)) {
    257         if (SkStrokerPriv::CapFactory(SkPaint::kButt_Cap) == fCapper) {
    258             return false;
    259         }
    260         /* Square caps and round caps draw even if the segment length is zero.
    261            Since the zero length segment has no direction, set the orientation
    262            to upright as the default orientation */
    263         normal->set(fRadius, 0);
    264         unitNormal->set(1, 0);
    265     }
    266 
    267     if (fSegmentCount == 0) {
    268         fFirstNormal = *normal;
    269         fFirstUnitNormal = *unitNormal;
    270         fFirstOuterPt.set(prevX + normal->fX, prevY + normal->fY);
    271 
    272         fOuter.moveTo(fFirstOuterPt.fX, fFirstOuterPt.fY);
    273         fInner.moveTo(prevX - normal->fX, prevY - normal->fY);
    274     } else {    // we have a previous segment
    275         fJoiner(&fOuter, &fInner, fPrevUnitNormal, fPrevPt, *unitNormal,
    276                 fRadius, fInvMiterLimit, fPrevIsLine, currIsLine);
    277     }
    278     fPrevIsLine = currIsLine;
    279     return true;
    280 }
    281 
    282 void SkPathStroker::postJoinTo(const SkPoint& currPt, const SkVector& normal,
    283                                const SkVector& unitNormal) {
    284     fJoinCompleted = true;
    285     fPrevPt = currPt;
    286     fPrevUnitNormal = unitNormal;
    287     fPrevNormal = normal;
    288     fSegmentCount += 1;
    289 }
    290 
    291 void SkPathStroker::finishContour(bool close, bool currIsLine) {
    292     if (fSegmentCount > 0) {
    293         SkPoint pt;
    294 
    295         if (close) {
    296             fJoiner(&fOuter, &fInner, fPrevUnitNormal, fPrevPt,
    297                     fFirstUnitNormal, fRadius, fInvMiterLimit,
    298                     fPrevIsLine, currIsLine);
    299             fOuter.close();
    300 
    301             if (fCanIgnoreCenter) {
    302                 if (!fOuter.getBounds().contains(fInner.getBounds())) {
    303                     SkASSERT(fInner.getBounds().contains(fOuter.getBounds()));
    304                     fInner.swap(fOuter);
    305                 }
    306             } else {
    307                 // now add fInner as its own contour
    308                 fInner.getLastPt(&pt);
    309                 fOuter.moveTo(pt.fX, pt.fY);
    310                 fOuter.reversePathTo(fInner);
    311                 fOuter.close();
    312             }
    313         } else {    // add caps to start and end
    314             // cap the end
    315             fInner.getLastPt(&pt);
    316             fCapper(&fOuter, fPrevPt, fPrevNormal, pt,
    317                     currIsLine ? &fInner : nullptr);
    318             fOuter.reversePathTo(fInner);
    319             // cap the start
    320             fCapper(&fOuter, fFirstPt, -fFirstNormal, fFirstOuterPt,
    321                     fPrevIsLine ? &fInner : nullptr);
    322             fOuter.close();
    323         }
    324     }
    325     // since we may re-use fInner, we rewind instead of reset, to save on
    326     // reallocating its internal storage.
    327     fInner.rewind();
    328     fSegmentCount = -1;
    329     fFirstOuterPtIndexInContour = fOuter.countPoints();
    330 }
    331 
    332 ///////////////////////////////////////////////////////////////////////////////
    333 
    334 SkPathStroker::SkPathStroker(const SkPath& src,
    335                              SkScalar radius, SkScalar miterLimit,
    336                              SkPaint::Cap cap, SkPaint::Join join, SkScalar resScale,
    337                              bool canIgnoreCenter)
    338         : fRadius(radius)
    339         , fResScale(resScale)
    340         , fCanIgnoreCenter(canIgnoreCenter) {
    341 
    342     /*  This is only used when join is miter_join, but we initialize it here
    343         so that it is always defined, to fis valgrind warnings.
    344     */
    345     fInvMiterLimit = 0;
    346 
    347     if (join == SkPaint::kMiter_Join) {
    348         if (miterLimit <= SK_Scalar1) {
    349             join = SkPaint::kBevel_Join;
    350         } else {
    351             fInvMiterLimit = SkScalarInvert(miterLimit);
    352         }
    353     }
    354     fCapper = SkStrokerPriv::CapFactory(cap);
    355     fJoiner = SkStrokerPriv::JoinFactory(join);
    356     fSegmentCount = -1;
    357     fFirstOuterPtIndexInContour = 0;
    358     fPrevIsLine = false;
    359 
    360     // Need some estimate of how large our final result (fOuter)
    361     // and our per-contour temp (fInner) will be, so we don't spend
    362     // extra time repeatedly growing these arrays.
    363     //
    364     // 3x for result == inner + outer + join (swag)
    365     // 1x for inner == 'wag' (worst contour length would be better guess)
    366     fOuter.incReserve(src.countPoints() * 3);
    367     fOuter.setIsVolatile(true);
    368     fInner.incReserve(src.countPoints());
    369     fInner.setIsVolatile(true);
    370     // TODO : write a common error function used by stroking and filling
    371     // The '4' below matches the fill scan converter's error term
    372     fInvResScale = SkScalarInvert(resScale * 4);
    373     fInvResScaleSquared = fInvResScale * fInvResScale;
    374     fRecursionDepth = 0;
    375 }
    376 
    377 void SkPathStroker::moveTo(const SkPoint& pt) {
    378     if (fSegmentCount > 0) {
    379         this->finishContour(false, false);
    380     }
    381     fSegmentCount = 0;
    382     fFirstPt = fPrevPt = pt;
    383     fJoinCompleted = false;
    384 }
    385 
    386 void SkPathStroker::line_to(const SkPoint& currPt, const SkVector& normal) {
    387     fOuter.lineTo(currPt.fX + normal.fX, currPt.fY + normal.fY);
    388     fInner.lineTo(currPt.fX - normal.fX, currPt.fY - normal.fY);
    389 }
    390 
    391 static bool has_valid_tangent(const SkPath::Iter* iter) {
    392     SkPath::Iter copy = *iter;
    393     SkPath::Verb verb;
    394     SkPoint pts[4];
    395     while ((verb = copy.next(pts))) {
    396         switch (verb) {
    397             case SkPath::kMove_Verb:
    398                 return false;
    399             case SkPath::kLine_Verb:
    400                 if (pts[0] == pts[1]) {
    401                     continue;
    402                 }
    403                 return true;
    404             case SkPath::kQuad_Verb:
    405             case SkPath::kConic_Verb:
    406                 if (pts[0] == pts[1] && pts[0] == pts[2]) {
    407                     continue;
    408                 }
    409                 return true;
    410             case SkPath::kCubic_Verb:
    411                 if (pts[0] == pts[1] && pts[0] == pts[2] && pts[0] == pts[3]) {
    412                     continue;
    413                 }
    414                 return true;
    415             case SkPath::kClose_Verb:
    416             case SkPath::kDone_Verb:
    417                 return false;
    418         }
    419     }
    420     return false;
    421 }
    422 
    423 void SkPathStroker::lineTo(const SkPoint& currPt, const SkPath::Iter* iter) {
    424     bool teenyLine = SkPointPriv::EqualsWithinTolerance(fPrevPt, currPt, SK_ScalarNearlyZero * fInvResScale);
    425     if (SkStrokerPriv::CapFactory(SkPaint::kButt_Cap) == fCapper && teenyLine) {
    426         return;
    427     }
    428     if (teenyLine && (fJoinCompleted || (iter && has_valid_tangent(iter)))) {
    429         return;
    430     }
    431     SkVector    normal, unitNormal;
    432 
    433     if (!this->preJoinTo(currPt, &normal, &unitNormal, true)) {
    434         return;
    435     }
    436     this->line_to(currPt, normal);
    437     this->postJoinTo(currPt, normal, unitNormal);
    438 }
    439 
    440 void SkPathStroker::setQuadEndNormal(const SkPoint quad[3], const SkVector& normalAB,
    441         const SkVector& unitNormalAB, SkVector* normalBC, SkVector* unitNormalBC) {
    442     if (!set_normal_unitnormal(quad[1], quad[2], fResScale, fRadius, normalBC, unitNormalBC)) {
    443         *normalBC = normalAB;
    444         *unitNormalBC = unitNormalAB;
    445     }
    446 }
    447 
    448 void SkPathStroker::setConicEndNormal(const SkConic& conic, const SkVector& normalAB,
    449         const SkVector& unitNormalAB, SkVector* normalBC, SkVector* unitNormalBC) {
    450     setQuadEndNormal(conic.fPts, normalAB, unitNormalAB, normalBC, unitNormalBC);
    451 }
    452 
    453 void SkPathStroker::setCubicEndNormal(const SkPoint cubic[4], const SkVector& normalAB,
    454         const SkVector& unitNormalAB, SkVector* normalCD, SkVector* unitNormalCD) {
    455     SkVector    ab = cubic[1] - cubic[0];
    456     SkVector    cd = cubic[3] - cubic[2];
    457 
    458     bool    degenerateAB = degenerate_vector(ab);
    459     bool    degenerateCD = degenerate_vector(cd);
    460 
    461     if (degenerateAB && degenerateCD) {
    462         goto DEGENERATE_NORMAL;
    463     }
    464 
    465     if (degenerateAB) {
    466         ab = cubic[2] - cubic[0];
    467         degenerateAB = degenerate_vector(ab);
    468     }
    469     if (degenerateCD) {
    470         cd = cubic[3] - cubic[1];
    471         degenerateCD = degenerate_vector(cd);
    472     }
    473     if (degenerateAB || degenerateCD) {
    474 DEGENERATE_NORMAL:
    475         *normalCD = normalAB;
    476         *unitNormalCD = unitNormalAB;
    477         return;
    478     }
    479     SkAssertResult(set_normal_unitnormal(cd, fRadius, normalCD, unitNormalCD));
    480 }
    481 
    482 void SkPathStroker::init(StrokeType strokeType, SkQuadConstruct* quadPts, SkScalar tStart,
    483         SkScalar tEnd) {
    484     fStrokeType = strokeType;
    485     fFoundTangents = false;
    486     quadPts->init(tStart, tEnd);
    487 }
    488 
    489 // returns the distance squared from the point to the line
    490 static SkScalar pt_to_line(const SkPoint& pt, const SkPoint& lineStart, const SkPoint& lineEnd) {
    491     SkVector dxy = lineEnd - lineStart;
    492     if (degenerate_vector(dxy)) {
    493         return SkPointPriv::DistanceToSqd(pt, lineStart);
    494     }
    495     SkVector ab0 = pt - lineStart;
    496     SkScalar numer = dxy.dot(ab0);
    497     SkScalar denom = dxy.dot(dxy);
    498     SkScalar t = numer / denom;
    499     SkPoint hit;
    500     hit.fX = lineStart.fX * (1 - t) + lineEnd.fX * t;
    501     hit.fY = lineStart.fY * (1 - t) + lineEnd.fY * t;
    502     return SkPointPriv::DistanceToSqd(hit, pt);
    503 }
    504 
    505 /*  Given a cubic, determine if all four points are in a line.
    506     Return true if the inner points is close to a line connecting the outermost points.
    507 
    508     Find the outermost point by looking for the largest difference in X or Y.
    509     Given the indices of the outermost points, and that outer_1 is greater than outer_2,
    510     this table shows the index of the smaller of the remaining points:
    511 
    512                       outer_2
    513                   0    1    2    3
    514       outer_1     ----------------
    515          0     |  -    2    1    1
    516          1     |  -    -    0    0
    517          2     |  -    -    -    0
    518          3     |  -    -    -    -
    519 
    520     If outer_1 == 0 and outer_2 == 1, the smaller of the remaining indices (2 and 3) is 2.
    521 
    522     This table can be collapsed to: (1 + (2 >> outer_2)) >> outer_1
    523 
    524     Given three indices (outer_1 outer_2 mid_1) from 0..3, the remaining index is:
    525 
    526                mid_2 == (outer_1 ^ outer_2 ^ mid_1)
    527  */
    528 static bool cubic_in_line(const SkPoint cubic[4]) {
    529     SkScalar ptMax = -1;
    530     int outer1 SK_INIT_TO_AVOID_WARNING;
    531     int outer2 SK_INIT_TO_AVOID_WARNING;
    532     for (int index = 0; index < 3; ++index) {
    533         for (int inner = index + 1; inner < 4; ++inner) {
    534             SkVector testDiff = cubic[inner] - cubic[index];
    535             SkScalar testMax = SkTMax(SkScalarAbs(testDiff.fX), SkScalarAbs(testDiff.fY));
    536             if (ptMax < testMax) {
    537                 outer1 = index;
    538                 outer2 = inner;
    539                 ptMax = testMax;
    540             }
    541         }
    542     }
    543     SkASSERT(outer1 >= 0 && outer1 <= 2);
    544     SkASSERT(outer2 >= 1 && outer2 <= 3);
    545     SkASSERT(outer1 < outer2);
    546     int mid1 = (1 + (2 >> outer2)) >> outer1;
    547     SkASSERT(mid1 >= 0 && mid1 <= 2);
    548     SkASSERT(outer1 != mid1 && outer2 != mid1);
    549     int mid2 = outer1 ^ outer2 ^ mid1;
    550     SkASSERT(mid2 >= 1 && mid2 <= 3);
    551     SkASSERT(mid2 != outer1 && mid2 != outer2 && mid2 != mid1);
    552     SkASSERT(((1 << outer1) | (1 << outer2) | (1 << mid1) | (1 << mid2)) == 0x0f);
    553     SkScalar lineSlop = ptMax * ptMax * 0.00001f;  // this multiplier is pulled out of the air
    554     return pt_to_line(cubic[mid1], cubic[outer1], cubic[outer2]) <= lineSlop
    555             && pt_to_line(cubic[mid2], cubic[outer1], cubic[outer2]) <= lineSlop;
    556 }
    557 
    558 /* Given quad, see if all there points are in a line.
    559    Return true if the inside point is close to a line connecting the outermost points.
    560 
    561    Find the outermost point by looking for the largest difference in X or Y.
    562    Since the XOR of the indices is 3  (0 ^ 1 ^ 2)
    563    the missing index equals: outer_1 ^ outer_2 ^ 3
    564  */
    565 static bool quad_in_line(const SkPoint quad[3]) {
    566     SkScalar ptMax = -1;
    567     int outer1 SK_INIT_TO_AVOID_WARNING;
    568     int outer2 SK_INIT_TO_AVOID_WARNING;
    569     for (int index = 0; index < 2; ++index) {
    570         for (int inner = index + 1; inner < 3; ++inner) {
    571             SkVector testDiff = quad[inner] - quad[index];
    572             SkScalar testMax = SkTMax(SkScalarAbs(testDiff.fX), SkScalarAbs(testDiff.fY));
    573             if (ptMax < testMax) {
    574                 outer1 = index;
    575                 outer2 = inner;
    576                 ptMax = testMax;
    577             }
    578         }
    579     }
    580     SkASSERT(outer1 >= 0 && outer1 <= 1);
    581     SkASSERT(outer2 >= 1 && outer2 <= 2);
    582     SkASSERT(outer1 < outer2);
    583     int mid = outer1 ^ outer2 ^ 3;
    584     SkScalar lineSlop =  ptMax * ptMax * 0.00001f;  // this multiplier is pulled out of the air
    585     return pt_to_line(quad[mid], quad[outer1], quad[outer2]) <= lineSlop;
    586 }
    587 
    588 static bool conic_in_line(const SkConic& conic) {
    589     return quad_in_line(conic.fPts);
    590 }
    591 
    592 SkPathStroker::ReductionType SkPathStroker::CheckCubicLinear(const SkPoint cubic[4],
    593         SkPoint reduction[3], const SkPoint** tangentPtPtr) {
    594     bool degenerateAB = degenerate_vector(cubic[1] - cubic[0]);
    595     bool degenerateBC = degenerate_vector(cubic[2] - cubic[1]);
    596     bool degenerateCD = degenerate_vector(cubic[3] - cubic[2]);
    597     if (degenerateAB & degenerateBC & degenerateCD) {
    598         return kPoint_ReductionType;
    599     }
    600     if (degenerateAB + degenerateBC + degenerateCD == 2) {
    601         return kLine_ReductionType;
    602     }
    603     if (!cubic_in_line(cubic)) {
    604         *tangentPtPtr = degenerateAB ? &cubic[2] : &cubic[1];
    605         return kQuad_ReductionType;
    606     }
    607     SkScalar tValues[3];
    608     int count = SkFindCubicMaxCurvature(cubic, tValues);
    609     if (count == 0) {
    610         return kLine_ReductionType;
    611     }
    612     int rCount = 0;
    613     // Now loop over the t-values, and reject any that evaluate to either end-point
    614     for (int index = 0; index < count; ++index) {
    615         SkScalar t = tValues[index];
    616         SkEvalCubicAt(cubic, t, &reduction[rCount], nullptr, nullptr);
    617         if (reduction[rCount] != cubic[0] && reduction[rCount] != cubic[3]) {
    618             ++rCount;
    619         }
    620     }
    621     if (rCount == 0) {
    622         return kLine_ReductionType;
    623     }
    624     static_assert(kQuad_ReductionType + 1 == kDegenerate_ReductionType, "enum_out_of_whack");
    625     static_assert(kQuad_ReductionType + 2 == kDegenerate2_ReductionType, "enum_out_of_whack");
    626     static_assert(kQuad_ReductionType + 3 == kDegenerate3_ReductionType, "enum_out_of_whack");
    627 
    628     return (ReductionType) (kQuad_ReductionType + rCount);
    629 }
    630 
    631 SkPathStroker::ReductionType SkPathStroker::CheckConicLinear(const SkConic& conic,
    632         SkPoint* reduction) {
    633     bool degenerateAB = degenerate_vector(conic.fPts[1] - conic.fPts[0]);
    634     bool degenerateBC = degenerate_vector(conic.fPts[2] - conic.fPts[1]);
    635     if (degenerateAB & degenerateBC) {
    636         return kPoint_ReductionType;
    637     }
    638     if (degenerateAB | degenerateBC) {
    639         return kLine_ReductionType;
    640     }
    641     if (!conic_in_line(conic)) {
    642         return kQuad_ReductionType;
    643     }
    644 #if 0   // once findMaxCurvature is implemented, this will be a better solution
    645     SkScalar t;
    646     if (!conic.findMaxCurvature(&t) || 0 == t) {
    647         return kLine_ReductionType;
    648     }
    649 #else  // but for now, use extrema instead
    650     SkScalar xT = 0, yT = 0;
    651     (void) conic.findXExtrema(&xT);
    652     (void) conic.findYExtrema(&yT);
    653     SkScalar t = SkTMax(xT, yT);
    654     if (0 == t) {
    655         return kLine_ReductionType;
    656     }
    657 #endif
    658     conic.evalAt(t, reduction, nullptr);
    659     return kDegenerate_ReductionType;
    660 }
    661 
    662 SkPathStroker::ReductionType SkPathStroker::CheckQuadLinear(const SkPoint quad[3],
    663         SkPoint* reduction) {
    664     bool degenerateAB = degenerate_vector(quad[1] - quad[0]);
    665     bool degenerateBC = degenerate_vector(quad[2] - quad[1]);
    666     if (degenerateAB & degenerateBC) {
    667         return kPoint_ReductionType;
    668     }
    669     if (degenerateAB | degenerateBC) {
    670         return kLine_ReductionType;
    671     }
    672     if (!quad_in_line(quad)) {
    673         return kQuad_ReductionType;
    674     }
    675     SkScalar t = SkFindQuadMaxCurvature(quad);
    676     if (0 == t) {
    677         return kLine_ReductionType;
    678     }
    679     *reduction = SkEvalQuadAt(quad, t);
    680     return kDegenerate_ReductionType;
    681 }
    682 
    683 void SkPathStroker::conicTo(const SkPoint& pt1, const SkPoint& pt2, SkScalar weight) {
    684     const SkConic conic(fPrevPt, pt1, pt2, weight);
    685     SkPoint reduction;
    686     ReductionType reductionType = CheckConicLinear(conic, &reduction);
    687     if (kPoint_ReductionType == reductionType) {
    688         /* If the stroke consists of a moveTo followed by a degenerate curve, treat it
    689             as if it were followed by a zero-length line. Lines without length
    690             can have square and round end caps. */
    691         this->lineTo(pt2);
    692         return;
    693     }
    694     if (kLine_ReductionType == reductionType) {
    695         this->lineTo(pt2);
    696         return;
    697     }
    698     if (kDegenerate_ReductionType == reductionType) {
    699         this->lineTo(reduction);
    700         SkStrokerPriv::JoinProc saveJoiner = fJoiner;
    701         fJoiner = SkStrokerPriv::JoinFactory(SkPaint::kRound_Join);
    702         this->lineTo(pt2);
    703         fJoiner = saveJoiner;
    704         return;
    705     }
    706     SkASSERT(kQuad_ReductionType == reductionType);
    707     SkVector normalAB, unitAB, normalBC, unitBC;
    708     if (!this->preJoinTo(pt1, &normalAB, &unitAB, false)) {
    709         this->lineTo(pt2);
    710         return;
    711     }
    712     SkQuadConstruct quadPts;
    713     this->init(kOuter_StrokeType, &quadPts, 0, 1);
    714     (void) this->conicStroke(conic, &quadPts);
    715     this->init(kInner_StrokeType, &quadPts, 0, 1);
    716     (void) this->conicStroke(conic, &quadPts);
    717     this->setConicEndNormal(conic, normalAB, unitAB, &normalBC, &unitBC);
    718     this->postJoinTo(pt2, normalBC, unitBC);
    719 }
    720 
    721 void SkPathStroker::quadTo(const SkPoint& pt1, const SkPoint& pt2) {
    722     const SkPoint quad[3] = { fPrevPt, pt1, pt2 };
    723     SkPoint reduction;
    724     ReductionType reductionType = CheckQuadLinear(quad, &reduction);
    725     if (kPoint_ReductionType == reductionType) {
    726         /* If the stroke consists of a moveTo followed by a degenerate curve, treat it
    727             as if it were followed by a zero-length line. Lines without length
    728             can have square and round end caps. */
    729         this->lineTo(pt2);
    730         return;
    731     }
    732     if (kLine_ReductionType == reductionType) {
    733         this->lineTo(pt2);
    734         return;
    735     }
    736     if (kDegenerate_ReductionType == reductionType) {
    737         this->lineTo(reduction);
    738         SkStrokerPriv::JoinProc saveJoiner = fJoiner;
    739         fJoiner = SkStrokerPriv::JoinFactory(SkPaint::kRound_Join);
    740         this->lineTo(pt2);
    741         fJoiner = saveJoiner;
    742         return;
    743     }
    744     SkASSERT(kQuad_ReductionType == reductionType);
    745     SkVector normalAB, unitAB, normalBC, unitBC;
    746     if (!this->preJoinTo(pt1, &normalAB, &unitAB, false)) {
    747         this->lineTo(pt2);
    748         return;
    749     }
    750     SkQuadConstruct quadPts;
    751     this->init(kOuter_StrokeType, &quadPts, 0, 1);
    752     (void) this->quadStroke(quad, &quadPts);
    753     this->init(kInner_StrokeType, &quadPts, 0, 1);
    754     (void) this->quadStroke(quad, &quadPts);
    755     this->setQuadEndNormal(quad, normalAB, unitAB, &normalBC, &unitBC);
    756 
    757     this->postJoinTo(pt2, normalBC, unitBC);
    758 }
    759 
    760 // Given a point on the curve and its derivative, scale the derivative by the radius, and
    761 // compute the perpendicular point and its tangent.
    762 void SkPathStroker::setRayPts(const SkPoint& tPt, SkVector* dxy, SkPoint* onPt,
    763         SkPoint* tangent) const {
    764     SkPoint oldDxy = *dxy;
    765     if (!dxy->setLength(fRadius)) {  // consider moving double logic into SkPoint::setLength
    766         double xx = oldDxy.fX;
    767         double yy = oldDxy.fY;
    768         double dscale = fRadius / sqrt(xx * xx + yy * yy);
    769         dxy->fX = SkDoubleToScalar(xx * dscale);
    770         dxy->fY = SkDoubleToScalar(yy * dscale);
    771     }
    772     SkScalar axisFlip = SkIntToScalar(fStrokeType);  // go opposite ways for outer, inner
    773     onPt->fX = tPt.fX + axisFlip * dxy->fY;
    774     onPt->fY = tPt.fY - axisFlip * dxy->fX;
    775     if (tangent) {
    776         tangent->fX = onPt->fX + dxy->fX;
    777         tangent->fY = onPt->fY + dxy->fY;
    778     }
    779 }
    780 
    781 // Given a conic and t, return the point on curve, its perpendicular, and the perpendicular tangent.
    782 // Returns false if the perpendicular could not be computed (because the derivative collapsed to 0)
    783 void SkPathStroker::conicPerpRay(const SkConic& conic, SkScalar t, SkPoint* tPt, SkPoint* onPt,
    784         SkPoint* tangent) const {
    785     SkVector dxy;
    786     conic.evalAt(t, tPt, &dxy);
    787     if (dxy.fX == 0 && dxy.fY == 0) {
    788         dxy = conic.fPts[2] - conic.fPts[0];
    789     }
    790     this->setRayPts(*tPt, &dxy, onPt, tangent);
    791 }
    792 
    793 // Given a conic and a t range, find the start and end if they haven't been found already.
    794 void SkPathStroker::conicQuadEnds(const SkConic& conic, SkQuadConstruct* quadPts) const {
    795     if (!quadPts->fStartSet) {
    796         SkPoint conicStartPt;
    797         this->conicPerpRay(conic, quadPts->fStartT, &conicStartPt, &quadPts->fQuad[0],
    798                 &quadPts->fTangentStart);
    799         quadPts->fStartSet = true;
    800     }
    801     if (!quadPts->fEndSet) {
    802         SkPoint conicEndPt;
    803         this->conicPerpRay(conic, quadPts->fEndT, &conicEndPt, &quadPts->fQuad[2],
    804                 &quadPts->fTangentEnd);
    805         quadPts->fEndSet = true;
    806     }
    807 }
    808 
    809 
    810 // Given a cubic and t, return the point on curve, its perpendicular, and the perpendicular tangent.
    811 void SkPathStroker::cubicPerpRay(const SkPoint cubic[4], SkScalar t, SkPoint* tPt, SkPoint* onPt,
    812         SkPoint* tangent) const {
    813     SkVector dxy;
    814     SkPoint chopped[7];
    815     SkEvalCubicAt(cubic, t, tPt, &dxy, nullptr);
    816     if (dxy.fX == 0 && dxy.fY == 0) {
    817         const SkPoint* cPts = cubic;
    818         if (SkScalarNearlyZero(t)) {
    819             dxy = cubic[2] - cubic[0];
    820         } else if (SkScalarNearlyZero(1 - t)) {
    821             dxy = cubic[3] - cubic[1];
    822         } else {
    823             // If the cubic inflection falls on the cusp, subdivide the cubic
    824             // to find the tangent at that point.
    825             SkChopCubicAt(cubic, chopped, t);
    826             dxy = chopped[3] - chopped[2];
    827             if (dxy.fX == 0 && dxy.fY == 0) {
    828                 dxy = chopped[3] - chopped[1];
    829                 cPts = chopped;
    830             }
    831         }
    832         if (dxy.fX == 0 && dxy.fY == 0) {
    833             dxy = cPts[3] - cPts[0];
    834         }
    835     }
    836     setRayPts(*tPt, &dxy, onPt, tangent);
    837 }
    838 
    839 // Given a cubic and a t range, find the start and end if they haven't been found already.
    840 void SkPathStroker::cubicQuadEnds(const SkPoint cubic[4], SkQuadConstruct* quadPts) {
    841     if (!quadPts->fStartSet) {
    842         SkPoint cubicStartPt;
    843         this->cubicPerpRay(cubic, quadPts->fStartT, &cubicStartPt, &quadPts->fQuad[0],
    844                 &quadPts->fTangentStart);
    845         quadPts->fStartSet = true;
    846     }
    847     if (!quadPts->fEndSet) {
    848         SkPoint cubicEndPt;
    849         this->cubicPerpRay(cubic, quadPts->fEndT, &cubicEndPt, &quadPts->fQuad[2],
    850                 &quadPts->fTangentEnd);
    851         quadPts->fEndSet = true;
    852     }
    853 }
    854 
    855 void SkPathStroker::cubicQuadMid(const SkPoint cubic[4], const SkQuadConstruct* quadPts,
    856         SkPoint* mid) const {
    857     SkPoint cubicMidPt;
    858     this->cubicPerpRay(cubic, quadPts->fMidT, &cubicMidPt, mid, nullptr);
    859 }
    860 
    861 // Given a quad and t, return the point on curve, its perpendicular, and the perpendicular tangent.
    862 void SkPathStroker::quadPerpRay(const SkPoint quad[3], SkScalar t, SkPoint* tPt, SkPoint* onPt,
    863         SkPoint* tangent) const {
    864     SkVector dxy;
    865     SkEvalQuadAt(quad, t, tPt, &dxy);
    866     if (dxy.fX == 0 && dxy.fY == 0) {
    867         dxy = quad[2] - quad[0];
    868     }
    869     setRayPts(*tPt, &dxy, onPt, tangent);
    870 }
    871 
    872 // Find the intersection of the stroke tangents to construct a stroke quad.
    873 // Return whether the stroke is a degenerate (a line), a quad, or must be split.
    874 // Optionally compute the quad's control point.
    875 SkPathStroker::ResultType SkPathStroker::intersectRay(SkQuadConstruct* quadPts,
    876         IntersectRayType intersectRayType  STROKER_DEBUG_PARAMS(int depth)) const {
    877     const SkPoint& start = quadPts->fQuad[0];
    878     const SkPoint& end = quadPts->fQuad[2];
    879     SkVector aLen = quadPts->fTangentStart - start;
    880     SkVector bLen = quadPts->fTangentEnd - end;
    881     /* Slopes match when denom goes to zero:
    882                       axLen / ayLen ==                   bxLen / byLen
    883     (ayLen * byLen) * axLen / ayLen == (ayLen * byLen) * bxLen / byLen
    884              byLen  * axLen         ==  ayLen          * bxLen
    885              byLen  * axLen         -   ayLen          * bxLen         ( == denom )
    886      */
    887     SkScalar denom = aLen.cross(bLen);
    888     if (denom == 0 || !SkScalarIsFinite(denom)) {
    889         quadPts->fOppositeTangents = aLen.dot(bLen) < 0;
    890         return STROKER_RESULT(kDegenerate_ResultType, depth, quadPts, "denom == 0");
    891     }
    892     quadPts->fOppositeTangents = false;
    893     SkVector ab0 = start - end;
    894     SkScalar numerA = bLen.cross(ab0);
    895     SkScalar numerB = aLen.cross(ab0);
    896     if ((numerA >= 0) == (numerB >= 0)) { // if the control point is outside the quad ends
    897         // if the perpendicular distances from the quad points to the opposite tangent line
    898         // are small, a straight line is good enough
    899         SkScalar dist1 = pt_to_line(start, end, quadPts->fTangentEnd);
    900         SkScalar dist2 = pt_to_line(end, start, quadPts->fTangentStart);
    901         if (SkTMax(dist1, dist2) <= fInvResScaleSquared) {
    902             return STROKER_RESULT(kDegenerate_ResultType, depth, quadPts,
    903                     "SkTMax(dist1=%g, dist2=%g) <= fInvResScaleSquared", dist1, dist2);
    904         }
    905         return STROKER_RESULT(kSplit_ResultType, depth, quadPts,
    906                 "(numerA=%g >= 0) == (numerB=%g >= 0)", numerA, numerB);
    907     }
    908     // check to see if the denominator is teeny relative to the numerator
    909     // if the offset by one will be lost, the ratio is too large
    910     numerA /= denom;
    911     bool validDivide = numerA > numerA - 1;
    912     if (validDivide) {
    913         if (kCtrlPt_RayType == intersectRayType) {
    914             SkPoint* ctrlPt = &quadPts->fQuad[1];
    915             // the intersection of the tangents need not be on the tangent segment
    916             // so 0 <= numerA <= 1 is not necessarily true
    917             ctrlPt->fX = start.fX * (1 - numerA) + quadPts->fTangentStart.fX * numerA;
    918             ctrlPt->fY = start.fY * (1 - numerA) + quadPts->fTangentStart.fY * numerA;
    919         }
    920         return STROKER_RESULT(kQuad_ResultType, depth, quadPts,
    921                 "(numerA=%g >= 0) != (numerB=%g >= 0)", numerA, numerB);
    922     }
    923     quadPts->fOppositeTangents = aLen.dot(bLen) < 0;
    924     // if the lines are parallel, straight line is good enough
    925     return STROKER_RESULT(kDegenerate_ResultType, depth, quadPts,
    926             "SkScalarNearlyZero(denom=%g)", denom);
    927 }
    928 
    929 // Given a cubic and a t-range, determine if the stroke can be described by a quadratic.
    930 SkPathStroker::ResultType SkPathStroker::tangentsMeet(const SkPoint cubic[4],
    931         SkQuadConstruct* quadPts) {
    932     this->cubicQuadEnds(cubic, quadPts);
    933     return this->intersectRay(quadPts, kResultType_RayType  STROKER_DEBUG_PARAMS(fRecursionDepth));
    934 }
    935 
    936 // Intersect the line with the quad and return the t values on the quad where the line crosses.
    937 static int intersect_quad_ray(const SkPoint line[2], const SkPoint quad[3], SkScalar roots[2]) {
    938     SkVector vec = line[1] - line[0];
    939     SkScalar r[3];
    940     for (int n = 0; n < 3; ++n) {
    941         r[n] = (quad[n].fY - line[0].fY) * vec.fX - (quad[n].fX - line[0].fX) * vec.fY;
    942     }
    943     SkScalar A = r[2];
    944     SkScalar B = r[1];
    945     SkScalar C = r[0];
    946     A += C - 2 * B;  // A = a - 2*b + c
    947     B -= C;  // B = -(b - c)
    948     return SkFindUnitQuadRoots(A, 2 * B, C, roots);
    949 }
    950 
    951 // Return true if the point is close to the bounds of the quad. This is used as a quick reject.
    952 bool SkPathStroker::ptInQuadBounds(const SkPoint quad[3], const SkPoint& pt) const {
    953     SkScalar xMin = SkTMin(SkTMin(quad[0].fX, quad[1].fX), quad[2].fX);
    954     if (pt.fX + fInvResScale < xMin) {
    955         return false;
    956     }
    957     SkScalar xMax = SkTMax(SkTMax(quad[0].fX, quad[1].fX), quad[2].fX);
    958     if (pt.fX - fInvResScale > xMax) {
    959         return false;
    960     }
    961     SkScalar yMin = SkTMin(SkTMin(quad[0].fY, quad[1].fY), quad[2].fY);
    962     if (pt.fY + fInvResScale < yMin) {
    963         return false;
    964     }
    965     SkScalar yMax = SkTMax(SkTMax(quad[0].fY, quad[1].fY), quad[2].fY);
    966     if (pt.fY - fInvResScale > yMax) {
    967         return false;
    968     }
    969     return true;
    970 }
    971 
    972 static bool points_within_dist(const SkPoint& nearPt, const SkPoint& farPt, SkScalar limit) {
    973     return SkPointPriv::DistanceToSqd(nearPt, farPt) <= limit * limit;
    974 }
    975 
    976 static bool sharp_angle(const SkPoint quad[3]) {
    977     SkVector smaller = quad[1] - quad[0];
    978     SkVector larger = quad[1] - quad[2];
    979     SkScalar smallerLen = SkPointPriv::LengthSqd(smaller);
    980     SkScalar largerLen = SkPointPriv::LengthSqd(larger);
    981     if (smallerLen > largerLen) {
    982         SkTSwap(smaller, larger);
    983         largerLen = smallerLen;
    984     }
    985     if (!smaller.setLength(largerLen)) {
    986         return false;
    987     }
    988     SkScalar dot = smaller.dot(larger);
    989     return dot > 0;
    990 }
    991 
    992 SkPathStroker::ResultType SkPathStroker::strokeCloseEnough(const SkPoint stroke[3],
    993         const SkPoint ray[2], SkQuadConstruct* quadPts  STROKER_DEBUG_PARAMS(int depth)) const {
    994     SkPoint strokeMid = SkEvalQuadAt(stroke, SK_ScalarHalf);
    995     // measure the distance from the curve to the quad-stroke midpoint, compare to radius
    996     if (points_within_dist(ray[0], strokeMid, fInvResScale)) {  // if the difference is small
    997         if (sharp_angle(quadPts->fQuad)) {
    998             return STROKER_RESULT(kSplit_ResultType, depth, quadPts,
    999                     "sharp_angle (1) =%g,%g, %g,%g, %g,%g",
   1000                     quadPts->fQuad[0].fX, quadPts->fQuad[0].fY,
   1001                     quadPts->fQuad[1].fX, quadPts->fQuad[1].fY,
   1002                     quadPts->fQuad[2].fX, quadPts->fQuad[2].fY);
   1003         }
   1004         return STROKER_RESULT(kQuad_ResultType, depth, quadPts,
   1005                 "points_within_dist(ray[0]=%g,%g, strokeMid=%g,%g, fInvResScale=%g)",
   1006                 ray[0].fX, ray[0].fY, strokeMid.fX, strokeMid.fY, fInvResScale);
   1007     }
   1008     // measure the distance to quad's bounds (quick reject)
   1009         // an alternative : look for point in triangle
   1010     if (!ptInQuadBounds(stroke, ray[0])) {  // if far, subdivide
   1011         return STROKER_RESULT(kSplit_ResultType, depth, quadPts,
   1012                 "!pt_in_quad_bounds(stroke=(%g,%g %g,%g %g,%g), ray[0]=%g,%g)",
   1013                 stroke[0].fX, stroke[0].fY, stroke[1].fX, stroke[1].fY, stroke[2].fX, stroke[2].fY,
   1014                 ray[0].fX, ray[0].fY);
   1015     }
   1016     // measure the curve ray distance to the quad-stroke
   1017     SkScalar roots[2];
   1018     int rootCount = intersect_quad_ray(ray, stroke, roots);
   1019     if (rootCount != 1) {
   1020         return STROKER_RESULT(kSplit_ResultType, depth, quadPts,
   1021                 "rootCount=%d != 1", rootCount);
   1022     }
   1023     SkPoint quadPt = SkEvalQuadAt(stroke, roots[0]);
   1024     SkScalar error = fInvResScale * (SK_Scalar1 - SkScalarAbs(roots[0] - 0.5f) * 2);
   1025     if (points_within_dist(ray[0], quadPt, error)) {  // if the difference is small, we're done
   1026         if (sharp_angle(quadPts->fQuad)) {
   1027             return STROKER_RESULT(kSplit_ResultType, depth, quadPts,
   1028                     "sharp_angle (2) =%g,%g, %g,%g, %g,%g",
   1029                     quadPts->fQuad[0].fX, quadPts->fQuad[0].fY,
   1030                     quadPts->fQuad[1].fX, quadPts->fQuad[1].fY,
   1031                     quadPts->fQuad[2].fX, quadPts->fQuad[2].fY);
   1032         }
   1033         return STROKER_RESULT(kQuad_ResultType, depth, quadPts,
   1034                 "points_within_dist(ray[0]=%g,%g, quadPt=%g,%g, error=%g)",
   1035                 ray[0].fX, ray[0].fY, quadPt.fX, quadPt.fY, error);
   1036     }
   1037     // otherwise, subdivide
   1038     return STROKER_RESULT(kSplit_ResultType, depth, quadPts, "%s", "fall through");
   1039 }
   1040 
   1041 SkPathStroker::ResultType SkPathStroker::compareQuadCubic(const SkPoint cubic[4],
   1042         SkQuadConstruct* quadPts) {
   1043     // get the quadratic approximation of the stroke
   1044     this->cubicQuadEnds(cubic, quadPts);
   1045     ResultType resultType = this->intersectRay(quadPts, kCtrlPt_RayType
   1046             STROKER_DEBUG_PARAMS(fRecursionDepth) );
   1047     if (resultType != kQuad_ResultType) {
   1048         return resultType;
   1049     }
   1050     // project a ray from the curve to the stroke
   1051     SkPoint ray[2];  // points near midpoint on quad, midpoint on cubic
   1052     this->cubicPerpRay(cubic, quadPts->fMidT, &ray[1], &ray[0], nullptr);
   1053     return this->strokeCloseEnough(quadPts->fQuad, ray, quadPts
   1054             STROKER_DEBUG_PARAMS(fRecursionDepth));
   1055 }
   1056 
   1057 SkPathStroker::ResultType SkPathStroker::compareQuadConic(const SkConic& conic,
   1058         SkQuadConstruct* quadPts) const {
   1059     // get the quadratic approximation of the stroke
   1060     this->conicQuadEnds(conic, quadPts);
   1061     ResultType resultType = this->intersectRay(quadPts, kCtrlPt_RayType
   1062             STROKER_DEBUG_PARAMS(fRecursionDepth) );
   1063     if (resultType != kQuad_ResultType) {
   1064         return resultType;
   1065     }
   1066     // project a ray from the curve to the stroke
   1067     SkPoint ray[2];  // points near midpoint on quad, midpoint on conic
   1068     this->conicPerpRay(conic, quadPts->fMidT, &ray[1], &ray[0], nullptr);
   1069     return this->strokeCloseEnough(quadPts->fQuad, ray, quadPts
   1070             STROKER_DEBUG_PARAMS(fRecursionDepth));
   1071 }
   1072 
   1073 SkPathStroker::ResultType SkPathStroker::compareQuadQuad(const SkPoint quad[3],
   1074         SkQuadConstruct* quadPts) {
   1075     // get the quadratic approximation of the stroke
   1076     if (!quadPts->fStartSet) {
   1077         SkPoint quadStartPt;
   1078         this->quadPerpRay(quad, quadPts->fStartT, &quadStartPt, &quadPts->fQuad[0],
   1079                 &quadPts->fTangentStart);
   1080         quadPts->fStartSet = true;
   1081     }
   1082     if (!quadPts->fEndSet) {
   1083         SkPoint quadEndPt;
   1084         this->quadPerpRay(quad, quadPts->fEndT, &quadEndPt, &quadPts->fQuad[2],
   1085                 &quadPts->fTangentEnd);
   1086         quadPts->fEndSet = true;
   1087     }
   1088     ResultType resultType = this->intersectRay(quadPts, kCtrlPt_RayType
   1089             STROKER_DEBUG_PARAMS(fRecursionDepth));
   1090     if (resultType != kQuad_ResultType) {
   1091         return resultType;
   1092     }
   1093     // project a ray from the curve to the stroke
   1094     SkPoint ray[2];
   1095     this->quadPerpRay(quad, quadPts->fMidT, &ray[1], &ray[0], nullptr);
   1096     return this->strokeCloseEnough(quadPts->fQuad, ray, quadPts
   1097             STROKER_DEBUG_PARAMS(fRecursionDepth));
   1098 }
   1099 
   1100 void SkPathStroker::addDegenerateLine(const SkQuadConstruct* quadPts) {
   1101     const SkPoint* quad = quadPts->fQuad;
   1102     SkPath* path = fStrokeType == kOuter_StrokeType ? &fOuter : &fInner;
   1103     path->lineTo(quad[2].fX, quad[2].fY);
   1104 }
   1105 
   1106 bool SkPathStroker::cubicMidOnLine(const SkPoint cubic[4], const SkQuadConstruct* quadPts) const {
   1107     SkPoint strokeMid;
   1108     this->cubicQuadMid(cubic, quadPts, &strokeMid);
   1109     SkScalar dist = pt_to_line(strokeMid, quadPts->fQuad[0], quadPts->fQuad[2]);
   1110     return dist < fInvResScaleSquared;
   1111 }
   1112 
   1113 bool SkPathStroker::cubicStroke(const SkPoint cubic[4], SkQuadConstruct* quadPts) {
   1114     if (!fFoundTangents) {
   1115         ResultType resultType = this->tangentsMeet(cubic, quadPts);
   1116         if (kQuad_ResultType != resultType) {
   1117             if ((kDegenerate_ResultType == resultType
   1118                     || points_within_dist(quadPts->fQuad[0], quadPts->fQuad[2],
   1119                     fInvResScale)) && cubicMidOnLine(cubic, quadPts)) {
   1120                 addDegenerateLine(quadPts);
   1121                 return true;
   1122             }
   1123         } else {
   1124             fFoundTangents = true;
   1125         }
   1126     }
   1127     if (fFoundTangents) {
   1128         ResultType resultType = this->compareQuadCubic(cubic, quadPts);
   1129         if (kQuad_ResultType == resultType) {
   1130             SkPath* path = fStrokeType == kOuter_StrokeType ? &fOuter : &fInner;
   1131             const SkPoint* stroke = quadPts->fQuad;
   1132             path->quadTo(stroke[1].fX, stroke[1].fY, stroke[2].fX, stroke[2].fY);
   1133             return true;
   1134         }
   1135         if (kDegenerate_ResultType == resultType) {
   1136             if (!quadPts->fOppositeTangents) {
   1137               addDegenerateLine(quadPts);
   1138               return true;
   1139             }
   1140         }
   1141     }
   1142     if (!SkScalarIsFinite(quadPts->fQuad[2].fX) || !SkScalarIsFinite(quadPts->fQuad[2].fY)) {
   1143         return false;  // just abort if projected quad isn't representable
   1144     }
   1145 #if QUAD_STROKE_APPROX_EXTENDED_DEBUGGING
   1146     SkDEBUGCODE(gMaxRecursion[fFoundTangents] = SkTMax(gMaxRecursion[fFoundTangents],
   1147             fRecursionDepth + 1));
   1148 #endif
   1149     if (++fRecursionDepth > kRecursiveLimits[fFoundTangents]) {
   1150         return false;  // just abort if projected quad isn't representable
   1151     }
   1152     SkQuadConstruct half;
   1153     if (!half.initWithStart(quadPts)) {
   1154         addDegenerateLine(quadPts);
   1155         return true;
   1156     }
   1157     if (!this->cubicStroke(cubic, &half)) {
   1158         return false;
   1159     }
   1160     if (!half.initWithEnd(quadPts)) {
   1161         addDegenerateLine(quadPts);
   1162         return true;
   1163     }
   1164     if (!this->cubicStroke(cubic, &half)) {
   1165         return false;
   1166     }
   1167     --fRecursionDepth;
   1168     return true;
   1169 }
   1170 
   1171 bool SkPathStroker::conicStroke(const SkConic& conic, SkQuadConstruct* quadPts) {
   1172     ResultType resultType = this->compareQuadConic(conic, quadPts);
   1173     if (kQuad_ResultType == resultType) {
   1174         const SkPoint* stroke = quadPts->fQuad;
   1175         SkPath* path = fStrokeType == kOuter_StrokeType ? &fOuter : &fInner;
   1176         path->quadTo(stroke[1].fX, stroke[1].fY, stroke[2].fX, stroke[2].fY);
   1177         return true;
   1178     }
   1179     if (kDegenerate_ResultType == resultType) {
   1180         addDegenerateLine(quadPts);
   1181         return true;
   1182     }
   1183 #if QUAD_STROKE_APPROX_EXTENDED_DEBUGGING
   1184     SkDEBUGCODE(gMaxRecursion[kConic_RecursiveLimit] = SkTMax(gMaxRecursion[kConic_RecursiveLimit],
   1185             fRecursionDepth + 1));
   1186 #endif
   1187     if (++fRecursionDepth > kRecursiveLimits[kConic_RecursiveLimit]) {
   1188         return false;  // just abort if projected quad isn't representable
   1189     }
   1190     SkQuadConstruct half;
   1191     (void) half.initWithStart(quadPts);
   1192     if (!this->conicStroke(conic, &half)) {
   1193         return false;
   1194     }
   1195     (void) half.initWithEnd(quadPts);
   1196     if (!this->conicStroke(conic, &half)) {
   1197         return false;
   1198     }
   1199     --fRecursionDepth;
   1200     return true;
   1201 }
   1202 
   1203 bool SkPathStroker::quadStroke(const SkPoint quad[3], SkQuadConstruct* quadPts) {
   1204     ResultType resultType = this->compareQuadQuad(quad, quadPts);
   1205     if (kQuad_ResultType == resultType) {
   1206         const SkPoint* stroke = quadPts->fQuad;
   1207         SkPath* path = fStrokeType == kOuter_StrokeType ? &fOuter : &fInner;
   1208         path->quadTo(stroke[1].fX, stroke[1].fY, stroke[2].fX, stroke[2].fY);
   1209         return true;
   1210     }
   1211     if (kDegenerate_ResultType == resultType) {
   1212         addDegenerateLine(quadPts);
   1213         return true;
   1214     }
   1215 #if QUAD_STROKE_APPROX_EXTENDED_DEBUGGING
   1216     SkDEBUGCODE(gMaxRecursion[kQuad_RecursiveLimit] = SkTMax(gMaxRecursion[kQuad_RecursiveLimit],
   1217             fRecursionDepth + 1));
   1218 #endif
   1219     if (++fRecursionDepth > kRecursiveLimits[kQuad_RecursiveLimit]) {
   1220         return false;  // just abort if projected quad isn't representable
   1221     }
   1222     SkQuadConstruct half;
   1223     (void) half.initWithStart(quadPts);
   1224     if (!this->quadStroke(quad, &half)) {
   1225         return false;
   1226     }
   1227     (void) half.initWithEnd(quadPts);
   1228     if (!this->quadStroke(quad, &half)) {
   1229         return false;
   1230     }
   1231     --fRecursionDepth;
   1232     return true;
   1233 }
   1234 
   1235 void SkPathStroker::cubicTo(const SkPoint& pt1, const SkPoint& pt2,
   1236                             const SkPoint& pt3) {
   1237     const SkPoint cubic[4] = { fPrevPt, pt1, pt2, pt3 };
   1238     SkPoint reduction[3];
   1239     const SkPoint* tangentPt;
   1240     ReductionType reductionType = CheckCubicLinear(cubic, reduction, &tangentPt);
   1241     if (kPoint_ReductionType == reductionType) {
   1242         /* If the stroke consists of a moveTo followed by a degenerate curve, treat it
   1243             as if it were followed by a zero-length line. Lines without length
   1244             can have square and round end caps. */
   1245         this->lineTo(pt3);
   1246         return;
   1247     }
   1248     if (kLine_ReductionType == reductionType) {
   1249         this->lineTo(pt3);
   1250         return;
   1251     }
   1252     if (kDegenerate_ReductionType <= reductionType && kDegenerate3_ReductionType >= reductionType) {
   1253         this->lineTo(reduction[0]);
   1254         SkStrokerPriv::JoinProc saveJoiner = fJoiner;
   1255         fJoiner = SkStrokerPriv::JoinFactory(SkPaint::kRound_Join);
   1256         if (kDegenerate2_ReductionType <= reductionType) {
   1257             this->lineTo(reduction[1]);
   1258         }
   1259         if (kDegenerate3_ReductionType == reductionType) {
   1260             this->lineTo(reduction[2]);
   1261         }
   1262         this->lineTo(pt3);
   1263         fJoiner = saveJoiner;
   1264         return;
   1265     }
   1266     SkASSERT(kQuad_ReductionType == reductionType);
   1267     SkVector normalAB, unitAB, normalCD, unitCD;
   1268     if (!this->preJoinTo(*tangentPt, &normalAB, &unitAB, false)) {
   1269         this->lineTo(pt3);
   1270         return;
   1271     }
   1272     SkScalar tValues[2];
   1273     int count = SkFindCubicInflections(cubic, tValues);
   1274     SkScalar lastT = 0;
   1275     for (int index = 0; index <= count; ++index) {
   1276         SkScalar nextT = index < count ? tValues[index] : 1;
   1277         SkQuadConstruct quadPts;
   1278         this->init(kOuter_StrokeType, &quadPts, lastT, nextT);
   1279         (void) this->cubicStroke(cubic, &quadPts);
   1280         this->init(kInner_StrokeType, &quadPts, lastT, nextT);
   1281         (void) this->cubicStroke(cubic, &quadPts);
   1282         lastT = nextT;
   1283     }
   1284     // emit the join even if one stroke succeeded but the last one failed
   1285     // this avoids reversing an inner stroke with a partial path followed by another moveto
   1286     this->setCubicEndNormal(cubic, normalAB, unitAB, &normalCD, &unitCD);
   1287 
   1288     this->postJoinTo(pt3, normalCD, unitCD);
   1289 }
   1290 
   1291 ///////////////////////////////////////////////////////////////////////////////
   1292 ///////////////////////////////////////////////////////////////////////////////
   1293 
   1294 #include "SkPaintDefaults.h"
   1295 
   1296 SkStroke::SkStroke() {
   1297     fWidth      = SK_Scalar1;
   1298     fMiterLimit = SkPaintDefaults_MiterLimit;
   1299     fResScale   = 1;
   1300     fCap        = SkPaint::kDefault_Cap;
   1301     fJoin       = SkPaint::kDefault_Join;
   1302     fDoFill     = false;
   1303 }
   1304 
   1305 SkStroke::SkStroke(const SkPaint& p) {
   1306     fWidth      = p.getStrokeWidth();
   1307     fMiterLimit = p.getStrokeMiter();
   1308     fResScale   = 1;
   1309     fCap        = (uint8_t)p.getStrokeCap();
   1310     fJoin       = (uint8_t)p.getStrokeJoin();
   1311     fDoFill     = SkToU8(p.getStyle() == SkPaint::kStrokeAndFill_Style);
   1312 }
   1313 
   1314 SkStroke::SkStroke(const SkPaint& p, SkScalar width) {
   1315     fWidth      = width;
   1316     fMiterLimit = p.getStrokeMiter();
   1317     fResScale   = 1;
   1318     fCap        = (uint8_t)p.getStrokeCap();
   1319     fJoin       = (uint8_t)p.getStrokeJoin();
   1320     fDoFill     = SkToU8(p.getStyle() == SkPaint::kStrokeAndFill_Style);
   1321 }
   1322 
   1323 void SkStroke::setWidth(SkScalar width) {
   1324     SkASSERT(width >= 0);
   1325     fWidth = width;
   1326 }
   1327 
   1328 void SkStroke::setMiterLimit(SkScalar miterLimit) {
   1329     SkASSERT(miterLimit >= 0);
   1330     fMiterLimit = miterLimit;
   1331 }
   1332 
   1333 void SkStroke::setCap(SkPaint::Cap cap) {
   1334     SkASSERT((unsigned)cap < SkPaint::kCapCount);
   1335     fCap = SkToU8(cap);
   1336 }
   1337 
   1338 void SkStroke::setJoin(SkPaint::Join join) {
   1339     SkASSERT((unsigned)join < SkPaint::kJoinCount);
   1340     fJoin = SkToU8(join);
   1341 }
   1342 
   1343 ///////////////////////////////////////////////////////////////////////////////
   1344 
   1345 // If src==dst, then we use a tmp path to record the stroke, and then swap
   1346 // its contents with src when we're done.
   1347 class AutoTmpPath {
   1348 public:
   1349     AutoTmpPath(const SkPath& src, SkPath** dst) : fSrc(src) {
   1350         if (&src == *dst) {
   1351             *dst = &fTmpDst;
   1352             fSwapWithSrc = true;
   1353         } else {
   1354             (*dst)->reset();
   1355             fSwapWithSrc = false;
   1356         }
   1357     }
   1358 
   1359     ~AutoTmpPath() {
   1360         if (fSwapWithSrc) {
   1361             fTmpDst.swap(*const_cast<SkPath*>(&fSrc));
   1362         }
   1363     }
   1364 
   1365 private:
   1366     SkPath          fTmpDst;
   1367     const SkPath&   fSrc;
   1368     bool            fSwapWithSrc;
   1369 };
   1370 
   1371 void SkStroke::strokePath(const SkPath& src, SkPath* dst) const {
   1372     SkASSERT(dst);
   1373 
   1374     SkScalar radius = SkScalarHalf(fWidth);
   1375 
   1376     AutoTmpPath tmp(src, &dst);
   1377 
   1378     if (radius <= 0) {
   1379         return;
   1380     }
   1381 
   1382     // If src is really a rect, call our specialty strokeRect() method
   1383     {
   1384         SkRect rect;
   1385         bool isClosed;
   1386         SkPath::Direction dir;
   1387         if (src.isRect(&rect, &isClosed, &dir) && isClosed) {
   1388             this->strokeRect(rect, dst, dir);
   1389             // our answer should preserve the inverseness of the src
   1390             if (src.isInverseFillType()) {
   1391                 SkASSERT(!dst->isInverseFillType());
   1392                 dst->toggleInverseFillType();
   1393             }
   1394             return;
   1395         }
   1396     }
   1397 
   1398     // We can always ignore centers for stroke and fill convex line-only paths
   1399     // TODO: remove the line-only restriction
   1400     bool ignoreCenter = fDoFill && (src.getSegmentMasks() == SkPath::kLine_SegmentMask) &&
   1401                         src.isLastContourClosed() && src.isConvex();
   1402 
   1403     SkPathStroker   stroker(src, radius, fMiterLimit, this->getCap(), this->getJoin(),
   1404                             fResScale, ignoreCenter);
   1405     SkPath::Iter    iter(src, false);
   1406     SkPath::Verb    lastSegment = SkPath::kMove_Verb;
   1407 
   1408     for (;;) {
   1409         SkPoint  pts[4];
   1410         switch (iter.next(pts, false)) {
   1411             case SkPath::kMove_Verb:
   1412                 stroker.moveTo(pts[0]);
   1413                 break;
   1414             case SkPath::kLine_Verb:
   1415                 stroker.lineTo(pts[1], &iter);
   1416                 lastSegment = SkPath::kLine_Verb;
   1417                 break;
   1418             case SkPath::kQuad_Verb:
   1419                 stroker.quadTo(pts[1], pts[2]);
   1420                 lastSegment = SkPath::kQuad_Verb;
   1421                 break;
   1422             case SkPath::kConic_Verb: {
   1423                 stroker.conicTo(pts[1], pts[2], iter.conicWeight());
   1424                 lastSegment = SkPath::kConic_Verb;
   1425                 break;
   1426             } break;
   1427             case SkPath::kCubic_Verb:
   1428                 stroker.cubicTo(pts[1], pts[2], pts[3]);
   1429                 lastSegment = SkPath::kCubic_Verb;
   1430                 break;
   1431             case SkPath::kClose_Verb:
   1432                 if (SkPaint::kButt_Cap != this->getCap()) {
   1433                     /* If the stroke consists of a moveTo followed by a close, treat it
   1434                        as if it were followed by a zero-length line. Lines without length
   1435                        can have square and round end caps. */
   1436                     if (stroker.hasOnlyMoveTo()) {
   1437                         stroker.lineTo(stroker.moveToPt());
   1438                         goto ZERO_LENGTH;
   1439                     }
   1440                     /* If the stroke consists of a moveTo followed by one or more zero-length
   1441                        verbs, then followed by a close, treat is as if it were followed by a
   1442                        zero-length line. Lines without length can have square & round end caps. */
   1443                     if (stroker.isCurrentContourEmpty()) {
   1444                 ZERO_LENGTH:
   1445                         lastSegment = SkPath::kLine_Verb;
   1446                         break;
   1447                     }
   1448                 }
   1449                 stroker.close(lastSegment == SkPath::kLine_Verb);
   1450                 break;
   1451             case SkPath::kDone_Verb:
   1452                 goto DONE;
   1453         }
   1454     }
   1455 DONE:
   1456     stroker.done(dst, lastSegment == SkPath::kLine_Verb);
   1457 
   1458     if (fDoFill && !ignoreCenter) {
   1459         if (SkPathPriv::CheapIsFirstDirection(src, SkPathPriv::kCCW_FirstDirection)) {
   1460             dst->reverseAddPath(src);
   1461         } else {
   1462             dst->addPath(src);
   1463         }
   1464     } else {
   1465         //  Seems like we can assume that a 2-point src would always result in
   1466         //  a convex stroke, but testing has proved otherwise.
   1467         //  TODO: fix the stroker to make this assumption true (without making
   1468         //  it slower that the work that will be done in computeConvexity())
   1469 #if 0
   1470         // this test results in a non-convex stroke :(
   1471         static void test(SkCanvas* canvas) {
   1472             SkPoint pts[] = { 146.333328,  192.333328, 300.333344, 293.333344 };
   1473             SkPaint paint;
   1474             paint.setStrokeWidth(7);
   1475             paint.setStrokeCap(SkPaint::kRound_Cap);
   1476             canvas->drawLine(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY, paint);
   1477         }
   1478 #endif
   1479 #if 0
   1480         if (2 == src.countPoints()) {
   1481             dst->setIsConvex(true);
   1482         }
   1483 #endif
   1484     }
   1485 
   1486     // our answer should preserve the inverseness of the src
   1487     if (src.isInverseFillType()) {
   1488         SkASSERT(!dst->isInverseFillType());
   1489         dst->toggleInverseFillType();
   1490     }
   1491 }
   1492 
   1493 static SkPath::Direction reverse_direction(SkPath::Direction dir) {
   1494     static const SkPath::Direction gOpposite[] = { SkPath::kCCW_Direction, SkPath::kCW_Direction };
   1495     return gOpposite[dir];
   1496 }
   1497 
   1498 static void addBevel(SkPath* path, const SkRect& r, const SkRect& outer, SkPath::Direction dir) {
   1499     SkPoint pts[8];
   1500 
   1501     if (SkPath::kCW_Direction == dir) {
   1502         pts[0].set(r.fLeft, outer.fTop);
   1503         pts[1].set(r.fRight, outer.fTop);
   1504         pts[2].set(outer.fRight, r.fTop);
   1505         pts[3].set(outer.fRight, r.fBottom);
   1506         pts[4].set(r.fRight, outer.fBottom);
   1507         pts[5].set(r.fLeft, outer.fBottom);
   1508         pts[6].set(outer.fLeft, r.fBottom);
   1509         pts[7].set(outer.fLeft, r.fTop);
   1510     } else {
   1511         pts[7].set(r.fLeft, outer.fTop);
   1512         pts[6].set(r.fRight, outer.fTop);
   1513         pts[5].set(outer.fRight, r.fTop);
   1514         pts[4].set(outer.fRight, r.fBottom);
   1515         pts[3].set(r.fRight, outer.fBottom);
   1516         pts[2].set(r.fLeft, outer.fBottom);
   1517         pts[1].set(outer.fLeft, r.fBottom);
   1518         pts[0].set(outer.fLeft, r.fTop);
   1519     }
   1520     path->addPoly(pts, 8, true);
   1521 }
   1522 
   1523 void SkStroke::strokeRect(const SkRect& origRect, SkPath* dst,
   1524                           SkPath::Direction dir) const {
   1525     SkASSERT(dst != nullptr);
   1526     dst->reset();
   1527 
   1528     SkScalar radius = SkScalarHalf(fWidth);
   1529     if (radius <= 0) {
   1530         return;
   1531     }
   1532 
   1533     SkScalar rw = origRect.width();
   1534     SkScalar rh = origRect.height();
   1535     if ((rw < 0) ^ (rh < 0)) {
   1536         dir = reverse_direction(dir);
   1537     }
   1538     SkRect rect(origRect);
   1539     rect.sort();
   1540     // reassign these, now that we know they'll be >= 0
   1541     rw = rect.width();
   1542     rh = rect.height();
   1543 
   1544     SkRect r(rect);
   1545     r.outset(radius, radius);
   1546 
   1547     SkPaint::Join join = (SkPaint::Join)fJoin;
   1548     if (SkPaint::kMiter_Join == join && fMiterLimit < SK_ScalarSqrt2) {
   1549         join = SkPaint::kBevel_Join;
   1550     }
   1551 
   1552     switch (join) {
   1553         case SkPaint::kMiter_Join:
   1554             dst->addRect(r, dir);
   1555             break;
   1556         case SkPaint::kBevel_Join:
   1557             addBevel(dst, rect, r, dir);
   1558             break;
   1559         case SkPaint::kRound_Join:
   1560             dst->addRoundRect(r, radius, radius, dir);
   1561             break;
   1562         default:
   1563             break;
   1564     }
   1565 
   1566     if (fWidth < SkMinScalar(rw, rh) && !fDoFill) {
   1567         r = rect;
   1568         r.inset(radius, radius);
   1569         dst->addRect(r, reverse_direction(dir));
   1570     }
   1571 }
   1572