1fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot/*
2fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot * Copyright 2006 The Android Open Source Project
3fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot *
4fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot * Use of this source code is governed by a BSD-style license that can be
5fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot * found in the LICENSE file.
6fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot */
7fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
8fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot#include "SkStrokerPriv.h"
9fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot#include "SkGeometry.h"
10fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot#include "SkPath.h"
11fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot#include "SkPointPriv.h"
12fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
13fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robotstatic void ButtCapper(SkPath* path, const SkPoint& pivot, const SkVector& normal,
14fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot                       const SkPoint& stop, SkPath*) {
15fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    path->lineTo(stop.fX, stop.fY);
16fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot}
17fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
18fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robotstatic void RoundCapper(SkPath* path, const SkPoint& pivot, const SkVector& normal,
19fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot                        const SkPoint& stop, SkPath*) {
20fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    SkVector parallel;
21fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    SkPointPriv::RotateCW(normal, &parallel);
22fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
23fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    SkPoint projectedCenter = pivot + parallel;
24fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
25fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    path->conicTo(projectedCenter + normal, projectedCenter, SK_ScalarRoot2Over2);
26fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    path->conicTo(projectedCenter - normal, stop, SK_ScalarRoot2Over2);
27fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot}
28fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
29fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robotstatic void SquareCapper(SkPath* path, const SkPoint& pivot, const SkVector& normal,
30fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot                         const SkPoint& stop, SkPath* otherPath) {
31fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    SkVector parallel;
32fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    SkPointPriv::RotateCW(normal, &parallel);
33fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
34fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    if (otherPath) {
35fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot        path->setLastPt(pivot.fX + normal.fX + parallel.fX, pivot.fY + normal.fY + parallel.fY);
36fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot        path->lineTo(pivot.fX - normal.fX + parallel.fX, pivot.fY - normal.fY + parallel.fY);
37fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    } else {
38fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot        path->lineTo(pivot.fX + normal.fX + parallel.fX, pivot.fY + normal.fY + parallel.fY);
39fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot        path->lineTo(pivot.fX - normal.fX + parallel.fX, pivot.fY - normal.fY + parallel.fY);
40fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot        path->lineTo(stop.fX, stop.fY);
41fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    }
42fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot}
43fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
44fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot/////////////////////////////////////////////////////////////////////////////
45fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
46fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robotstatic bool is_clockwise(const SkVector& before, const SkVector& after) {
47fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    return before.fX * after.fY > before.fY * after.fX;
48fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot}
49fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
50fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robotenum AngleType {
51fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    kNearly180_AngleType,
52fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    kSharp_AngleType,
53fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    kShallow_AngleType,
54fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    kNearlyLine_AngleType
55fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot};
56fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
57fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robotstatic AngleType Dot2AngleType(SkScalar dot) {
58fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot// need more precise fixed normalization
59fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot//  SkASSERT(SkScalarAbs(dot) <= SK_Scalar1 + SK_ScalarNearlyZero);
60fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
61fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    if (dot >= 0) { // shallow or line
62fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot        return SkScalarNearlyZero(SK_Scalar1 - dot) ? kNearlyLine_AngleType : kShallow_AngleType;
63fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    } else {           // sharp or 180
64fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot        return SkScalarNearlyZero(SK_Scalar1 + dot) ? kNearly180_AngleType : kSharp_AngleType;
65fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    }
66fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot}
67fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
68fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robotstatic void HandleInnerJoin(SkPath* inner, const SkPoint& pivot, const SkVector& after) {
69fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot#if 1
70fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    /*  In the degenerate case that the stroke radius is larger than our segments
71fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot        just connecting the two inner segments may "show through" as a funny
72fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot        diagonal. To pseudo-fix this, we go through the pivot point. This adds
73fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot        an extra point/edge, but I can't see a cheap way to know when this is
74fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot        not needed :(
75fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    */
76fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    inner->lineTo(pivot.fX, pivot.fY);
77fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot#endif
78fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
79fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    inner->lineTo(pivot.fX - after.fX, pivot.fY - after.fY);
80fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot}
81fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
82fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robotstatic void BluntJoiner(SkPath* outer, SkPath* inner, const SkVector& beforeUnitNormal,
83fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot                        const SkPoint& pivot, const SkVector& afterUnitNormal,
84fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot                        SkScalar radius, SkScalar invMiterLimit, bool, bool) {
85fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    SkVector    after;
86fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    afterUnitNormal.scale(radius, &after);
87fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
88fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    if (!is_clockwise(beforeUnitNormal, afterUnitNormal)) {
89fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot        SkTSwap<SkPath*>(outer, inner);
90fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot        after.negate();
91fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    }
92fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
93fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    outer->lineTo(pivot.fX + after.fX, pivot.fY + after.fY);
94fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    HandleInnerJoin(inner, pivot, after);
95fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot}
96fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
97fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robotstatic void RoundJoiner(SkPath* outer, SkPath* inner, const SkVector& beforeUnitNormal,
98fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot                        const SkPoint& pivot, const SkVector& afterUnitNormal,
99fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot                        SkScalar radius, SkScalar invMiterLimit, bool, bool) {
100fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    SkScalar    dotProd = SkPoint::DotProduct(beforeUnitNormal, afterUnitNormal);
101fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    AngleType   angleType = Dot2AngleType(dotProd);
102fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
103fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    if (angleType == kNearlyLine_AngleType)
104fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot        return;
105fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
106fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    SkVector            before = beforeUnitNormal;
107fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    SkVector            after = afterUnitNormal;
108fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    SkRotationDirection dir = kCW_SkRotationDirection;
109fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
110fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    if (!is_clockwise(before, after)) {
111fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot        SkTSwap<SkPath*>(outer, inner);
112fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot        before.negate();
113fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot        after.negate();
114fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot        dir = kCCW_SkRotationDirection;
115fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    }
116fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
117fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    SkMatrix    matrix;
118fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    matrix.setScale(radius, radius);
119fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    matrix.postTranslate(pivot.fX, pivot.fY);
120fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    SkConic conics[SkConic::kMaxConicsForArc];
121fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    int count = SkConic::BuildUnitArc(before, after, dir, &matrix, conics);
122fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    if (count > 0) {
123fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot        for (int i = 0; i < count; ++i) {
124fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot            outer->conicTo(conics[i].fPts[1], conics[i].fPts[2], conics[i].fW);
125fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot        }
126fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot        after.scale(radius);
127fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot        HandleInnerJoin(inner, pivot, after);
128fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    }
129fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot}
130fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
131fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot#define kOneOverSqrt2   (0.707106781f)
132fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
133fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robotstatic void MiterJoiner(SkPath* outer, SkPath* inner, const SkVector& beforeUnitNormal,
134fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot                        const SkPoint& pivot, const SkVector& afterUnitNormal,
135fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot                        SkScalar radius, SkScalar invMiterLimit,
136fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot                        bool prevIsLine, bool currIsLine) {
137fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    // negate the dot since we're using normals instead of tangents
138fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    SkScalar    dotProd = SkPoint::DotProduct(beforeUnitNormal, afterUnitNormal);
139fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    AngleType   angleType = Dot2AngleType(dotProd);
140fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    SkVector    before = beforeUnitNormal;
141fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    SkVector    after = afterUnitNormal;
142fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    SkVector    mid;
143fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    SkScalar    sinHalfAngle;
144fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    bool        ccw;
145fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
146fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    if (angleType == kNearlyLine_AngleType) {
147fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot        return;
148fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    }
149fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    if (angleType == kNearly180_AngleType) {
150fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot        currIsLine = false;
151fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot        goto DO_BLUNT;
152fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    }
153fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
154fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    ccw = !is_clockwise(before, after);
155fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    if (ccw) {
156fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot        SkTSwap<SkPath*>(outer, inner);
157fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot        before.negate();
158fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot        after.negate();
159fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    }
160fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
161fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    /*  Before we enter the world of square-roots and divides,
162fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot        check if we're trying to join an upright right angle
163fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot        (common case for stroking rectangles). If so, special case
164fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot        that (for speed an accuracy).
165fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot        Note: we only need to check one normal if dot==0
166fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    */
167fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    if (0 == dotProd && invMiterLimit <= kOneOverSqrt2) {
168fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot        mid = (before + after) * radius;
169fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot        goto DO_MITER;
170fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    }
171fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
172fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    /*  midLength = radius / sinHalfAngle
173fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot        if (midLength > miterLimit * radius) abort
174fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot        if (radius / sinHalf > miterLimit * radius) abort
175fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot        if (1 / sinHalf > miterLimit) abort
176fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot        if (1 / miterLimit > sinHalf) abort
177fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot        My dotProd is opposite sign, since it is built from normals and not tangents
178fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot        hence 1 + dot instead of 1 - dot in the formula
179fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    */
180fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    sinHalfAngle = SkScalarSqrt(SkScalarHalf(SK_Scalar1 + dotProd));
181fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    if (sinHalfAngle < invMiterLimit) {
182fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot        currIsLine = false;
183fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot        goto DO_BLUNT;
184fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    }
185fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
186fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    // choose the most accurate way to form the initial mid-vector
187fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    if (angleType == kSharp_AngleType) {
188fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot        mid.set(after.fY - before.fY, before.fX - after.fX);
189fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot        if (ccw) {
190fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot            mid.negate();
191fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot        }
192fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    } else {
193fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot        mid.set(before.fX + after.fX, before.fY + after.fY);
194fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    }
195fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
196fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    mid.setLength(radius / sinHalfAngle);
197fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team RobotDO_MITER:
198fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    if (prevIsLine) {
199fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot        outer->setLastPt(pivot.fX + mid.fX, pivot.fY + mid.fY);
200fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    } else {
201fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot        outer->lineTo(pivot.fX + mid.fX, pivot.fY + mid.fY);
202fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    }
203fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
204fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team RobotDO_BLUNT:
205fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    after.scale(radius);
206fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    if (!currIsLine) {
207fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot        outer->lineTo(pivot.fX + after.fX, pivot.fY + after.fY);
208fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    }
209fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    HandleInnerJoin(inner, pivot, after);
210fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot}
211fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
212fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot/////////////////////////////////////////////////////////////////////////////
213fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
214fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team RobotSkStrokerPriv::CapProc SkStrokerPriv::CapFactory(SkPaint::Cap cap) {
215fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    const SkStrokerPriv::CapProc gCappers[] = {
216fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot        ButtCapper, RoundCapper, SquareCapper
217fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    };
218fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
219fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    SkASSERT((unsigned)cap < SkPaint::kCapCount);
220fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    return gCappers[cap];
221fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot}
222fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
223fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team RobotSkStrokerPriv::JoinProc SkStrokerPriv::JoinFactory(SkPaint::Join join) {
224fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    const SkStrokerPriv::JoinProc gJoiners[] = {
225fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot        MiterJoiner, RoundJoiner, BluntJoiner
226fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    };
227fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
228fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    SkASSERT((unsigned)join < SkPaint::kJoinCount);
229fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    return gJoiners[join];
230fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot}
231