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 "SkPath.h"
11
12#define kMaxQuadSubdivide   5
13#define kMaxCubicSubdivide  7
14
15static inline bool degenerate_vector(const SkVector& v) {
16    return !SkPoint::CanNormalize(v.fX, v.fY);
17}
18
19static inline bool normals_too_curvy(const SkVector& norm0, SkVector& norm1) {
20    /*  root2/2 is a 45-degree angle
21        make this constant bigger for more subdivisions (but not >= 1)
22    */
23    static const SkScalar kFlatEnoughNormalDotProd =
24                                            SK_ScalarSqrt2/2 + SK_Scalar1/10;
25
26    SkASSERT(kFlatEnoughNormalDotProd > 0 &&
27             kFlatEnoughNormalDotProd < SK_Scalar1);
28
29    return SkPoint::DotProduct(norm0, norm1) <= kFlatEnoughNormalDotProd;
30}
31
32static inline bool normals_too_pinchy(const SkVector& norm0, SkVector& norm1) {
33    // if the dot-product is -1, then we are definitely too pinchy. We tweak
34    // that by an epsilon to ensure we have significant bits in our test
35    static const int kMinSigBitsForDot = 8;
36    static const SkScalar kDotEpsilon = FLT_EPSILON * (1 << kMinSigBitsForDot);
37    static const SkScalar kTooPinchyNormalDotProd = kDotEpsilon - 1;
38
39    // just some sanity asserts to help document the expected range
40    SkASSERT(kTooPinchyNormalDotProd >= -1);
41    SkASSERT(kTooPinchyNormalDotProd < SkDoubleToScalar(-0.999));
42
43    SkScalar dot = SkPoint::DotProduct(norm0, norm1);
44    return dot <= kTooPinchyNormalDotProd;
45}
46
47static bool set_normal_unitnormal(const SkPoint& before, const SkPoint& after,
48                                  SkScalar radius,
49                                  SkVector* normal, SkVector* unitNormal) {
50    if (!unitNormal->setNormalize(after.fX - before.fX, after.fY - before.fY)) {
51        return false;
52    }
53    unitNormal->rotateCCW();
54    unitNormal->scale(radius, normal);
55    return true;
56}
57
58static bool set_normal_unitnormal(const SkVector& vec,
59                                  SkScalar radius,
60                                  SkVector* normal, SkVector* unitNormal) {
61    if (!unitNormal->setNormalize(vec.fX, vec.fY)) {
62        return false;
63    }
64    unitNormal->rotateCCW();
65    unitNormal->scale(radius, normal);
66    return true;
67}
68
69///////////////////////////////////////////////////////////////////////////////
70
71class SkPathStroker {
72public:
73    SkPathStroker(const SkPath& src,
74                  SkScalar radius, SkScalar miterLimit, SkPaint::Cap cap,
75                  SkPaint::Join join);
76
77    void moveTo(const SkPoint&);
78    void lineTo(const SkPoint&);
79    void quadTo(const SkPoint&, const SkPoint&);
80    void cubicTo(const SkPoint&, const SkPoint&, const SkPoint&);
81    void close(bool isLine) { this->finishContour(true, isLine); }
82
83    void done(SkPath* dst, bool isLine) {
84        this->finishContour(false, isLine);
85        fOuter.addPath(fExtra);
86        dst->swap(fOuter);
87    }
88
89private:
90    SkScalar    fRadius;
91    SkScalar    fInvMiterLimit;
92
93    SkVector    fFirstNormal, fPrevNormal, fFirstUnitNormal, fPrevUnitNormal;
94    SkPoint     fFirstPt, fPrevPt;  // on original path
95    SkPoint     fFirstOuterPt;
96    int         fSegmentCount;
97    bool        fPrevIsLine;
98
99    SkStrokerPriv::CapProc  fCapper;
100    SkStrokerPriv::JoinProc fJoiner;
101
102    SkPath  fInner, fOuter; // outer is our working answer, inner is temp
103    SkPath  fExtra;         // added as extra complete contours
104
105    void    finishContour(bool close, bool isLine);
106    void    preJoinTo(const SkPoint&, SkVector* normal, SkVector* unitNormal,
107                      bool isLine);
108    void    postJoinTo(const SkPoint&, const SkVector& normal,
109                       const SkVector& unitNormal);
110
111    void    line_to(const SkPoint& currPt, const SkVector& normal);
112    void    quad_to(const SkPoint pts[3],
113                    const SkVector& normalAB, const SkVector& unitNormalAB,
114                    SkVector* normalBC, SkVector* unitNormalBC,
115                    int subDivide);
116    void    cubic_to(const SkPoint pts[4],
117                    const SkVector& normalAB, const SkVector& unitNormalAB,
118                    SkVector* normalCD, SkVector* unitNormalCD,
119                    int subDivide);
120};
121
122///////////////////////////////////////////////////////////////////////////////
123
124void SkPathStroker::preJoinTo(const SkPoint& currPt, SkVector* normal,
125                              SkVector* unitNormal, bool currIsLine) {
126    SkASSERT(fSegmentCount >= 0);
127
128    SkScalar    prevX = fPrevPt.fX;
129    SkScalar    prevY = fPrevPt.fY;
130
131    SkAssertResult(set_normal_unitnormal(fPrevPt, currPt, fRadius, normal,
132                                         unitNormal));
133
134    if (fSegmentCount == 0) {
135        fFirstNormal = *normal;
136        fFirstUnitNormal = *unitNormal;
137        fFirstOuterPt.set(prevX + normal->fX, prevY + normal->fY);
138
139        fOuter.moveTo(fFirstOuterPt.fX, fFirstOuterPt.fY);
140        fInner.moveTo(prevX - normal->fX, prevY - normal->fY);
141    } else {    // we have a previous segment
142        fJoiner(&fOuter, &fInner, fPrevUnitNormal, fPrevPt, *unitNormal,
143                fRadius, fInvMiterLimit, fPrevIsLine, currIsLine);
144    }
145    fPrevIsLine = currIsLine;
146}
147
148void SkPathStroker::postJoinTo(const SkPoint& currPt, const SkVector& normal,
149                               const SkVector& unitNormal) {
150    fPrevPt = currPt;
151    fPrevUnitNormal = unitNormal;
152    fPrevNormal = normal;
153    fSegmentCount += 1;
154}
155
156void SkPathStroker::finishContour(bool close, bool currIsLine) {
157    if (fSegmentCount > 0) {
158        SkPoint pt;
159
160        if (close) {
161            fJoiner(&fOuter, &fInner, fPrevUnitNormal, fPrevPt,
162                    fFirstUnitNormal, fRadius, fInvMiterLimit,
163                    fPrevIsLine, currIsLine);
164            fOuter.close();
165            // now add fInner as its own contour
166            fInner.getLastPt(&pt);
167            fOuter.moveTo(pt.fX, pt.fY);
168            fOuter.reversePathTo(fInner);
169            fOuter.close();
170        } else {    // add caps to start and end
171            // cap the end
172            fInner.getLastPt(&pt);
173            fCapper(&fOuter, fPrevPt, fPrevNormal, pt,
174                    currIsLine ? &fInner : NULL);
175            fOuter.reversePathTo(fInner);
176            // cap the start
177            fCapper(&fOuter, fFirstPt, -fFirstNormal, fFirstOuterPt,
178                    fPrevIsLine ? &fInner : NULL);
179            fOuter.close();
180        }
181    }
182    // since we may re-use fInner, we rewind instead of reset, to save on
183    // reallocating its internal storage.
184    fInner.rewind();
185    fSegmentCount = -1;
186}
187
188///////////////////////////////////////////////////////////////////////////////
189
190SkPathStroker::SkPathStroker(const SkPath& src,
191                             SkScalar radius, SkScalar miterLimit,
192                             SkPaint::Cap cap, SkPaint::Join join)
193        : fRadius(radius) {
194
195    /*  This is only used when join is miter_join, but we initialize it here
196        so that it is always defined, to fis valgrind warnings.
197    */
198    fInvMiterLimit = 0;
199
200    if (join == SkPaint::kMiter_Join) {
201        if (miterLimit <= SK_Scalar1) {
202            join = SkPaint::kBevel_Join;
203        } else {
204            fInvMiterLimit = SkScalarInvert(miterLimit);
205        }
206    }
207    fCapper = SkStrokerPriv::CapFactory(cap);
208    fJoiner = SkStrokerPriv::JoinFactory(join);
209    fSegmentCount = -1;
210    fPrevIsLine = false;
211
212    // Need some estimate of how large our final result (fOuter)
213    // and our per-contour temp (fInner) will be, so we don't spend
214    // extra time repeatedly growing these arrays.
215    //
216    // 3x for result == inner + outer + join (swag)
217    // 1x for inner == 'wag' (worst contour length would be better guess)
218    fOuter.incReserve(src.countPoints() * 3);
219    fInner.incReserve(src.countPoints());
220}
221
222void SkPathStroker::moveTo(const SkPoint& pt) {
223    if (fSegmentCount > 0) {
224        this->finishContour(false, false);
225    }
226    fSegmentCount = 0;
227    fFirstPt = fPrevPt = pt;
228}
229
230void SkPathStroker::line_to(const SkPoint& currPt, const SkVector& normal) {
231    fOuter.lineTo(currPt.fX + normal.fX, currPt.fY + normal.fY);
232    fInner.lineTo(currPt.fX - normal.fX, currPt.fY - normal.fY);
233}
234
235void SkPathStroker::lineTo(const SkPoint& currPt) {
236    if (SkPath::IsLineDegenerate(fPrevPt, currPt)) {
237        return;
238    }
239    SkVector    normal, unitNormal;
240
241    this->preJoinTo(currPt, &normal, &unitNormal, true);
242    this->line_to(currPt, normal);
243    this->postJoinTo(currPt, normal, unitNormal);
244}
245
246void SkPathStroker::quad_to(const SkPoint pts[3],
247                      const SkVector& normalAB, const SkVector& unitNormalAB,
248                      SkVector* normalBC, SkVector* unitNormalBC,
249                      int subDivide) {
250    if (!set_normal_unitnormal(pts[1], pts[2], fRadius,
251                               normalBC, unitNormalBC)) {
252        // pts[1] nearly equals pts[2], so just draw a line to pts[2]
253        this->line_to(pts[2], normalAB);
254        *normalBC = normalAB;
255        *unitNormalBC = unitNormalAB;
256        return;
257    }
258
259    if (--subDivide >= 0 && normals_too_curvy(unitNormalAB, *unitNormalBC)) {
260        SkPoint     tmp[5];
261        SkVector    norm, unit;
262
263        SkChopQuadAtHalf(pts, tmp);
264        this->quad_to(&tmp[0], normalAB, unitNormalAB, &norm, &unit, subDivide);
265        this->quad_to(&tmp[2], norm, unit, normalBC, unitNormalBC, subDivide);
266    } else {
267        SkVector    normalB;
268
269        normalB = pts[2] - pts[0];
270        normalB.rotateCCW();
271        SkScalar dot = SkPoint::DotProduct(unitNormalAB, *unitNormalBC);
272        SkAssertResult(normalB.setLength(SkScalarDiv(fRadius,
273                                     SkScalarSqrt((SK_Scalar1 + dot)/2))));
274
275        fOuter.quadTo(  pts[1].fX + normalB.fX, pts[1].fY + normalB.fY,
276                        pts[2].fX + normalBC->fX, pts[2].fY + normalBC->fY);
277        fInner.quadTo(  pts[1].fX - normalB.fX, pts[1].fY - normalB.fY,
278                        pts[2].fX - normalBC->fX, pts[2].fY - normalBC->fY);
279    }
280}
281
282void SkPathStroker::cubic_to(const SkPoint pts[4],
283                      const SkVector& normalAB, const SkVector& unitNormalAB,
284                      SkVector* normalCD, SkVector* unitNormalCD,
285                      int subDivide) {
286    SkVector    ab = pts[1] - pts[0];
287    SkVector    cd = pts[3] - pts[2];
288    SkVector    normalBC, unitNormalBC;
289
290    bool    degenerateAB = degenerate_vector(ab);
291    bool    degenerateCD = degenerate_vector(cd);
292
293    if (degenerateAB && degenerateCD) {
294DRAW_LINE:
295        this->line_to(pts[3], normalAB);
296        *normalCD = normalAB;
297        *unitNormalCD = unitNormalAB;
298        return;
299    }
300
301    if (degenerateAB) {
302        ab = pts[2] - pts[0];
303        degenerateAB = degenerate_vector(ab);
304    }
305    if (degenerateCD) {
306        cd = pts[3] - pts[1];
307        degenerateCD = degenerate_vector(cd);
308    }
309    if (degenerateAB || degenerateCD) {
310        goto DRAW_LINE;
311    }
312    SkAssertResult(set_normal_unitnormal(cd, fRadius, normalCD, unitNormalCD));
313    bool degenerateBC = !set_normal_unitnormal(pts[1], pts[2], fRadius,
314                                               &normalBC, &unitNormalBC);
315#ifndef SK_IGNORE_CUBIC_STROKE_FIX
316    if (--subDivide < 0) {
317        goto DRAW_LINE;
318    }
319#endif
320    if (degenerateBC || normals_too_curvy(unitNormalAB, unitNormalBC) ||
321             normals_too_curvy(unitNormalBC, *unitNormalCD)) {
322#ifdef SK_IGNORE_CUBIC_STROKE_FIX
323        // subdivide if we can
324        if (--subDivide < 0) {
325            goto DRAW_LINE;
326        }
327#endif
328        SkPoint     tmp[7];
329        SkVector    norm, unit, dummy, unitDummy;
330
331        SkChopCubicAtHalf(pts, tmp);
332        this->cubic_to(&tmp[0], normalAB, unitNormalAB, &norm, &unit,
333                       subDivide);
334        // we use dummys since we already have a valid (and more accurate)
335        // normals for CD
336        this->cubic_to(&tmp[3], norm, unit, &dummy, &unitDummy, subDivide);
337    } else {
338        SkVector    normalB, normalC;
339
340        // need normals to inset/outset the off-curve pts B and C
341
342        SkVector    unitBC = pts[2] - pts[1];
343        unitBC.normalize();
344        unitBC.rotateCCW();
345
346        normalB = unitNormalAB + unitBC;
347        normalC = *unitNormalCD + unitBC;
348
349        SkScalar dot = SkPoint::DotProduct(unitNormalAB, unitBC);
350        SkAssertResult(normalB.setLength(SkScalarDiv(fRadius,
351                                    SkScalarSqrt((SK_Scalar1 + dot)/2))));
352        dot = SkPoint::DotProduct(*unitNormalCD, unitBC);
353        SkAssertResult(normalC.setLength(SkScalarDiv(fRadius,
354                                    SkScalarSqrt((SK_Scalar1 + dot)/2))));
355
356        fOuter.cubicTo( pts[1].fX + normalB.fX, pts[1].fY + normalB.fY,
357                        pts[2].fX + normalC.fX, pts[2].fY + normalC.fY,
358                        pts[3].fX + normalCD->fX, pts[3].fY + normalCD->fY);
359
360        fInner.cubicTo( pts[1].fX - normalB.fX, pts[1].fY - normalB.fY,
361                        pts[2].fX - normalC.fX, pts[2].fY - normalC.fY,
362                        pts[3].fX - normalCD->fX, pts[3].fY - normalCD->fY);
363    }
364}
365
366void SkPathStroker::quadTo(const SkPoint& pt1, const SkPoint& pt2) {
367    bool    degenerateAB = SkPath::IsLineDegenerate(fPrevPt, pt1);
368    bool    degenerateBC = SkPath::IsLineDegenerate(pt1, pt2);
369
370    if (degenerateAB | degenerateBC) {
371        if (degenerateAB ^ degenerateBC) {
372            this->lineTo(pt2);
373        }
374        return;
375    }
376
377    SkVector    normalAB, unitAB, normalBC, unitBC;
378
379    this->preJoinTo(pt1, &normalAB, &unitAB, false);
380
381    {
382        SkPoint pts[3], tmp[5];
383        pts[0] = fPrevPt;
384        pts[1] = pt1;
385        pts[2] = pt2;
386
387        if (SkChopQuadAtMaxCurvature(pts, tmp) == 2) {
388            unitBC.setNormalize(pts[2].fX - pts[1].fX, pts[2].fY - pts[1].fY);
389            unitBC.rotateCCW();
390            if (normals_too_pinchy(unitAB, unitBC)) {
391                normalBC = unitBC;
392                normalBC.scale(fRadius);
393
394                fOuter.lineTo(tmp[2].fX + normalAB.fX, tmp[2].fY + normalAB.fY);
395                fOuter.lineTo(tmp[2].fX + normalBC.fX, tmp[2].fY + normalBC.fY);
396                fOuter.lineTo(tmp[4].fX + normalBC.fX, tmp[4].fY + normalBC.fY);
397
398                fInner.lineTo(tmp[2].fX - normalAB.fX, tmp[2].fY - normalAB.fY);
399                fInner.lineTo(tmp[2].fX - normalBC.fX, tmp[2].fY - normalBC.fY);
400                fInner.lineTo(tmp[4].fX - normalBC.fX, tmp[4].fY - normalBC.fY);
401
402                fExtra.addCircle(tmp[2].fX, tmp[2].fY, fRadius,
403                                 SkPath::kCW_Direction);
404            } else {
405                this->quad_to(&tmp[0], normalAB, unitAB, &normalBC, &unitBC,
406                              kMaxQuadSubdivide);
407                SkVector n = normalBC;
408                SkVector u = unitBC;
409                this->quad_to(&tmp[2], n, u, &normalBC, &unitBC,
410                              kMaxQuadSubdivide);
411            }
412        } else {
413            this->quad_to(pts, normalAB, unitAB, &normalBC, &unitBC,
414                          kMaxQuadSubdivide);
415        }
416    }
417
418    this->postJoinTo(pt2, normalBC, unitBC);
419}
420
421void SkPathStroker::cubicTo(const SkPoint& pt1, const SkPoint& pt2,
422                            const SkPoint& pt3) {
423    bool    degenerateAB = SkPath::IsLineDegenerate(fPrevPt, pt1);
424    bool    degenerateBC = SkPath::IsLineDegenerate(pt1, pt2);
425    bool    degenerateCD = SkPath::IsLineDegenerate(pt2, pt3);
426
427    if (degenerateAB + degenerateBC + degenerateCD >= 2
428            || (degenerateAB && SkPath::IsLineDegenerate(fPrevPt, pt2))) {
429        this->lineTo(pt3);
430        return;
431    }
432
433    SkVector    normalAB, unitAB, normalCD, unitCD;
434
435    // find the first tangent (which might be pt1 or pt2
436    {
437        const SkPoint*  nextPt = &pt1;
438        if (degenerateAB)
439            nextPt = &pt2;
440        this->preJoinTo(*nextPt, &normalAB, &unitAB, false);
441    }
442
443    {
444        SkPoint pts[4], tmp[13];
445        int         i, count;
446        SkVector    n, u;
447        SkScalar    tValues[3];
448
449        pts[0] = fPrevPt;
450        pts[1] = pt1;
451        pts[2] = pt2;
452        pts[3] = pt3;
453
454        count = SkChopCubicAtMaxCurvature(pts, tmp, tValues);
455        n = normalAB;
456        u = unitAB;
457        for (i = 0; i < count; i++) {
458            this->cubic_to(&tmp[i * 3], n, u, &normalCD, &unitCD,
459                           kMaxCubicSubdivide);
460            if (i == count - 1) {
461                break;
462            }
463            n = normalCD;
464            u = unitCD;
465
466        }
467    }
468
469    this->postJoinTo(pt3, normalCD, unitCD);
470}
471
472///////////////////////////////////////////////////////////////////////////////
473///////////////////////////////////////////////////////////////////////////////
474
475#include "SkPaintDefaults.h"
476
477SkStroke::SkStroke() {
478    fWidth      = SK_Scalar1;
479    fMiterLimit = SkPaintDefaults_MiterLimit;
480    fCap        = SkPaint::kDefault_Cap;
481    fJoin       = SkPaint::kDefault_Join;
482    fDoFill     = false;
483}
484
485SkStroke::SkStroke(const SkPaint& p) {
486    fWidth      = p.getStrokeWidth();
487    fMiterLimit = p.getStrokeMiter();
488    fCap        = (uint8_t)p.getStrokeCap();
489    fJoin       = (uint8_t)p.getStrokeJoin();
490    fDoFill     = SkToU8(p.getStyle() == SkPaint::kStrokeAndFill_Style);
491}
492
493SkStroke::SkStroke(const SkPaint& p, SkScalar width) {
494    fWidth      = width;
495    fMiterLimit = p.getStrokeMiter();
496    fCap        = (uint8_t)p.getStrokeCap();
497    fJoin       = (uint8_t)p.getStrokeJoin();
498    fDoFill     = SkToU8(p.getStyle() == SkPaint::kStrokeAndFill_Style);
499}
500
501void SkStroke::setWidth(SkScalar width) {
502    SkASSERT(width >= 0);
503    fWidth = width;
504}
505
506void SkStroke::setMiterLimit(SkScalar miterLimit) {
507    SkASSERT(miterLimit >= 0);
508    fMiterLimit = miterLimit;
509}
510
511void SkStroke::setCap(SkPaint::Cap cap) {
512    SkASSERT((unsigned)cap < SkPaint::kCapCount);
513    fCap = SkToU8(cap);
514}
515
516void SkStroke::setJoin(SkPaint::Join join) {
517    SkASSERT((unsigned)join < SkPaint::kJoinCount);
518    fJoin = SkToU8(join);
519}
520
521///////////////////////////////////////////////////////////////////////////////
522
523// If src==dst, then we use a tmp path to record the stroke, and then swap
524// its contents with src when we're done.
525class AutoTmpPath {
526public:
527    AutoTmpPath(const SkPath& src, SkPath** dst) : fSrc(src) {
528        if (&src == *dst) {
529            *dst = &fTmpDst;
530            fSwapWithSrc = true;
531        } else {
532            (*dst)->reset();
533            fSwapWithSrc = false;
534        }
535    }
536
537    ~AutoTmpPath() {
538        if (fSwapWithSrc) {
539            fTmpDst.swap(*const_cast<SkPath*>(&fSrc));
540        }
541    }
542
543private:
544    SkPath          fTmpDst;
545    const SkPath&   fSrc;
546    bool            fSwapWithSrc;
547};
548
549void SkStroke::strokePath(const SkPath& src, SkPath* dst) const {
550    SkASSERT(&src != NULL && dst != NULL);
551
552    SkScalar radius = SkScalarHalf(fWidth);
553
554    AutoTmpPath tmp(src, &dst);
555
556    if (radius <= 0) {
557        return;
558    }
559
560    // If src is really a rect, call our specialty strokeRect() method
561    {
562        bool isClosed;
563        SkPath::Direction dir;
564        if (src.isRect(&isClosed, &dir) && isClosed) {
565            this->strokeRect(src.getBounds(), dst, dir);
566            // our answer should preserve the inverseness of the src
567            if (src.isInverseFillType()) {
568                SkASSERT(!dst->isInverseFillType());
569                dst->toggleInverseFillType();
570            }
571            return;
572        }
573    }
574
575    SkAutoConicToQuads converter;
576    const SkScalar conicTol = SK_Scalar1 / 4;
577
578    SkPathStroker   stroker(src, radius, fMiterLimit, this->getCap(),
579                            this->getJoin());
580    SkPath::Iter    iter(src, false);
581    SkPath::Verb    lastSegment = SkPath::kMove_Verb;
582
583    for (;;) {
584        SkPoint  pts[4];
585        switch (iter.next(pts, false)) {
586            case SkPath::kMove_Verb:
587                stroker.moveTo(pts[0]);
588                break;
589            case SkPath::kLine_Verb:
590                stroker.lineTo(pts[1]);
591                lastSegment = SkPath::kLine_Verb;
592                break;
593            case SkPath::kQuad_Verb:
594                stroker.quadTo(pts[1], pts[2]);
595                lastSegment = SkPath::kQuad_Verb;
596                break;
597            case SkPath::kConic_Verb: {
598                // todo: if we had maxcurvature for conics, perhaps we should
599                // natively extrude the conic instead of converting to quads.
600                const SkPoint* quadPts =
601                    converter.computeQuads(pts, iter.conicWeight(), conicTol);
602                for (int i = 0; i < converter.countQuads(); ++i) {
603                    stroker.quadTo(quadPts[1], quadPts[2]);
604                    quadPts += 2;
605                }
606                lastSegment = SkPath::kQuad_Verb;
607            } break;
608            case SkPath::kCubic_Verb:
609                stroker.cubicTo(pts[1], pts[2], pts[3]);
610                lastSegment = SkPath::kCubic_Verb;
611                break;
612            case SkPath::kClose_Verb:
613                stroker.close(lastSegment == SkPath::kLine_Verb);
614                break;
615            case SkPath::kDone_Verb:
616                goto DONE;
617        }
618    }
619DONE:
620    stroker.done(dst, lastSegment == SkPath::kLine_Verb);
621
622    if (fDoFill) {
623        if (src.cheapIsDirection(SkPath::kCCW_Direction)) {
624            dst->reverseAddPath(src);
625        } else {
626            dst->addPath(src);
627        }
628    } else {
629        //  Seems like we can assume that a 2-point src would always result in
630        //  a convex stroke, but testing has proved otherwise.
631        //  TODO: fix the stroker to make this assumption true (without making
632        //  it slower that the work that will be done in computeConvexity())
633#if 0
634        // this test results in a non-convex stroke :(
635        static void test(SkCanvas* canvas) {
636            SkPoint pts[] = { 146.333328,  192.333328, 300.333344, 293.333344 };
637            SkPaint paint;
638            paint.setStrokeWidth(7);
639            paint.setStrokeCap(SkPaint::kRound_Cap);
640            canvas->drawLine(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY, paint);
641        }
642#endif
643#if 0
644        if (2 == src.countPoints()) {
645            dst->setIsConvex(true);
646        }
647#endif
648    }
649
650    // our answer should preserve the inverseness of the src
651    if (src.isInverseFillType()) {
652        SkASSERT(!dst->isInverseFillType());
653        dst->toggleInverseFillType();
654    }
655}
656
657static SkPath::Direction reverse_direction(SkPath::Direction dir) {
658    SkASSERT(SkPath::kUnknown_Direction != dir);
659    return SkPath::kCW_Direction == dir ? SkPath::kCCW_Direction : SkPath::kCW_Direction;
660}
661
662static void addBevel(SkPath* path, const SkRect& r, const SkRect& outer, SkPath::Direction dir) {
663    SkPoint pts[8];
664
665    if (SkPath::kCW_Direction == dir) {
666        pts[0].set(r.fLeft, outer.fTop);
667        pts[1].set(r.fRight, outer.fTop);
668        pts[2].set(outer.fRight, r.fTop);
669        pts[3].set(outer.fRight, r.fBottom);
670        pts[4].set(r.fRight, outer.fBottom);
671        pts[5].set(r.fLeft, outer.fBottom);
672        pts[6].set(outer.fLeft, r.fBottom);
673        pts[7].set(outer.fLeft, r.fTop);
674    } else {
675        pts[7].set(r.fLeft, outer.fTop);
676        pts[6].set(r.fRight, outer.fTop);
677        pts[5].set(outer.fRight, r.fTop);
678        pts[4].set(outer.fRight, r.fBottom);
679        pts[3].set(r.fRight, outer.fBottom);
680        pts[2].set(r.fLeft, outer.fBottom);
681        pts[1].set(outer.fLeft, r.fBottom);
682        pts[0].set(outer.fLeft, r.fTop);
683    }
684    path->addPoly(pts, 8, true);
685}
686
687void SkStroke::strokeRect(const SkRect& origRect, SkPath* dst,
688                          SkPath::Direction dir) const {
689    SkASSERT(dst != NULL);
690    dst->reset();
691
692    SkScalar radius = SkScalarHalf(fWidth);
693    if (radius <= 0) {
694        return;
695    }
696
697    SkScalar rw = origRect.width();
698    SkScalar rh = origRect.height();
699    if ((rw < 0) ^ (rh < 0)) {
700        dir = reverse_direction(dir);
701    }
702    SkRect rect(origRect);
703    rect.sort();
704    // reassign these, now that we know they'll be >= 0
705    rw = rect.width();
706    rh = rect.height();
707
708    SkRect r(rect);
709    r.outset(radius, radius);
710
711    SkPaint::Join join = (SkPaint::Join)fJoin;
712    if (SkPaint::kMiter_Join == join && fMiterLimit < SK_ScalarSqrt2) {
713        join = SkPaint::kBevel_Join;
714    }
715
716    switch (join) {
717        case SkPaint::kMiter_Join:
718            dst->addRect(r, dir);
719            break;
720        case SkPaint::kBevel_Join:
721            addBevel(dst, rect, r, dir);
722            break;
723        case SkPaint::kRound_Join:
724            dst->addRoundRect(r, radius, radius, dir);
725            break;
726        default:
727            break;
728    }
729
730    if (fWidth < SkMinScalar(rw, rh) && !fDoFill) {
731        r = rect;
732        r.inset(radius, radius);
733        dst->addRect(r, reverse_direction(dir));
734    }
735}
736