1/*
2 * Copyright (C) 2006-2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include "SkStrokerPriv.h"
18#include "SkGeometry.h"
19#include "SkPath.h"
20
21#define kMaxQuadSubdivide   5
22#define kMaxCubicSubdivide  4
23
24static inline bool degenerate_vector(const SkVector& v) {
25    return SkScalarNearlyZero(v.fX) && SkScalarNearlyZero(v.fY);
26}
27
28static inline bool degenerate_line(const SkPoint& a, const SkPoint& b,
29                                   SkScalar tolerance = SK_ScalarNearlyZero) {
30    return SkScalarNearlyZero(a.fX - b.fX, tolerance) &&
31            SkScalarNearlyZero(a.fY - b.fY, tolerance);
32}
33
34static inline bool normals_too_curvy(const SkVector& norm0, SkVector& norm1) {
35    /*  root2/2 is a 45-degree angle
36        make this constant bigger for more subdivisions (but not >= 1)
37    */
38    static const SkScalar kFlatEnoughNormalDotProd =
39                                            SK_ScalarSqrt2/2 + SK_Scalar1/10;
40
41    SkASSERT(kFlatEnoughNormalDotProd > 0 &&
42             kFlatEnoughNormalDotProd < SK_Scalar1);
43
44    return SkPoint::DotProduct(norm0, norm1) <= kFlatEnoughNormalDotProd;
45}
46
47static inline bool normals_too_pinchy(const SkVector& norm0, SkVector& norm1) {
48    static const SkScalar kTooPinchyNormalDotProd = -SK_Scalar1 * 999 / 1000;
49
50    return SkPoint::DotProduct(norm0, norm1) <= kTooPinchyNormalDotProd;
51}
52
53static bool set_normal_unitnormal(const SkPoint& before, const SkPoint& after,
54                                  SkScalar radius,
55                                  SkVector* normal, SkVector* unitNormal) {
56    if (!unitNormal->setNormalize(after.fX - before.fX, after.fY - before.fY)) {
57        return false;
58    }
59    unitNormal->rotateCCW();
60    unitNormal->scale(radius, normal);
61    return true;
62}
63
64static bool set_normal_unitnormal(const SkVector& vec,
65                                  SkScalar radius,
66                                  SkVector* normal, SkVector* unitNormal) {
67    if (!unitNormal->setNormalize(vec.fX, vec.fY)) {
68        return false;
69    }
70    unitNormal->rotateCCW();
71    unitNormal->scale(radius, normal);
72    return true;
73}
74
75///////////////////////////////////////////////////////////////////////////////
76
77class SkPathStroker {
78public:
79    SkPathStroker(SkScalar radius, SkScalar miterLimit, SkPaint::Cap cap,
80                  SkPaint::Join join);
81
82    void moveTo(const SkPoint&);
83    void lineTo(const SkPoint&);
84    void quadTo(const SkPoint&, const SkPoint&);
85    void cubicTo(const SkPoint&, const SkPoint&, const SkPoint&);
86    void close(bool isLine) { this->finishContour(true, isLine); }
87
88    void done(SkPath* dst, bool isLine) {
89        this->finishContour(false, isLine);
90        fOuter.addPath(fExtra);
91        dst->swap(fOuter);
92    }
93
94private:
95    SkScalar    fRadius;
96    SkScalar    fInvMiterLimit;
97
98    SkVector    fFirstNormal, fPrevNormal, fFirstUnitNormal, fPrevUnitNormal;
99    SkPoint     fFirstPt, fPrevPt;  // on original path
100    SkPoint     fFirstOuterPt;
101    int         fSegmentCount;
102    bool        fPrevIsLine;
103
104    SkStrokerPriv::CapProc  fCapper;
105    SkStrokerPriv::JoinProc fJoiner;
106
107    SkPath  fInner, fOuter; // outer is our working answer, inner is temp
108    SkPath  fExtra;         // added as extra complete contours
109
110    void    finishContour(bool close, bool isLine);
111    void    preJoinTo(const SkPoint&, SkVector* normal, SkVector* unitNormal,
112                      bool isLine);
113    void    postJoinTo(const SkPoint&, const SkVector& normal,
114                       const SkVector& unitNormal);
115
116    void    line_to(const SkPoint& currPt, const SkVector& normal);
117    void    quad_to(const SkPoint pts[3],
118                    const SkVector& normalAB, const SkVector& unitNormalAB,
119                    SkVector* normalBC, SkVector* unitNormalBC,
120                    int subDivide);
121    void    cubic_to(const SkPoint pts[4],
122                    const SkVector& normalAB, const SkVector& unitNormalAB,
123                    SkVector* normalCD, SkVector* unitNormalCD,
124                    int subDivide);
125};
126
127///////////////////////////////////////////////////////////////////////////////
128
129void SkPathStroker::preJoinTo(const SkPoint& currPt, SkVector* normal,
130                              SkVector* unitNormal, bool currIsLine) {
131    SkASSERT(fSegmentCount >= 0);
132
133    SkScalar    prevX = fPrevPt.fX;
134    SkScalar    prevY = fPrevPt.fY;
135
136    SkAssertResult(set_normal_unitnormal(fPrevPt, currPt, fRadius, normal,
137                                         unitNormal));
138
139    if (fSegmentCount == 0) {
140        fFirstNormal = *normal;
141        fFirstUnitNormal = *unitNormal;
142        fFirstOuterPt.set(prevX + normal->fX, prevY + normal->fY);
143
144        fOuter.moveTo(fFirstOuterPt.fX, fFirstOuterPt.fY);
145        fInner.moveTo(prevX - normal->fX, prevY - normal->fY);
146    } else {    // we have a previous segment
147        fJoiner(&fOuter, &fInner, fPrevUnitNormal, fPrevPt, *unitNormal,
148                fRadius, fInvMiterLimit, fPrevIsLine, currIsLine);
149    }
150    fPrevIsLine = currIsLine;
151}
152
153void SkPathStroker::postJoinTo(const SkPoint& currPt, const SkVector& normal,
154                               const SkVector& unitNormal) {
155    fPrevPt = currPt;
156    fPrevUnitNormal = unitNormal;
157    fPrevNormal = normal;
158    fSegmentCount += 1;
159}
160
161void SkPathStroker::finishContour(bool close, bool currIsLine) {
162    if (fSegmentCount > 0) {
163        SkPoint pt;
164
165        if (close) {
166            fJoiner(&fOuter, &fInner, fPrevUnitNormal, fPrevPt,
167                    fFirstUnitNormal, fRadius, fInvMiterLimit,
168                    fPrevIsLine, currIsLine);
169            fOuter.close();
170            // now add fInner as its own contour
171            fInner.getLastPt(&pt);
172            fOuter.moveTo(pt.fX, pt.fY);
173            fOuter.reversePathTo(fInner);
174            fOuter.close();
175        } else {    // add caps to start and end
176            // cap the end
177            fInner.getLastPt(&pt);
178            fCapper(&fOuter, fPrevPt, fPrevNormal, pt,
179                    currIsLine ? &fInner : NULL);
180            fOuter.reversePathTo(fInner);
181            // cap the start
182            fCapper(&fOuter, fFirstPt, -fFirstNormal, fFirstOuterPt,
183                    fPrevIsLine ? &fInner : NULL);
184            fOuter.close();
185        }
186    }
187    fInner.reset();
188    fSegmentCount = -1;
189}
190
191///////////////////////////////////////////////////////////////////////////////
192
193SkPathStroker::SkPathStroker(SkScalar radius, SkScalar miterLimit,
194                             SkPaint::Cap cap, SkPaint::Join join)
195        : fRadius(radius) {
196
197    /*  This is only used when join is miter_join, but we initialize it here
198        so that it is always defined, to fis valgrind warnings.
199    */
200    fInvMiterLimit = 0;
201
202    if (join == SkPaint::kMiter_Join) {
203        if (miterLimit <= SK_Scalar1) {
204            join = SkPaint::kBevel_Join;
205        } else {
206            fInvMiterLimit = SkScalarInvert(miterLimit);
207        }
208    }
209    fCapper = SkStrokerPriv::CapFactory(cap);
210    fJoiner = SkStrokerPriv::JoinFactory(join);
211    fSegmentCount = -1;
212    fPrevIsLine = false;
213}
214
215void SkPathStroker::moveTo(const SkPoint& pt) {
216    if (fSegmentCount > 0) {
217        this->finishContour(false, false);
218    }
219    fSegmentCount = 0;
220    fFirstPt = fPrevPt = pt;
221}
222
223void SkPathStroker::line_to(const SkPoint& currPt, const SkVector& normal) {
224    fOuter.lineTo(currPt.fX + normal.fX, currPt.fY + normal.fY);
225    fInner.lineTo(currPt.fX - normal.fX, currPt.fY - normal.fY);
226}
227
228void SkPathStroker::lineTo(const SkPoint& currPt) {
229    if (degenerate_line(fPrevPt, currPt)) {
230        return;
231    }
232    SkVector    normal, unitNormal;
233
234    this->preJoinTo(currPt, &normal, &unitNormal, true);
235    this->line_to(currPt, normal);
236    this->postJoinTo(currPt, normal, unitNormal);
237}
238
239void SkPathStroker::quad_to(const SkPoint pts[3],
240                      const SkVector& normalAB, const SkVector& unitNormalAB,
241                      SkVector* normalBC, SkVector* unitNormalBC,
242                      int subDivide) {
243    if (!set_normal_unitnormal(pts[1], pts[2], fRadius,
244                               normalBC, unitNormalBC)) {
245        // pts[1] nearly equals pts[2], so just draw a line to pts[2]
246        this->line_to(pts[2], normalAB);
247        *normalBC = normalAB;
248        *unitNormalBC = unitNormalAB;
249        return;
250    }
251
252    if (--subDivide >= 0 && normals_too_curvy(unitNormalAB, *unitNormalBC)) {
253        SkPoint     tmp[5];
254        SkVector    norm, unit;
255
256        SkChopQuadAtHalf(pts, tmp);
257        this->quad_to(&tmp[0], normalAB, unitNormalAB, &norm, &unit, subDivide);
258        this->quad_to(&tmp[2], norm, unit, normalBC, unitNormalBC, subDivide);
259    } else {
260        SkVector    normalB, unitB;
261        SkAssertResult(set_normal_unitnormal(pts[0], pts[2], fRadius,
262                                             &normalB, &unitB));
263
264        fOuter.quadTo(  pts[1].fX + normalB.fX, pts[1].fY + normalB.fY,
265                        pts[2].fX + normalBC->fX, pts[2].fY + normalBC->fY);
266        fInner.quadTo(  pts[1].fX - normalB.fX, pts[1].fY - normalB.fY,
267                        pts[2].fX - normalBC->fX, pts[2].fY - normalBC->fY);
268    }
269}
270
271void SkPathStroker::cubic_to(const SkPoint pts[4],
272                      const SkVector& normalAB, const SkVector& unitNormalAB,
273                      SkVector* normalCD, SkVector* unitNormalCD,
274                      int subDivide) {
275    SkVector    ab = pts[1] - pts[0];
276    SkVector    cd = pts[3] - pts[2];
277    SkVector    normalBC, unitNormalBC;
278
279    bool    degenerateAB = degenerate_vector(ab);
280    bool    degenerateCD = degenerate_vector(cd);
281
282    if (degenerateAB && degenerateCD) {
283DRAW_LINE:
284        this->line_to(pts[3], normalAB);
285        *normalCD = normalAB;
286        *unitNormalCD = unitNormalAB;
287        return;
288    }
289
290    if (degenerateAB) {
291        ab = pts[2] - pts[0];
292        degenerateAB = degenerate_vector(ab);
293    }
294    if (degenerateCD) {
295        cd = pts[3] - pts[1];
296        degenerateCD = degenerate_vector(cd);
297    }
298    if (degenerateAB || degenerateCD) {
299        goto DRAW_LINE;
300    }
301    SkAssertResult(set_normal_unitnormal(cd, fRadius, normalCD, unitNormalCD));
302    bool degenerateBC = !set_normal_unitnormal(pts[1], pts[2], fRadius,
303                                               &normalBC, &unitNormalBC);
304
305    if (degenerateBC || normals_too_curvy(unitNormalAB, unitNormalBC) ||
306             normals_too_curvy(unitNormalBC, *unitNormalCD)) {
307        // subdivide if we can
308        if (--subDivide < 0) {
309            goto DRAW_LINE;
310        }
311        SkPoint     tmp[7];
312        SkVector    norm, unit, dummy, unitDummy;
313
314        SkChopCubicAtHalf(pts, tmp);
315        this->cubic_to(&tmp[0], normalAB, unitNormalAB, &norm, &unit,
316                       subDivide);
317        // we use dummys since we already have a valid (and more accurate)
318        // normals for CD
319        this->cubic_to(&tmp[3], norm, unit, &dummy, &unitDummy, subDivide);
320    } else {
321        SkVector    normalB, normalC;
322
323        // need normals to inset/outset the off-curve pts B and C
324
325        if (0) {    // this is normal to the line between our adjacent pts
326            normalB = pts[2] - pts[0];
327            normalB.rotateCCW();
328            SkAssertResult(normalB.setLength(fRadius));
329
330            normalC = pts[3] - pts[1];
331            normalC.rotateCCW();
332            SkAssertResult(normalC.setLength(fRadius));
333        } else {    // miter-join
334            SkVector    unitBC = pts[2] - pts[1];
335            unitBC.normalize();
336            unitBC.rotateCCW();
337
338            normalB = unitNormalAB + unitBC;
339            normalC = *unitNormalCD + unitBC;
340
341            SkScalar dot = SkPoint::DotProduct(unitNormalAB, unitBC);
342            SkAssertResult(normalB.setLength(SkScalarDiv(fRadius,
343                                        SkScalarSqrt((SK_Scalar1 + dot)/2))));
344            dot = SkPoint::DotProduct(*unitNormalCD, unitBC);
345            SkAssertResult(normalC.setLength(SkScalarDiv(fRadius,
346                                        SkScalarSqrt((SK_Scalar1 + dot)/2))));
347        }
348
349        fOuter.cubicTo( pts[1].fX + normalB.fX, pts[1].fY + normalB.fY,
350                        pts[2].fX + normalC.fX, pts[2].fY + normalC.fY,
351                        pts[3].fX + normalCD->fX, pts[3].fY + normalCD->fY);
352
353        fInner.cubicTo( pts[1].fX - normalB.fX, pts[1].fY - normalB.fY,
354                        pts[2].fX - normalC.fX, pts[2].fY - normalC.fY,
355                        pts[3].fX - normalCD->fX, pts[3].fY - normalCD->fY);
356    }
357}
358
359void SkPathStroker::quadTo(const SkPoint& pt1, const SkPoint& pt2) {
360    bool    degenerateAB = degenerate_line(fPrevPt, pt1);
361    bool    degenerateBC = degenerate_line(pt1, pt2);
362
363    if (degenerateAB | degenerateBC) {
364        if (degenerateAB ^ degenerateBC) {
365            this->lineTo(pt2);
366        }
367        return;
368    }
369
370    SkVector    normalAB, unitAB, normalBC, unitBC;
371
372    this->preJoinTo(pt1, &normalAB, &unitAB, false);
373
374    {
375        SkPoint pts[3], tmp[5];
376        pts[0] = fPrevPt;
377        pts[1] = pt1;
378        pts[2] = pt2;
379
380        if (SkChopQuadAtMaxCurvature(pts, tmp) == 2) {
381            unitBC.setNormalize(pts[2].fX - pts[1].fX, pts[2].fY - pts[1].fY);
382            unitBC.rotateCCW();
383            if (normals_too_pinchy(unitAB, unitBC)) {
384                normalBC = unitBC;
385                normalBC.scale(fRadius);
386
387                fOuter.lineTo(tmp[2].fX + normalAB.fX, tmp[2].fY + normalAB.fY);
388                fOuter.lineTo(tmp[2].fX + normalBC.fX, tmp[2].fY + normalBC.fY);
389                fOuter.lineTo(tmp[4].fX + normalBC.fX, tmp[4].fY + normalBC.fY);
390
391                fInner.lineTo(tmp[2].fX - normalAB.fX, tmp[2].fY - normalAB.fY);
392                fInner.lineTo(tmp[2].fX - normalBC.fX, tmp[2].fY - normalBC.fY);
393                fInner.lineTo(tmp[4].fX - normalBC.fX, tmp[4].fY - normalBC.fY);
394
395                fExtra.addCircle(tmp[2].fX, tmp[2].fY, fRadius,
396                                 SkPath::kCW_Direction);
397            } else {
398                this->quad_to(&tmp[0], normalAB, unitAB, &normalBC, &unitBC,
399                              kMaxQuadSubdivide);
400                SkVector n = normalBC;
401                SkVector u = unitBC;
402                this->quad_to(&tmp[2], n, u, &normalBC, &unitBC,
403                              kMaxQuadSubdivide);
404            }
405        } else {
406            this->quad_to(pts, normalAB, unitAB, &normalBC, &unitBC,
407                          kMaxQuadSubdivide);
408        }
409    }
410
411    this->postJoinTo(pt2, normalBC, unitBC);
412}
413
414void SkPathStroker::cubicTo(const SkPoint& pt1, const SkPoint& pt2,
415                            const SkPoint& pt3) {
416    bool    degenerateAB = degenerate_line(fPrevPt, pt1);
417    bool    degenerateBC = degenerate_line(pt1, pt2);
418    bool    degenerateCD = degenerate_line(pt2, pt3);
419
420    if (degenerateAB + degenerateBC + degenerateCD >= 2) {
421        this->lineTo(pt3);
422        return;
423    }
424
425    SkVector    normalAB, unitAB, normalCD, unitCD;
426
427    // find the first tangent (which might be pt1 or pt2
428    {
429        const SkPoint*  nextPt = &pt1;
430        if (degenerateAB)
431            nextPt = &pt2;
432        this->preJoinTo(*nextPt, &normalAB, &unitAB, false);
433    }
434
435    {
436        SkPoint pts[4], tmp[13];
437        int         i, count;
438        SkVector    n, u;
439        SkScalar    tValues[3];
440
441        pts[0] = fPrevPt;
442        pts[1] = pt1;
443        pts[2] = pt2;
444        pts[3] = pt3;
445
446#if 1
447        count = SkChopCubicAtMaxCurvature(pts, tmp, tValues);
448#else
449        count = 1;
450        memcpy(tmp, pts, 4 * sizeof(SkPoint));
451#endif
452        n = normalAB;
453        u = unitAB;
454        for (i = 0; i < count; i++) {
455            this->cubic_to(&tmp[i * 3], n, u, &normalCD, &unitCD,
456                           kMaxCubicSubdivide);
457            if (i == count - 1) {
458                break;
459            }
460            n = normalCD;
461            u = unitCD;
462
463        }
464
465        // check for too pinchy
466        for (i = 1; i < count; i++) {
467            SkPoint p;
468            SkVector    v, c;
469
470            SkEvalCubicAt(pts, tValues[i - 1], &p, &v, &c);
471
472            SkScalar    dot = SkPoint::DotProduct(c, c);
473            v.scale(SkScalarInvert(dot));
474
475            if (SkScalarNearlyZero(v.fX) && SkScalarNearlyZero(v.fY)) {
476                fExtra.addCircle(p.fX, p.fY, fRadius, SkPath::kCW_Direction);
477            }
478        }
479
480    }
481
482    this->postJoinTo(pt3, normalCD, unitCD);
483}
484
485///////////////////////////////////////////////////////////////////////////////
486///////////////////////////////////////////////////////////////////////////////
487
488#include "SkPaint.h"
489
490SkStroke::SkStroke() {
491    fWidth      = SK_DefaultStrokeWidth;
492    fMiterLimit = SK_DefaultMiterLimit;
493    fCap        = SkPaint::kDefault_Cap;
494    fJoin       = SkPaint::kDefault_Join;
495    fDoFill     = false;
496}
497
498SkStroke::SkStroke(const SkPaint& p) {
499    fWidth      = p.getStrokeWidth();
500    fMiterLimit = p.getStrokeMiter();
501    fCap        = (uint8_t)p.getStrokeCap();
502    fJoin       = (uint8_t)p.getStrokeJoin();
503    fDoFill     = SkToU8(p.getStyle() == SkPaint::kStrokeAndFill_Style);
504}
505
506SkStroke::SkStroke(const SkPaint& p, SkScalar width) {
507    fWidth      = width;
508    fMiterLimit = p.getStrokeMiter();
509    fCap        = (uint8_t)p.getStrokeCap();
510    fJoin       = (uint8_t)p.getStrokeJoin();
511    fDoFill     = SkToU8(p.getStyle() == SkPaint::kStrokeAndFill_Style);
512}
513
514void SkStroke::setWidth(SkScalar width) {
515    SkASSERT(width >= 0);
516    fWidth = width;
517}
518
519void SkStroke::setMiterLimit(SkScalar miterLimit) {
520    SkASSERT(miterLimit >= 0);
521    fMiterLimit = miterLimit;
522}
523
524void SkStroke::setCap(SkPaint::Cap cap) {
525    SkASSERT((unsigned)cap < SkPaint::kCapCount);
526    fCap = SkToU8(cap);
527}
528
529void SkStroke::setJoin(SkPaint::Join join) {
530    SkASSERT((unsigned)join < SkPaint::kJoinCount);
531    fJoin = SkToU8(join);
532}
533
534///////////////////////////////////////////////////////////////////////////////
535
536#ifdef SK_SCALAR_IS_FIXED
537    /*  return non-zero if the path is too big, and should be shrunk to avoid
538        overflows during intermediate calculations. Note that we compute the
539        bounds for this. If we had a custom callback/walker for paths, we could
540        perhaps go faster by using that, and just perform the abs | in that
541        routine
542    */
543    static int needs_to_shrink(const SkPath& path) {
544        const SkRect& r = path.getBounds();
545        SkFixed mask = SkAbs32(r.fLeft);
546        mask |= SkAbs32(r.fTop);
547        mask |= SkAbs32(r.fRight);
548        mask |= SkAbs32(r.fBottom);
549        // we need the top 3 bits clear (after abs) to avoid overflow
550        return mask >> 29;
551    }
552
553    static void identity_proc(SkPoint pts[], int count) {}
554    static void shift_down_2_proc(SkPoint pts[], int count) {
555        for (int i = 0; i < count; i++) {
556            pts->fX >>= 2;
557            pts->fY >>= 2;
558            pts += 1;
559        }
560    }
561    #define APPLY_PROC(proc, pts, count)    proc(pts, count)
562#else   // float does need any of this
563    #define APPLY_PROC(proc, pts, count)
564#endif
565
566void SkStroke::strokePath(const SkPath& src, SkPath* dst) const {
567    SkASSERT(&src != NULL && dst != NULL);
568
569    SkScalar radius = SkScalarHalf(fWidth);
570
571    dst->reset();
572    if (radius <= 0) {
573        return;
574    }
575
576#ifdef SK_SCALAR_IS_FIXED
577    void (*proc)(SkPoint pts[], int count) = identity_proc;
578    if (needs_to_shrink(src)) {
579        proc = shift_down_2_proc;
580        radius >>= 2;
581        if (radius == 0) {
582            return;
583        }
584    }
585#endif
586
587    SkPathStroker   stroker(radius, fMiterLimit, this->getCap(),
588                            this->getJoin());
589
590    SkPath::Iter    iter(src, false);
591    SkPoint         pts[4];
592    SkPath::Verb    verb, lastSegment = SkPath::kMove_Verb;
593
594    while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
595        switch (verb) {
596            case SkPath::kMove_Verb:
597                APPLY_PROC(proc, &pts[0], 1);
598                stroker.moveTo(pts[0]);
599                break;
600            case SkPath::kLine_Verb:
601                APPLY_PROC(proc, &pts[1], 1);
602                stroker.lineTo(pts[1]);
603                lastSegment = verb;
604                break;
605            case SkPath::kQuad_Verb:
606                APPLY_PROC(proc, &pts[1], 2);
607                stroker.quadTo(pts[1], pts[2]);
608                lastSegment = verb;
609                break;
610            case SkPath::kCubic_Verb:
611                APPLY_PROC(proc, &pts[1], 3);
612                stroker.cubicTo(pts[1], pts[2], pts[3]);
613                lastSegment = verb;
614                break;
615            case SkPath::kClose_Verb:
616                stroker.close(lastSegment == SkPath::kLine_Verb);
617                break;
618            default:
619                break;
620        }
621    }
622    stroker.done(dst, lastSegment == SkPath::kLine_Verb);
623
624#ifdef SK_SCALAR_IS_FIXED
625    // undo our previous down_shift
626    if (shift_down_2_proc == proc) {
627        // need a real shift methid on path. antialias paths could use this too
628        SkMatrix matrix;
629        matrix.setScale(SkIntToScalar(4), SkIntToScalar(4));
630        dst->transform(matrix);
631    }
632#endif
633
634    if (fDoFill) {
635        dst->addPath(src);
636    }
637}
638
639void SkStroke::strokeLine(const SkPoint& p0, const SkPoint& p1,
640                          SkPath* dst) const {
641    SkPath  tmp;
642
643    tmp.moveTo(p0);
644    tmp.lineTo(p1);
645    this->strokePath(tmp, dst);
646}
647
648