1cb93a386Sopenharmony_ci/*
2cb93a386Sopenharmony_ci * Copyright 2020 Google Inc.
3cb93a386Sopenharmony_ci *
4cb93a386Sopenharmony_ci * Use of this source code is governed by a BSD-style license that can be
5cb93a386Sopenharmony_ci * found in the LICENSE file.
6cb93a386Sopenharmony_ci */
7cb93a386Sopenharmony_ci
8cb93a386Sopenharmony_ci#include "imgui.h"
9cb93a386Sopenharmony_ci#include "include/core/SkBitmap.h"
10cb93a386Sopenharmony_ci#include "include/core/SkCanvas.h"
11cb93a386Sopenharmony_ci#include "include/core/SkPath.h"
12cb93a386Sopenharmony_ci#include "include/core/SkPathMeasure.h"
13cb93a386Sopenharmony_ci#include "include/utils/SkParsePath.h"
14cb93a386Sopenharmony_ci#include "samplecode/Sample.h"
15cb93a386Sopenharmony_ci
16cb93a386Sopenharmony_ci#include "src/core/SkGeometry.h"
17cb93a386Sopenharmony_ci
18cb93a386Sopenharmony_ci#include <stack>
19cb93a386Sopenharmony_ci
20cb93a386Sopenharmony_cinamespace {
21cb93a386Sopenharmony_ci
22cb93a386Sopenharmony_ci//////////////////////////////////////////////////////////////////////////////
23cb93a386Sopenharmony_ci
24cb93a386Sopenharmony_ciconstexpr inline SkPoint rotate90(const SkPoint& p) { return {p.fY, -p.fX}; }
25cb93a386Sopenharmony_ciinline SkPoint rotate180(const SkPoint& p) { return p * -1; }
26cb93a386Sopenharmony_ciinline bool isClockwise(const SkPoint& a, const SkPoint& b) { return a.cross(b) > 0; }
27cb93a386Sopenharmony_ci
28cb93a386Sopenharmony_cistatic SkPoint checkSetLength(SkPoint p, float len, const char* file, int line) {
29cb93a386Sopenharmony_ci    if (!p.setLength(len)) {
30cb93a386Sopenharmony_ci        SkDebugf("%s:%d: Failed to set point length\n", file, line);
31cb93a386Sopenharmony_ci    }
32cb93a386Sopenharmony_ci    return p;
33cb93a386Sopenharmony_ci}
34cb93a386Sopenharmony_ci
35cb93a386Sopenharmony_ci/** Version of setLength that prints debug msg on failure to help catch edge cases */
36cb93a386Sopenharmony_ci#define setLength(p, len) checkSetLength(p, len, __FILE__, __LINE__)
37cb93a386Sopenharmony_ci
38cb93a386Sopenharmony_ciconstexpr uint64_t choose(uint64_t n, uint64_t k) {
39cb93a386Sopenharmony_ci    SkASSERT(n >= k);
40cb93a386Sopenharmony_ci    uint64_t result = 1;
41cb93a386Sopenharmony_ci    for (uint64_t i = 1; i <= k; i++) {
42cb93a386Sopenharmony_ci        result *= (n + 1 - i);
43cb93a386Sopenharmony_ci        result /= i;
44cb93a386Sopenharmony_ci    }
45cb93a386Sopenharmony_ci    return result;
46cb93a386Sopenharmony_ci}
47cb93a386Sopenharmony_ci
48cb93a386Sopenharmony_ci//////////////////////////////////////////////////////////////////////////////
49cb93a386Sopenharmony_ci
50cb93a386Sopenharmony_ci/**
51cb93a386Sopenharmony_ci * A scalar (float-valued weights) Bezier curve of arbitrary degree.
52cb93a386Sopenharmony_ci */
53cb93a386Sopenharmony_ciclass ScalarBezCurve {
54cb93a386Sopenharmony_cipublic:
55cb93a386Sopenharmony_ci    inline static constexpr int kDegreeInvalid = -1;
56cb93a386Sopenharmony_ci
57cb93a386Sopenharmony_ci    /** Creates an empty curve with invalid degree. */
58cb93a386Sopenharmony_ci    ScalarBezCurve() : fDegree(kDegreeInvalid) {}
59cb93a386Sopenharmony_ci
60cb93a386Sopenharmony_ci    /** Creates a curve of the specified degree with weights initialized to 0. */
61cb93a386Sopenharmony_ci    explicit ScalarBezCurve(int degree) : fDegree(degree) {
62cb93a386Sopenharmony_ci        SkASSERT(degree >= 0);
63cb93a386Sopenharmony_ci        fWeights.resize(degree + 1, {0});
64cb93a386Sopenharmony_ci    }
65cb93a386Sopenharmony_ci
66cb93a386Sopenharmony_ci    /** Creates a curve of specified degree with the given weights. */
67cb93a386Sopenharmony_ci    ScalarBezCurve(int degree, const std::vector<float>& weights) : ScalarBezCurve(degree) {
68cb93a386Sopenharmony_ci        SkASSERT(degree >= 0);
69cb93a386Sopenharmony_ci        SkASSERT(weights.size() == (size_t)degree + 1);
70cb93a386Sopenharmony_ci        fWeights.insert(fWeights.begin(), weights.begin(), weights.end());
71cb93a386Sopenharmony_ci    }
72cb93a386Sopenharmony_ci
73cb93a386Sopenharmony_ci    /** Returns the extreme-valued weight */
74cb93a386Sopenharmony_ci    float extremumWeight() const {
75cb93a386Sopenharmony_ci        float f = 0;
76cb93a386Sopenharmony_ci        int sign = 1;
77cb93a386Sopenharmony_ci        for (float w : fWeights) {
78cb93a386Sopenharmony_ci            if (std::abs(w) > f) {
79cb93a386Sopenharmony_ci                f = std::abs(w);
80cb93a386Sopenharmony_ci                sign = w >= 0 ? 1 : -1;
81cb93a386Sopenharmony_ci            }
82cb93a386Sopenharmony_ci        }
83cb93a386Sopenharmony_ci        return sign * f;
84cb93a386Sopenharmony_ci    }
85cb93a386Sopenharmony_ci
86cb93a386Sopenharmony_ci    /** Evaluates the curve at t */
87cb93a386Sopenharmony_ci    float eval(float t) const { return Eval(*this, t); }
88cb93a386Sopenharmony_ci
89cb93a386Sopenharmony_ci    /** Evaluates the curve at t */
90cb93a386Sopenharmony_ci    static float Eval(const ScalarBezCurve& curve, float t) {
91cb93a386Sopenharmony_ci        // Set up starting point of recursion (k=0)
92cb93a386Sopenharmony_ci        ScalarBezCurve result = curve;
93cb93a386Sopenharmony_ci
94cb93a386Sopenharmony_ci        for (int k = 1; k <= curve.fDegree; k++) {
95cb93a386Sopenharmony_ci            // k is level of recursion, k-1 has previous level's values.
96cb93a386Sopenharmony_ci            for (int i = curve.fDegree; i >= k; i--) {
97cb93a386Sopenharmony_ci                result.fWeights[i] = result.fWeights[i - 1] * (1 - t) + result.fWeights[i] * t;
98cb93a386Sopenharmony_ci            }
99cb93a386Sopenharmony_ci        }
100cb93a386Sopenharmony_ci
101cb93a386Sopenharmony_ci        return result.fWeights[curve.fDegree];
102cb93a386Sopenharmony_ci    }
103cb93a386Sopenharmony_ci
104cb93a386Sopenharmony_ci    /** Splits this curve at t into two halves (of the same degree) */
105cb93a386Sopenharmony_ci    void split(float t, ScalarBezCurve* left, ScalarBezCurve* right) const {
106cb93a386Sopenharmony_ci        Split(*this, t, left, right);
107cb93a386Sopenharmony_ci    }
108cb93a386Sopenharmony_ci
109cb93a386Sopenharmony_ci    /** Splits this curve into the subinterval [tmin,tmax]. */
110cb93a386Sopenharmony_ci    void split(float tmin, float tmax, ScalarBezCurve* result) const {
111cb93a386Sopenharmony_ci        // TODO: I believe there's a more efficient algorithm for this
112cb93a386Sopenharmony_ci        const float tRel = tmin / tmax;
113cb93a386Sopenharmony_ci        ScalarBezCurve ll, rl, rr;
114cb93a386Sopenharmony_ci        this->split(tmax, &rl, &rr);
115cb93a386Sopenharmony_ci        rl.split(tRel, &ll, result);
116cb93a386Sopenharmony_ci    }
117cb93a386Sopenharmony_ci
118cb93a386Sopenharmony_ci    /** Splits the curve at t into two halves (of the same degree) */
119cb93a386Sopenharmony_ci    static void Split(const ScalarBezCurve& curve,
120cb93a386Sopenharmony_ci                      float t,
121cb93a386Sopenharmony_ci                      ScalarBezCurve* left,
122cb93a386Sopenharmony_ci                      ScalarBezCurve* right) {
123cb93a386Sopenharmony_ci        // Set up starting point of recursion (k=0)
124cb93a386Sopenharmony_ci        const int degree = curve.fDegree;
125cb93a386Sopenharmony_ci        ScalarBezCurve result = curve;
126cb93a386Sopenharmony_ci        *left = ScalarBezCurve(degree);
127cb93a386Sopenharmony_ci        *right = ScalarBezCurve(degree);
128cb93a386Sopenharmony_ci        left->fWeights[0] = curve.fWeights[0];
129cb93a386Sopenharmony_ci        right->fWeights[degree] = curve.fWeights[degree];
130cb93a386Sopenharmony_ci
131cb93a386Sopenharmony_ci        for (int k = 1; k <= degree; k++) {
132cb93a386Sopenharmony_ci            // k is level of recursion, k-1 has previous level's values.
133cb93a386Sopenharmony_ci            for (int i = degree; i >= k; i--) {
134cb93a386Sopenharmony_ci                result.fWeights[i] = result.fWeights[i - 1] * (1 - t) + result.fWeights[i] * t;
135cb93a386Sopenharmony_ci            }
136cb93a386Sopenharmony_ci
137cb93a386Sopenharmony_ci            left->fWeights[k] = result.fWeights[k];
138cb93a386Sopenharmony_ci            right->fWeights[degree - k] = result.fWeights[degree];
139cb93a386Sopenharmony_ci        }
140cb93a386Sopenharmony_ci    }
141cb93a386Sopenharmony_ci
142cb93a386Sopenharmony_ci    /**
143cb93a386Sopenharmony_ci     * Increases the degree of the curve to the given degree. Has no effect if the
144cb93a386Sopenharmony_ci     * degree is already equal to the given degree.
145cb93a386Sopenharmony_ci     *
146cb93a386Sopenharmony_ci     * This process is always exact (NB the reverse, degree reduction, is not exact).
147cb93a386Sopenharmony_ci     */
148cb93a386Sopenharmony_ci    void elevateDegree(int newDegree) {
149cb93a386Sopenharmony_ci        if (newDegree == fDegree) {
150cb93a386Sopenharmony_ci            return;
151cb93a386Sopenharmony_ci        }
152cb93a386Sopenharmony_ci
153cb93a386Sopenharmony_ci        fWeights = ElevateDegree(*this, newDegree).fWeights;
154cb93a386Sopenharmony_ci        fDegree = newDegree;
155cb93a386Sopenharmony_ci    }
156cb93a386Sopenharmony_ci
157cb93a386Sopenharmony_ci    /**
158cb93a386Sopenharmony_ci     * Increases the degree of the curve to the given degree. Has no effect if the
159cb93a386Sopenharmony_ci     * degree is already equal to the given degree.
160cb93a386Sopenharmony_ci     *
161cb93a386Sopenharmony_ci     * This process is always exact (NB the reverse, degree reduction, is not exact).
162cb93a386Sopenharmony_ci     */
163cb93a386Sopenharmony_ci    static ScalarBezCurve ElevateDegree(const ScalarBezCurve& curve, int newDegree) {
164cb93a386Sopenharmony_ci        SkASSERT(newDegree >= curve.degree());
165cb93a386Sopenharmony_ci        if (newDegree == curve.degree()) {
166cb93a386Sopenharmony_ci            return curve;
167cb93a386Sopenharmony_ci        }
168cb93a386Sopenharmony_ci
169cb93a386Sopenharmony_ci        // From Farouki, Rajan, "Algorithms for polynomials in Bernstein form" 1988.
170cb93a386Sopenharmony_ci        ScalarBezCurve elevated(newDegree);
171cb93a386Sopenharmony_ci        const int r = newDegree - curve.fDegree;
172cb93a386Sopenharmony_ci        const int n = curve.fDegree;
173cb93a386Sopenharmony_ci
174cb93a386Sopenharmony_ci        for (int i = 0; i <= n + r; i++) {
175cb93a386Sopenharmony_ci            elevated.fWeights[i] = 0;
176cb93a386Sopenharmony_ci            for (int j = std::max(0, i - r); j <= std::min(n, i); j++) {
177cb93a386Sopenharmony_ci                const float f =
178cb93a386Sopenharmony_ci                        (choose(n, j) * choose(r, i - j)) / static_cast<float>(choose(n + r, i));
179cb93a386Sopenharmony_ci                elevated.fWeights[i] += curve.fWeights[j] * f;
180cb93a386Sopenharmony_ci            }
181cb93a386Sopenharmony_ci        }
182cb93a386Sopenharmony_ci
183cb93a386Sopenharmony_ci        return elevated;
184cb93a386Sopenharmony_ci    }
185cb93a386Sopenharmony_ci
186cb93a386Sopenharmony_ci    /**
187cb93a386Sopenharmony_ci     * Returns the zero-set of this curve, which is a list of t values where the curve crosses 0.
188cb93a386Sopenharmony_ci     */
189cb93a386Sopenharmony_ci    std::vector<float> zeroSet() const { return ZeroSet(*this); }
190cb93a386Sopenharmony_ci
191cb93a386Sopenharmony_ci    /**
192cb93a386Sopenharmony_ci     * Returns the zero-set of the curve, which is a list of t values where the curve crosses 0.
193cb93a386Sopenharmony_ci     */
194cb93a386Sopenharmony_ci    static std::vector<float> ZeroSet(const ScalarBezCurve& curve) {
195cb93a386Sopenharmony_ci        constexpr float kTol = 0.001f;
196cb93a386Sopenharmony_ci        std::vector<float> result;
197cb93a386Sopenharmony_ci        ZeroSetRec(curve, 0, 1, kTol, &result);
198cb93a386Sopenharmony_ci        return result;
199cb93a386Sopenharmony_ci    }
200cb93a386Sopenharmony_ci
201cb93a386Sopenharmony_ci    /** Multiplies the curve's weights by a constant value */
202cb93a386Sopenharmony_ci    static ScalarBezCurve Mul(const ScalarBezCurve& curve, float f) {
203cb93a386Sopenharmony_ci        ScalarBezCurve result = curve;
204cb93a386Sopenharmony_ci        for (int k = 0; k <= curve.fDegree; k++) {
205cb93a386Sopenharmony_ci            result.fWeights[k] *= f;
206cb93a386Sopenharmony_ci        }
207cb93a386Sopenharmony_ci        return result;
208cb93a386Sopenharmony_ci    }
209cb93a386Sopenharmony_ci
210cb93a386Sopenharmony_ci    /**
211cb93a386Sopenharmony_ci     * Multiplies the two curves and returns the result.
212cb93a386Sopenharmony_ci     *
213cb93a386Sopenharmony_ci     * Degree of resulting curve is the sum of the degrees of the input curves.
214cb93a386Sopenharmony_ci     */
215cb93a386Sopenharmony_ci    static ScalarBezCurve Mul(const ScalarBezCurve& a, const ScalarBezCurve& b) {
216cb93a386Sopenharmony_ci        // From G. Elber, "Free form surface analysis using a hybrid of symbolic and numeric
217cb93a386Sopenharmony_ci        // computation". PhD thesis, 1992. p.11.
218cb93a386Sopenharmony_ci        const int n = a.degree(), m = b.degree();
219cb93a386Sopenharmony_ci        const int newDegree = n + m;
220cb93a386Sopenharmony_ci        ScalarBezCurve result(newDegree);
221cb93a386Sopenharmony_ci
222cb93a386Sopenharmony_ci        for (int k = 0; k <= newDegree; k++) {
223cb93a386Sopenharmony_ci            result.fWeights[k] = 0;
224cb93a386Sopenharmony_ci            for (int i = std::max(0, k - n); i <= std::min(k, m); i++) {
225cb93a386Sopenharmony_ci                const float f =
226cb93a386Sopenharmony_ci                        (choose(m, i) * choose(n, k - i)) / static_cast<float>(choose(m + n, k));
227cb93a386Sopenharmony_ci                result.fWeights[k] += a.fWeights[i] * b.fWeights[k - i] * f;
228cb93a386Sopenharmony_ci            }
229cb93a386Sopenharmony_ci        }
230cb93a386Sopenharmony_ci
231cb93a386Sopenharmony_ci        return result;
232cb93a386Sopenharmony_ci    }
233cb93a386Sopenharmony_ci
234cb93a386Sopenharmony_ci    /** Returns a^2 + b^2. This is a specialized method because the loops are easily fused. */
235cb93a386Sopenharmony_ci    static ScalarBezCurve AddSquares(const ScalarBezCurve& a, const ScalarBezCurve& b) {
236cb93a386Sopenharmony_ci        const int n = a.degree(), m = b.degree();
237cb93a386Sopenharmony_ci        const int newDegree = n + m;
238cb93a386Sopenharmony_ci        ScalarBezCurve result(newDegree);
239cb93a386Sopenharmony_ci
240cb93a386Sopenharmony_ci        for (int k = 0; k <= newDegree; k++) {
241cb93a386Sopenharmony_ci            float aSq = 0, bSq = 0;
242cb93a386Sopenharmony_ci            for (int i = std::max(0, k - n); i <= std::min(k, m); i++) {
243cb93a386Sopenharmony_ci                const float f =
244cb93a386Sopenharmony_ci                        (choose(m, i) * choose(n, k - i)) / static_cast<float>(choose(m + n, k));
245cb93a386Sopenharmony_ci                aSq += a.fWeights[i] * a.fWeights[k - i] * f;
246cb93a386Sopenharmony_ci                bSq += b.fWeights[i] * b.fWeights[k - i] * f;
247cb93a386Sopenharmony_ci            }
248cb93a386Sopenharmony_ci            result.fWeights[k] = aSq + bSq;
249cb93a386Sopenharmony_ci        }
250cb93a386Sopenharmony_ci
251cb93a386Sopenharmony_ci        return result;
252cb93a386Sopenharmony_ci    }
253cb93a386Sopenharmony_ci
254cb93a386Sopenharmony_ci    /** Returns a - b. */
255cb93a386Sopenharmony_ci    static ScalarBezCurve Sub(const ScalarBezCurve& a, const ScalarBezCurve& b) {
256cb93a386Sopenharmony_ci        ScalarBezCurve result = a;
257cb93a386Sopenharmony_ci        result.sub(b);
258cb93a386Sopenharmony_ci        return result;
259cb93a386Sopenharmony_ci    }
260cb93a386Sopenharmony_ci
261cb93a386Sopenharmony_ci    /** Subtracts the other curve from this curve */
262cb93a386Sopenharmony_ci    void sub(const ScalarBezCurve& other) {
263cb93a386Sopenharmony_ci        SkASSERT(other.fDegree == fDegree);
264cb93a386Sopenharmony_ci        for (int k = 0; k <= fDegree; k++) {
265cb93a386Sopenharmony_ci            fWeights[k] -= other.fWeights[k];
266cb93a386Sopenharmony_ci        }
267cb93a386Sopenharmony_ci    }
268cb93a386Sopenharmony_ci
269cb93a386Sopenharmony_ci    /** Subtracts a constant from this curve */
270cb93a386Sopenharmony_ci    void sub(float f) {
271cb93a386Sopenharmony_ci        for (int k = 0; k <= fDegree; k++) {
272cb93a386Sopenharmony_ci            fWeights[k] -= f;
273cb93a386Sopenharmony_ci        }
274cb93a386Sopenharmony_ci    }
275cb93a386Sopenharmony_ci
276cb93a386Sopenharmony_ci    /** Returns the curve degree */
277cb93a386Sopenharmony_ci    int degree() const { return fDegree; }
278cb93a386Sopenharmony_ci
279cb93a386Sopenharmony_ci    /** Returns the curve weights */
280cb93a386Sopenharmony_ci    const std::vector<float>& weights() const { return fWeights; }
281cb93a386Sopenharmony_ci
282cb93a386Sopenharmony_ci    float operator[](size_t i) const { return fWeights[i]; }
283cb93a386Sopenharmony_ci    float& operator[](size_t i) { return fWeights[i]; }
284cb93a386Sopenharmony_ci
285cb93a386Sopenharmony_ciprivate:
286cb93a386Sopenharmony_ci    /** Recursive helper for ZeroSet */
287cb93a386Sopenharmony_ci    static void ZeroSetRec(const ScalarBezCurve& curve,
288cb93a386Sopenharmony_ci                           float tmin,
289cb93a386Sopenharmony_ci                           float tmax,
290cb93a386Sopenharmony_ci                           float tol,
291cb93a386Sopenharmony_ci                           std::vector<float>* result) {
292cb93a386Sopenharmony_ci        float lenP = 0;
293cb93a386Sopenharmony_ci        bool allPos = curve.fWeights[0] >= 0, allNeg = curve.fWeights[0] < 0;
294cb93a386Sopenharmony_ci        for (int i = 1; i <= curve.fDegree; i++) {
295cb93a386Sopenharmony_ci            lenP += std::abs(curve.fWeights[i] - curve.fWeights[i - 1]);
296cb93a386Sopenharmony_ci            allPos &= curve.fWeights[i] >= 0;
297cb93a386Sopenharmony_ci            allNeg &= curve.fWeights[i] < 0;
298cb93a386Sopenharmony_ci        }
299cb93a386Sopenharmony_ci        if (lenP <= tol) {
300cb93a386Sopenharmony_ci            result->push_back((tmin + tmax) * 0.5);
301cb93a386Sopenharmony_ci            return;
302cb93a386Sopenharmony_ci        } else if (allPos || allNeg) {
303cb93a386Sopenharmony_ci            // No zero crossings possible if the coefficients don't change sign (convex hull
304cb93a386Sopenharmony_ci            // property)
305cb93a386Sopenharmony_ci            return;
306cb93a386Sopenharmony_ci        } else if (SkScalarNearlyZero(tmax - tmin)) {
307cb93a386Sopenharmony_ci            return;
308cb93a386Sopenharmony_ci        } else {
309cb93a386Sopenharmony_ci            ScalarBezCurve left(curve.fDegree), right(curve.fDegree);
310cb93a386Sopenharmony_ci            Split(curve, 0.5f, &left, &right);
311cb93a386Sopenharmony_ci
312cb93a386Sopenharmony_ci            const float tmid = (tmin + tmax) * 0.5;
313cb93a386Sopenharmony_ci            ZeroSetRec(left, tmin, tmid, tol, result);
314cb93a386Sopenharmony_ci            ZeroSetRec(right, tmid, tmax, tol, result);
315cb93a386Sopenharmony_ci        }
316cb93a386Sopenharmony_ci    }
317cb93a386Sopenharmony_ci
318cb93a386Sopenharmony_ci    int fDegree;
319cb93a386Sopenharmony_ci    std::vector<float> fWeights;
320cb93a386Sopenharmony_ci};
321cb93a386Sopenharmony_ci
322cb93a386Sopenharmony_ci//////////////////////////////////////////////////////////////////////////////
323cb93a386Sopenharmony_ci
324cb93a386Sopenharmony_ci/** Helper class that measures per-verb path lengths. */
325cb93a386Sopenharmony_ciclass PathVerbMeasure {
326cb93a386Sopenharmony_cipublic:
327cb93a386Sopenharmony_ci    explicit PathVerbMeasure(const SkPath& path) : fPath(path), fIter(path, false) { nextVerb(); }
328cb93a386Sopenharmony_ci
329cb93a386Sopenharmony_ci    SkScalar totalLength() const;
330cb93a386Sopenharmony_ci
331cb93a386Sopenharmony_ci    SkScalar currentVerbLength() { return fMeas.getLength(); }
332cb93a386Sopenharmony_ci
333cb93a386Sopenharmony_ci    void nextVerb();
334cb93a386Sopenharmony_ci
335cb93a386Sopenharmony_ciprivate:
336cb93a386Sopenharmony_ci    const SkPath& fPath;
337cb93a386Sopenharmony_ci    SkPoint fFirstPointInContour;
338cb93a386Sopenharmony_ci    SkPoint fPreviousPoint;
339cb93a386Sopenharmony_ci    SkPath fCurrVerb;
340cb93a386Sopenharmony_ci    SkPath::Iter fIter;
341cb93a386Sopenharmony_ci    SkPathMeasure fMeas;
342cb93a386Sopenharmony_ci};
343cb93a386Sopenharmony_ci
344cb93a386Sopenharmony_ciSkScalar PathVerbMeasure::totalLength() const {
345cb93a386Sopenharmony_ci    SkPathMeasure meas(fPath, false);
346cb93a386Sopenharmony_ci    return meas.getLength();
347cb93a386Sopenharmony_ci}
348cb93a386Sopenharmony_ci
349cb93a386Sopenharmony_civoid PathVerbMeasure::nextVerb() {
350cb93a386Sopenharmony_ci    SkPoint pts[4];
351cb93a386Sopenharmony_ci    SkPath::Verb verb = fIter.next(pts);
352cb93a386Sopenharmony_ci
353cb93a386Sopenharmony_ci    while (verb == SkPath::kMove_Verb || verb == SkPath::kClose_Verb) {
354cb93a386Sopenharmony_ci        if (verb == SkPath::kMove_Verb) {
355cb93a386Sopenharmony_ci            fFirstPointInContour = pts[0];
356cb93a386Sopenharmony_ci            fPreviousPoint = fFirstPointInContour;
357cb93a386Sopenharmony_ci        }
358cb93a386Sopenharmony_ci        verb = fIter.next(pts);
359cb93a386Sopenharmony_ci    }
360cb93a386Sopenharmony_ci
361cb93a386Sopenharmony_ci    fCurrVerb.rewind();
362cb93a386Sopenharmony_ci    fCurrVerb.moveTo(fPreviousPoint);
363cb93a386Sopenharmony_ci    switch (verb) {
364cb93a386Sopenharmony_ci        case SkPath::kLine_Verb:
365cb93a386Sopenharmony_ci            fCurrVerb.lineTo(pts[1]);
366cb93a386Sopenharmony_ci            break;
367cb93a386Sopenharmony_ci        case SkPath::kQuad_Verb:
368cb93a386Sopenharmony_ci            fCurrVerb.quadTo(pts[1], pts[2]);
369cb93a386Sopenharmony_ci            break;
370cb93a386Sopenharmony_ci        case SkPath::kCubic_Verb:
371cb93a386Sopenharmony_ci            fCurrVerb.cubicTo(pts[1], pts[2], pts[3]);
372cb93a386Sopenharmony_ci            break;
373cb93a386Sopenharmony_ci        case SkPath::kConic_Verb:
374cb93a386Sopenharmony_ci            fCurrVerb.conicTo(pts[1], pts[2], fIter.conicWeight());
375cb93a386Sopenharmony_ci            break;
376cb93a386Sopenharmony_ci        case SkPath::kDone_Verb:
377cb93a386Sopenharmony_ci            break;
378cb93a386Sopenharmony_ci        case SkPath::kClose_Verb:
379cb93a386Sopenharmony_ci        case SkPath::kMove_Verb:
380cb93a386Sopenharmony_ci            SkASSERT(false);
381cb93a386Sopenharmony_ci            break;
382cb93a386Sopenharmony_ci    }
383cb93a386Sopenharmony_ci
384cb93a386Sopenharmony_ci    fCurrVerb.getLastPt(&fPreviousPoint);
385cb93a386Sopenharmony_ci    fMeas.setPath(&fCurrVerb, false);
386cb93a386Sopenharmony_ci}
387cb93a386Sopenharmony_ci
388cb93a386Sopenharmony_ci//////////////////////////////////////////////////////////////////////////////
389cb93a386Sopenharmony_ci
390cb93a386Sopenharmony_ci// Several debug-only visualization helpers
391cb93a386Sopenharmony_cinamespace viz {
392cb93a386Sopenharmony_cistd::unique_ptr<ScalarBezCurve> outerErr;
393cb93a386Sopenharmony_ciSkPath outerFirstApprox;
394cb93a386Sopenharmony_ci}  // namespace viz
395cb93a386Sopenharmony_ci
396cb93a386Sopenharmony_ci/**
397cb93a386Sopenharmony_ci * Prototype variable-width path stroker.
398cb93a386Sopenharmony_ci *
399cb93a386Sopenharmony_ci * Takes as input a path to be stroked, and two distance functions (inside and outside).
400cb93a386Sopenharmony_ci * Produces a fill path with the stroked path geometry.
401cb93a386Sopenharmony_ci *
402cb93a386Sopenharmony_ci * The algorithms in use here are from:
403cb93a386Sopenharmony_ci *
404cb93a386Sopenharmony_ci * G. Elber, E. Cohen. "Error bounded variable distance offset operator for free form curves and
405cb93a386Sopenharmony_ci * surfaces." International Journal of Computational Geometry & Applications 1, no. 01 (1991)
406cb93a386Sopenharmony_ci *
407cb93a386Sopenharmony_ci * G. Elber. "Free form surface analysis using a hybrid of symbolic and numeric computation."
408cb93a386Sopenharmony_ci * PhD diss., Dept. of Computer Science, University of Utah, 1992.
409cb93a386Sopenharmony_ci */
410cb93a386Sopenharmony_ciclass SkVarWidthStroker {
411cb93a386Sopenharmony_cipublic:
412cb93a386Sopenharmony_ci    /** Metric to use for interpolation of distance function across path segments. */
413cb93a386Sopenharmony_ci    enum class LengthMetric {
414cb93a386Sopenharmony_ci        /** Each path segment gets an equal interval of t in [0,1] */
415cb93a386Sopenharmony_ci        kNumSegments,
416cb93a386Sopenharmony_ci        /** Each path segment gets t interval equal to its percent of the total path length */
417cb93a386Sopenharmony_ci        kPathLength,
418cb93a386Sopenharmony_ci    };
419cb93a386Sopenharmony_ci
420cb93a386Sopenharmony_ci    /**
421cb93a386Sopenharmony_ci     * Strokes the path with a fixed-width distance function. This produces a traditional stroked
422cb93a386Sopenharmony_ci     * path.
423cb93a386Sopenharmony_ci     */
424cb93a386Sopenharmony_ci    SkPath getFillPath(const SkPath& path, const SkPaint& paint) {
425cb93a386Sopenharmony_ci        return getFillPath(path, paint, identityVarWidth(paint.getStrokeWidth()),
426cb93a386Sopenharmony_ci                           identityVarWidth(paint.getStrokeWidth()));
427cb93a386Sopenharmony_ci    }
428cb93a386Sopenharmony_ci
429cb93a386Sopenharmony_ci    /**
430cb93a386Sopenharmony_ci     * Strokes the given path using the two given distance functions for inner and outer offsets.
431cb93a386Sopenharmony_ci     */
432cb93a386Sopenharmony_ci    SkPath getFillPath(const SkPath& path,
433cb93a386Sopenharmony_ci                       const SkPaint& paint,
434cb93a386Sopenharmony_ci                       const ScalarBezCurve& varWidth,
435cb93a386Sopenharmony_ci                       const ScalarBezCurve& varWidthInner,
436cb93a386Sopenharmony_ci                       LengthMetric lengthMetric = LengthMetric::kNumSegments);
437cb93a386Sopenharmony_ci
438cb93a386Sopenharmony_ciprivate:
439cb93a386Sopenharmony_ci    /** Helper struct referring to a single segment of an SkPath */
440cb93a386Sopenharmony_ci    struct PathSegment {
441cb93a386Sopenharmony_ci        SkPath::Verb fVerb;
442cb93a386Sopenharmony_ci        std::array<SkPoint, 4> fPoints;
443cb93a386Sopenharmony_ci    };
444cb93a386Sopenharmony_ci
445cb93a386Sopenharmony_ci    struct OffsetSegments {
446cb93a386Sopenharmony_ci        std::vector<PathSegment> fInner;
447cb93a386Sopenharmony_ci        std::vector<PathSegment> fOuter;
448cb93a386Sopenharmony_ci    };
449cb93a386Sopenharmony_ci
450cb93a386Sopenharmony_ci    /** Initialize stroker state */
451cb93a386Sopenharmony_ci    void initForPath(const SkPath& path, const SkPaint& paint);
452cb93a386Sopenharmony_ci
453cb93a386Sopenharmony_ci    /** Strokes a path segment */
454cb93a386Sopenharmony_ci    OffsetSegments strokeSegment(const PathSegment& segment,
455cb93a386Sopenharmony_ci                                 const ScalarBezCurve& varWidth,
456cb93a386Sopenharmony_ci                                 const ScalarBezCurve& varWidthInner,
457cb93a386Sopenharmony_ci                                 bool needsMove);
458cb93a386Sopenharmony_ci
459cb93a386Sopenharmony_ci    /**
460cb93a386Sopenharmony_ci     * Strokes the given segment using the given distance function.
461cb93a386Sopenharmony_ci     *
462cb93a386Sopenharmony_ci     * Returns a list of quad segments that approximate the offset curve.
463cb93a386Sopenharmony_ci     * TODO: no reason this needs to return a vector of quads, can just append to the path
464cb93a386Sopenharmony_ci     */
465cb93a386Sopenharmony_ci    std::vector<PathSegment> strokeSegment(const PathSegment& seg,
466cb93a386Sopenharmony_ci                                           const ScalarBezCurve& distanceFunc) const;
467cb93a386Sopenharmony_ci
468cb93a386Sopenharmony_ci    /** Adds an endcap to fOuter */
469cb93a386Sopenharmony_ci    enum class CapLocation { Start, End };
470cb93a386Sopenharmony_ci    void endcap(CapLocation loc);
471cb93a386Sopenharmony_ci
472cb93a386Sopenharmony_ci    /** Adds a join between the two segments */
473cb93a386Sopenharmony_ci    void join(const SkPoint& common,
474cb93a386Sopenharmony_ci              float innerRadius,
475cb93a386Sopenharmony_ci              float outerRadius,
476cb93a386Sopenharmony_ci              const OffsetSegments& prev,
477cb93a386Sopenharmony_ci              const OffsetSegments& curr);
478cb93a386Sopenharmony_ci
479cb93a386Sopenharmony_ci    /** Appends path in reverse to result */
480cb93a386Sopenharmony_ci    static void appendPathReversed(const SkPath& path, SkPath* result);
481cb93a386Sopenharmony_ci
482cb93a386Sopenharmony_ci    /** Returns the segment unit normal and unit tangent if not nullptr */
483cb93a386Sopenharmony_ci    static SkPoint unitNormal(const PathSegment& seg, float t, SkPoint* tangentOut);
484cb93a386Sopenharmony_ci
485cb93a386Sopenharmony_ci    /** Returns the degree of a segment curve */
486cb93a386Sopenharmony_ci    static int segmentDegree(const PathSegment& seg);
487cb93a386Sopenharmony_ci
488cb93a386Sopenharmony_ci    /** Splits a path segment at t */
489cb93a386Sopenharmony_ci    static void splitSegment(const PathSegment& seg, float t, PathSegment* segA, PathSegment* segB);
490cb93a386Sopenharmony_ci
491cb93a386Sopenharmony_ci    /**
492cb93a386Sopenharmony_ci     * Returns a quadratic segment that approximates the given segment using the given distance
493cb93a386Sopenharmony_ci     * function.
494cb93a386Sopenharmony_ci     */
495cb93a386Sopenharmony_ci    static void approximateSegment(const PathSegment& seg,
496cb93a386Sopenharmony_ci                                   const ScalarBezCurve& distFnc,
497cb93a386Sopenharmony_ci                                   PathSegment* approxQuad);
498cb93a386Sopenharmony_ci
499cb93a386Sopenharmony_ci    /** Returns a constant (deg 0) distance function for the given stroke width */
500cb93a386Sopenharmony_ci    static ScalarBezCurve identityVarWidth(float strokeWidth) {
501cb93a386Sopenharmony_ci        return ScalarBezCurve(0, {strokeWidth / 2.0f});
502cb93a386Sopenharmony_ci    }
503cb93a386Sopenharmony_ci
504cb93a386Sopenharmony_ci    float fRadius;
505cb93a386Sopenharmony_ci    SkPaint::Cap fCap;
506cb93a386Sopenharmony_ci    SkPaint::Join fJoin;
507cb93a386Sopenharmony_ci    SkPath fInner, fOuter;
508cb93a386Sopenharmony_ci    ScalarBezCurve fVarWidth, fVarWidthInner;
509cb93a386Sopenharmony_ci    float fCurrT;
510cb93a386Sopenharmony_ci};
511cb93a386Sopenharmony_ci
512cb93a386Sopenharmony_civoid SkVarWidthStroker::initForPath(const SkPath& path, const SkPaint& paint) {
513cb93a386Sopenharmony_ci    fRadius = paint.getStrokeWidth() / 2;
514cb93a386Sopenharmony_ci    fCap = paint.getStrokeCap();
515cb93a386Sopenharmony_ci    fJoin = paint.getStrokeJoin();
516cb93a386Sopenharmony_ci    fInner.rewind();
517cb93a386Sopenharmony_ci    fOuter.rewind();
518cb93a386Sopenharmony_ci    fCurrT = 0;
519cb93a386Sopenharmony_ci}
520cb93a386Sopenharmony_ci
521cb93a386Sopenharmony_ciSkPath SkVarWidthStroker::getFillPath(const SkPath& path,
522cb93a386Sopenharmony_ci                                      const SkPaint& paint,
523cb93a386Sopenharmony_ci                                      const ScalarBezCurve& varWidth,
524cb93a386Sopenharmony_ci                                      const ScalarBezCurve& varWidthInner,
525cb93a386Sopenharmony_ci                                      LengthMetric lengthMetric) {
526cb93a386Sopenharmony_ci    const auto appendStrokes = [this](const OffsetSegments& strokes, bool needsMove) {
527cb93a386Sopenharmony_ci        if (needsMove) {
528cb93a386Sopenharmony_ci            fOuter.moveTo(strokes.fOuter.front().fPoints[0]);
529cb93a386Sopenharmony_ci            fInner.moveTo(strokes.fInner.front().fPoints[0]);
530cb93a386Sopenharmony_ci        }
531cb93a386Sopenharmony_ci
532cb93a386Sopenharmony_ci        for (const PathSegment& seg : strokes.fOuter) {
533cb93a386Sopenharmony_ci            fOuter.quadTo(seg.fPoints[1], seg.fPoints[2]);
534cb93a386Sopenharmony_ci        }
535cb93a386Sopenharmony_ci
536cb93a386Sopenharmony_ci        for (const PathSegment& seg : strokes.fInner) {
537cb93a386Sopenharmony_ci            fInner.quadTo(seg.fPoints[1], seg.fPoints[2]);
538cb93a386Sopenharmony_ci        }
539cb93a386Sopenharmony_ci    };
540cb93a386Sopenharmony_ci
541cb93a386Sopenharmony_ci    initForPath(path, paint);
542cb93a386Sopenharmony_ci    fVarWidth = varWidth;
543cb93a386Sopenharmony_ci    fVarWidthInner = varWidthInner;
544cb93a386Sopenharmony_ci
545cb93a386Sopenharmony_ci    // TODO: this assumes one contour:
546cb93a386Sopenharmony_ci    PathVerbMeasure meas(path);
547cb93a386Sopenharmony_ci    const float totalPathLength = lengthMetric == LengthMetric::kPathLength
548cb93a386Sopenharmony_ci                                          ? meas.totalLength()
549cb93a386Sopenharmony_ci                                          : (path.countVerbs() - 1);
550cb93a386Sopenharmony_ci
551cb93a386Sopenharmony_ci    // Trace the inner and outer paths simultaneously. Inner will therefore be
552cb93a386Sopenharmony_ci    // recorded in reverse from how we trace the outline.
553cb93a386Sopenharmony_ci    SkPath::Iter it(path, false);
554cb93a386Sopenharmony_ci    PathSegment segment, prevSegment;
555cb93a386Sopenharmony_ci    OffsetSegments offsetSegs, prevOffsetSegs;
556cb93a386Sopenharmony_ci    bool firstSegment = true, prevWasFirst = false;
557cb93a386Sopenharmony_ci
558cb93a386Sopenharmony_ci    float lenTraveled = 0;
559cb93a386Sopenharmony_ci    while ((segment.fVerb = it.next(&segment.fPoints[0])) != SkPath::kDone_Verb) {
560cb93a386Sopenharmony_ci        const float verbLength = lengthMetric == LengthMetric::kPathLength
561cb93a386Sopenharmony_ci                                         ? (meas.currentVerbLength() / totalPathLength)
562cb93a386Sopenharmony_ci                                         : (1.0f / totalPathLength);
563cb93a386Sopenharmony_ci        const float tmin = lenTraveled;
564cb93a386Sopenharmony_ci        const float tmax = lenTraveled + verbLength;
565cb93a386Sopenharmony_ci
566cb93a386Sopenharmony_ci        // Subset the distance function for the current interval.
567cb93a386Sopenharmony_ci        ScalarBezCurve partVarWidth, partVarWidthInner;
568cb93a386Sopenharmony_ci        fVarWidth.split(tmin, tmax, &partVarWidth);
569cb93a386Sopenharmony_ci        fVarWidthInner.split(tmin, tmax, &partVarWidthInner);
570cb93a386Sopenharmony_ci        partVarWidthInner = ScalarBezCurve::Mul(partVarWidthInner, -1);
571cb93a386Sopenharmony_ci
572cb93a386Sopenharmony_ci        // Stroke the current segment
573cb93a386Sopenharmony_ci        switch (segment.fVerb) {
574cb93a386Sopenharmony_ci            case SkPath::kLine_Verb:
575cb93a386Sopenharmony_ci            case SkPath::kQuad_Verb:
576cb93a386Sopenharmony_ci            case SkPath::kCubic_Verb:
577cb93a386Sopenharmony_ci                offsetSegs = strokeSegment(segment, partVarWidth, partVarWidthInner, firstSegment);
578cb93a386Sopenharmony_ci                break;
579cb93a386Sopenharmony_ci            case SkPath::kMove_Verb:
580cb93a386Sopenharmony_ci                // Don't care about multiple contours currently
581cb93a386Sopenharmony_ci                continue;
582cb93a386Sopenharmony_ci            default:
583cb93a386Sopenharmony_ci                SkDebugf("Unhandled path verb %d\n", segment.fVerb);
584cb93a386Sopenharmony_ci                SkASSERT(false);
585cb93a386Sopenharmony_ci                break;
586cb93a386Sopenharmony_ci        }
587cb93a386Sopenharmony_ci
588cb93a386Sopenharmony_ci        // Join to the previous segment
589cb93a386Sopenharmony_ci        if (!firstSegment) {
590cb93a386Sopenharmony_ci            // Append prev inner and outer strokes
591cb93a386Sopenharmony_ci            appendStrokes(prevOffsetSegs, prevWasFirst);
592cb93a386Sopenharmony_ci
593cb93a386Sopenharmony_ci            // Append the join
594cb93a386Sopenharmony_ci            const float innerRadius = varWidthInner.eval(tmin);
595cb93a386Sopenharmony_ci            const float outerRadius = varWidth.eval(tmin);
596cb93a386Sopenharmony_ci            join(segment.fPoints[0], innerRadius, outerRadius, prevOffsetSegs, offsetSegs);
597cb93a386Sopenharmony_ci        }
598cb93a386Sopenharmony_ci
599cb93a386Sopenharmony_ci        std::swap(segment, prevSegment);
600cb93a386Sopenharmony_ci        std::swap(offsetSegs, prevOffsetSegs);
601cb93a386Sopenharmony_ci        prevWasFirst = firstSegment;
602cb93a386Sopenharmony_ci        firstSegment = false;
603cb93a386Sopenharmony_ci        lenTraveled += verbLength;
604cb93a386Sopenharmony_ci        meas.nextVerb();
605cb93a386Sopenharmony_ci    }
606cb93a386Sopenharmony_ci
607cb93a386Sopenharmony_ci    // Finish appending final offset segments
608cb93a386Sopenharmony_ci    appendStrokes(prevOffsetSegs, prevWasFirst);
609cb93a386Sopenharmony_ci
610cb93a386Sopenharmony_ci    // Open contour => endcap at the end
611cb93a386Sopenharmony_ci    const bool isClosed = path.isLastContourClosed();
612cb93a386Sopenharmony_ci    if (isClosed) {
613cb93a386Sopenharmony_ci        SkDebugf("Unhandled closed contour\n");
614cb93a386Sopenharmony_ci        SkASSERT(false);
615cb93a386Sopenharmony_ci    } else {
616cb93a386Sopenharmony_ci        endcap(CapLocation::End);
617cb93a386Sopenharmony_ci    }
618cb93a386Sopenharmony_ci
619cb93a386Sopenharmony_ci    // Walk inner path in reverse, appending to result
620cb93a386Sopenharmony_ci    appendPathReversed(fInner, &fOuter);
621cb93a386Sopenharmony_ci    endcap(CapLocation::Start);
622cb93a386Sopenharmony_ci
623cb93a386Sopenharmony_ci    return fOuter;
624cb93a386Sopenharmony_ci}
625cb93a386Sopenharmony_ci
626cb93a386Sopenharmony_ciSkVarWidthStroker::OffsetSegments SkVarWidthStroker::strokeSegment(
627cb93a386Sopenharmony_ci        const PathSegment& segment,
628cb93a386Sopenharmony_ci        const ScalarBezCurve& varWidth,
629cb93a386Sopenharmony_ci        const ScalarBezCurve& varWidthInner,
630cb93a386Sopenharmony_ci        bool needsMove) {
631cb93a386Sopenharmony_ci    viz::outerErr.reset(nullptr);
632cb93a386Sopenharmony_ci
633cb93a386Sopenharmony_ci    std::vector<PathSegment> outer = strokeSegment(segment, varWidth);
634cb93a386Sopenharmony_ci    std::vector<PathSegment> inner = strokeSegment(segment, varWidthInner);
635cb93a386Sopenharmony_ci    return {inner, outer};
636cb93a386Sopenharmony_ci}
637cb93a386Sopenharmony_ci
638cb93a386Sopenharmony_cistd::vector<SkVarWidthStroker::PathSegment> SkVarWidthStroker::strokeSegment(
639cb93a386Sopenharmony_ci        const PathSegment& seg, const ScalarBezCurve& distanceFunc) const {
640cb93a386Sopenharmony_ci    // Work item for the recursive splitting stack.
641cb93a386Sopenharmony_ci    struct Item {
642cb93a386Sopenharmony_ci        PathSegment fSeg;
643cb93a386Sopenharmony_ci        ScalarBezCurve fDistFnc, fDistFncSqd;
644cb93a386Sopenharmony_ci        ScalarBezCurve fSegX, fSegY;
645cb93a386Sopenharmony_ci
646cb93a386Sopenharmony_ci        Item(const PathSegment& seg,
647cb93a386Sopenharmony_ci             const ScalarBezCurve& distFnc,
648cb93a386Sopenharmony_ci             const ScalarBezCurve& distFncSqd)
649cb93a386Sopenharmony_ci                : fSeg(seg), fDistFnc(distFnc), fDistFncSqd(distFncSqd) {
650cb93a386Sopenharmony_ci            const int segDegree = segmentDegree(seg);
651cb93a386Sopenharmony_ci            fSegX = ScalarBezCurve(segDegree);
652cb93a386Sopenharmony_ci            fSegY = ScalarBezCurve(segDegree);
653cb93a386Sopenharmony_ci            for (int i = 0; i <= segDegree; i++) {
654cb93a386Sopenharmony_ci                fSegX[i] = seg.fPoints[i].fX;
655cb93a386Sopenharmony_ci                fSegY[i] = seg.fPoints[i].fY;
656cb93a386Sopenharmony_ci            }
657cb93a386Sopenharmony_ci        }
658cb93a386Sopenharmony_ci    };
659cb93a386Sopenharmony_ci
660cb93a386Sopenharmony_ci    // Push the initial segment and distance function
661cb93a386Sopenharmony_ci    std::stack<Item> stack;
662cb93a386Sopenharmony_ci    stack.push(Item(seg, distanceFunc, ScalarBezCurve::Mul(distanceFunc, distanceFunc)));
663cb93a386Sopenharmony_ci
664cb93a386Sopenharmony_ci    std::vector<PathSegment> result;
665cb93a386Sopenharmony_ci    constexpr int kMaxIters = 5000; /** TODO: this is completely arbitrary */
666cb93a386Sopenharmony_ci    int iter = 0;
667cb93a386Sopenharmony_ci    while (!stack.empty()) {
668cb93a386Sopenharmony_ci        if (iter++ >= kMaxIters) break;
669cb93a386Sopenharmony_ci        const Item item = stack.top();
670cb93a386Sopenharmony_ci        stack.pop();
671cb93a386Sopenharmony_ci
672cb93a386Sopenharmony_ci        const ScalarBezCurve& distFnc = item.fDistFnc;
673cb93a386Sopenharmony_ci        ScalarBezCurve distFncSqd = item.fDistFncSqd;
674cb93a386Sopenharmony_ci        const float kTol = std::abs(0.5f * distFnc.extremumWeight());
675cb93a386Sopenharmony_ci
676cb93a386Sopenharmony_ci        // Compute a quad that approximates stroke outline
677cb93a386Sopenharmony_ci        PathSegment quadApprox;
678cb93a386Sopenharmony_ci        approximateSegment(item.fSeg, distFnc, &quadApprox);
679cb93a386Sopenharmony_ci        ScalarBezCurve quadApproxX(2), quadApproxY(2);
680cb93a386Sopenharmony_ci        for (int i = 0; i < 3; i++) {
681cb93a386Sopenharmony_ci            quadApproxX[i] = quadApprox.fPoints[i].fX;
682cb93a386Sopenharmony_ci            quadApproxY[i] = quadApprox.fPoints[i].fY;
683cb93a386Sopenharmony_ci        }
684cb93a386Sopenharmony_ci
685cb93a386Sopenharmony_ci        // Compute control polygon for the delta(t) curve. First must elevate to a common degree.
686cb93a386Sopenharmony_ci        const int deltaDegree = std::max(quadApproxX.degree(), item.fSegX.degree());
687cb93a386Sopenharmony_ci        ScalarBezCurve segX = item.fSegX, segY = item.fSegY;
688cb93a386Sopenharmony_ci        segX.elevateDegree(deltaDegree);
689cb93a386Sopenharmony_ci        segY.elevateDegree(deltaDegree);
690cb93a386Sopenharmony_ci        quadApproxX.elevateDegree(deltaDegree);
691cb93a386Sopenharmony_ci        quadApproxY.elevateDegree(deltaDegree);
692cb93a386Sopenharmony_ci
693cb93a386Sopenharmony_ci        ScalarBezCurve deltaX = ScalarBezCurve::Sub(quadApproxX, segX);
694cb93a386Sopenharmony_ci        ScalarBezCurve deltaY = ScalarBezCurve::Sub(quadApproxY, segY);
695cb93a386Sopenharmony_ci
696cb93a386Sopenharmony_ci        // Compute psi(t) = delta_x(t)^2 + delta_y(t)^2.
697cb93a386Sopenharmony_ci        ScalarBezCurve E = ScalarBezCurve::AddSquares(deltaX, deltaY);
698cb93a386Sopenharmony_ci
699cb93a386Sopenharmony_ci        // Promote E and d(t)^2 to a common degree.
700cb93a386Sopenharmony_ci        const int commonDeg = std::max(distFncSqd.degree(), E.degree());
701cb93a386Sopenharmony_ci        distFncSqd.elevateDegree(commonDeg);
702cb93a386Sopenharmony_ci        E.elevateDegree(commonDeg);
703cb93a386Sopenharmony_ci
704cb93a386Sopenharmony_ci        // Subtract dist squared curve from E, resulting in:
705cb93a386Sopenharmony_ci        //   eps(t) = delta_x(t)^2 + delta_y(t)^2 - d(t)^2
706cb93a386Sopenharmony_ci        E.sub(distFncSqd);
707cb93a386Sopenharmony_ci
708cb93a386Sopenharmony_ci        // Purely for debugging/testing, save the first approximation and error function:
709cb93a386Sopenharmony_ci        if (viz::outerErr == nullptr) {
710cb93a386Sopenharmony_ci            using namespace viz;
711cb93a386Sopenharmony_ci            outerErr = std::make_unique<ScalarBezCurve>(E);
712cb93a386Sopenharmony_ci            outerFirstApprox.rewind();
713cb93a386Sopenharmony_ci            outerFirstApprox.moveTo(quadApprox.fPoints[0]);
714cb93a386Sopenharmony_ci            outerFirstApprox.quadTo(quadApprox.fPoints[1], quadApprox.fPoints[2]);
715cb93a386Sopenharmony_ci        }
716cb93a386Sopenharmony_ci
717cb93a386Sopenharmony_ci        // Compute maxErr, which is just the max coefficient of eps (using convex hull property
718cb93a386Sopenharmony_ci        // of bez curves)
719cb93a386Sopenharmony_ci        float maxAbsErr = std::abs(E.extremumWeight());
720cb93a386Sopenharmony_ci
721cb93a386Sopenharmony_ci        if (maxAbsErr > kTol) {
722cb93a386Sopenharmony_ci            PathSegment left, right;
723cb93a386Sopenharmony_ci            splitSegment(item.fSeg, 0.5f, &left, &right);
724cb93a386Sopenharmony_ci
725cb93a386Sopenharmony_ci            ScalarBezCurve distFncL, distFncR;
726cb93a386Sopenharmony_ci            distFnc.split(0.5f, &distFncL, &distFncR);
727cb93a386Sopenharmony_ci
728cb93a386Sopenharmony_ci            ScalarBezCurve distFncSqdL, distFncSqdR;
729cb93a386Sopenharmony_ci            distFncSqd.split(0.5f, &distFncSqdL, &distFncSqdR);
730cb93a386Sopenharmony_ci
731cb93a386Sopenharmony_ci            stack.push(Item(right, distFncR, distFncSqdR));
732cb93a386Sopenharmony_ci            stack.push(Item(left, distFncL, distFncSqdL));
733cb93a386Sopenharmony_ci        } else {
734cb93a386Sopenharmony_ci            // Approximation is good enough.
735cb93a386Sopenharmony_ci            quadApprox.fVerb = SkPath::kQuad_Verb;
736cb93a386Sopenharmony_ci            result.push_back(quadApprox);
737cb93a386Sopenharmony_ci        }
738cb93a386Sopenharmony_ci    }
739cb93a386Sopenharmony_ci    SkASSERT(!result.empty());
740cb93a386Sopenharmony_ci    return result;
741cb93a386Sopenharmony_ci}
742cb93a386Sopenharmony_ci
743cb93a386Sopenharmony_civoid SkVarWidthStroker::endcap(CapLocation loc) {
744cb93a386Sopenharmony_ci    const auto buttCap = [this](CapLocation loc) {
745cb93a386Sopenharmony_ci        if (loc == CapLocation::Start) {
746cb93a386Sopenharmony_ci            // Back at the start of the path: just close the stroked outline
747cb93a386Sopenharmony_ci            fOuter.close();
748cb93a386Sopenharmony_ci        } else {
749cb93a386Sopenharmony_ci            // Inner last pt == first pt when appending in reverse
750cb93a386Sopenharmony_ci            SkPoint innerLastPt;
751cb93a386Sopenharmony_ci            fInner.getLastPt(&innerLastPt);
752cb93a386Sopenharmony_ci            fOuter.lineTo(innerLastPt);
753cb93a386Sopenharmony_ci        }
754cb93a386Sopenharmony_ci    };
755cb93a386Sopenharmony_ci
756cb93a386Sopenharmony_ci    switch (fCap) {
757cb93a386Sopenharmony_ci        case SkPaint::kButt_Cap:
758cb93a386Sopenharmony_ci            buttCap(loc);
759cb93a386Sopenharmony_ci            break;
760cb93a386Sopenharmony_ci        default:
761cb93a386Sopenharmony_ci            SkDebugf("Unhandled endcap %d\n", fCap);
762cb93a386Sopenharmony_ci            buttCap(loc);
763cb93a386Sopenharmony_ci            break;
764cb93a386Sopenharmony_ci    }
765cb93a386Sopenharmony_ci}
766cb93a386Sopenharmony_ci
767cb93a386Sopenharmony_civoid SkVarWidthStroker::join(const SkPoint& common,
768cb93a386Sopenharmony_ci                             float innerRadius,
769cb93a386Sopenharmony_ci                             float outerRadius,
770cb93a386Sopenharmony_ci                             const OffsetSegments& prev,
771cb93a386Sopenharmony_ci                             const OffsetSegments& curr) {
772cb93a386Sopenharmony_ci    const auto miterJoin = [this](const SkPoint& common,
773cb93a386Sopenharmony_ci                                  float leftRadius,
774cb93a386Sopenharmony_ci                                  float rightRadius,
775cb93a386Sopenharmony_ci                                  const OffsetSegments& prev,
776cb93a386Sopenharmony_ci                                  const OffsetSegments& curr) {
777cb93a386Sopenharmony_ci        // With variable-width stroke you can actually have a situation where both sides
778cb93a386Sopenharmony_ci        // need an "inner" or an "outer" join. So we call the two sides "left" and
779cb93a386Sopenharmony_ci        // "right" and they can each independently get an inner or outer join.
780cb93a386Sopenharmony_ci        const auto makeJoin = [this, &common, &prev, &curr](bool left, float radius) {
781cb93a386Sopenharmony_ci            SkPath* path = left ? &fOuter : &fInner;
782cb93a386Sopenharmony_ci            const auto& prevSegs = left ? prev.fOuter : prev.fInner;
783cb93a386Sopenharmony_ci            const auto& currSegs = left ? curr.fOuter : curr.fInner;
784cb93a386Sopenharmony_ci            SkASSERT(!prevSegs.empty());
785cb93a386Sopenharmony_ci            SkASSERT(!currSegs.empty());
786cb93a386Sopenharmony_ci            const SkPoint afterEndpt = currSegs.front().fPoints[0];
787cb93a386Sopenharmony_ci            SkPoint before = unitNormal(prevSegs.back(), 1, nullptr);
788cb93a386Sopenharmony_ci            SkPoint after = unitNormal(currSegs.front(), 0, nullptr);
789cb93a386Sopenharmony_ci
790cb93a386Sopenharmony_ci            // Don't create any join geometry if the normals are nearly identical.
791cb93a386Sopenharmony_ci            const float cosTheta = before.dot(after);
792cb93a386Sopenharmony_ci            if (!SkScalarNearlyZero(1 - cosTheta)) {
793cb93a386Sopenharmony_ci                bool outerJoin;
794cb93a386Sopenharmony_ci                if (left) {
795cb93a386Sopenharmony_ci                    outerJoin = isClockwise(before, after);
796cb93a386Sopenharmony_ci                } else {
797cb93a386Sopenharmony_ci                    before = rotate180(before);
798cb93a386Sopenharmony_ci                    after = rotate180(after);
799cb93a386Sopenharmony_ci                    outerJoin = !isClockwise(before, after);
800cb93a386Sopenharmony_ci                }
801cb93a386Sopenharmony_ci
802cb93a386Sopenharmony_ci                if (outerJoin) {
803cb93a386Sopenharmony_ci                    // Before and after have the same origin and magnitude, so before+after is the
804cb93a386Sopenharmony_ci                    // diagonal of their rhombus. Origin of this vector is the midpoint of the miter
805cb93a386Sopenharmony_ci                    // line.
806cb93a386Sopenharmony_ci                    SkPoint miterVec = before + after;
807cb93a386Sopenharmony_ci
808cb93a386Sopenharmony_ci                    // Note the relationship (draw a right triangle with the miter line as its
809cb93a386Sopenharmony_ci                    // hypoteneuse):
810cb93a386Sopenharmony_ci                    //     sin(theta/2) = strokeWidth / miterLength
811cb93a386Sopenharmony_ci                    // so miterLength = strokeWidth / sin(theta/2)
812cb93a386Sopenharmony_ci                    // where miterLength is the length of the miter from outer point to inner
813cb93a386Sopenharmony_ci                    // corner. miterVec's origin is the midpoint of the miter line, so we use
814cb93a386Sopenharmony_ci                    // strokeWidth/2. Sqrt is just an application of half-angle identities.
815cb93a386Sopenharmony_ci                    const float sinHalfTheta = sqrtf(0.5 * (1 + cosTheta));
816cb93a386Sopenharmony_ci                    const float halfMiterLength = radius / sinHalfTheta;
817cb93a386Sopenharmony_ci                    // TODO: miter length limit
818cb93a386Sopenharmony_ci                    miterVec = setLength(miterVec, halfMiterLength);
819cb93a386Sopenharmony_ci
820cb93a386Sopenharmony_ci                    // Outer join: connect to the miter point, and then to t=0 of next segment.
821cb93a386Sopenharmony_ci                    path->lineTo(common + miterVec);
822cb93a386Sopenharmony_ci                    path->lineTo(afterEndpt);
823cb93a386Sopenharmony_ci                } else {
824cb93a386Sopenharmony_ci                    // Connect to the miter midpoint (common path endpoint of the two segments),
825cb93a386Sopenharmony_ci                    // and then to t=0 of the next segment. This adds an interior "loop"
826cb93a386Sopenharmony_ci                    // of geometry that handles edge cases where segment lengths are shorter than
827cb93a386Sopenharmony_ci                    // the stroke width.
828cb93a386Sopenharmony_ci                    path->lineTo(common);
829cb93a386Sopenharmony_ci                    path->lineTo(afterEndpt);
830cb93a386Sopenharmony_ci                }
831cb93a386Sopenharmony_ci            }
832cb93a386Sopenharmony_ci        };
833cb93a386Sopenharmony_ci
834cb93a386Sopenharmony_ci        makeJoin(true, leftRadius);
835cb93a386Sopenharmony_ci        makeJoin(false, rightRadius);
836cb93a386Sopenharmony_ci    };
837cb93a386Sopenharmony_ci
838cb93a386Sopenharmony_ci    switch (fJoin) {
839cb93a386Sopenharmony_ci        case SkPaint::kMiter_Join:
840cb93a386Sopenharmony_ci            miterJoin(common, innerRadius, outerRadius, prev, curr);
841cb93a386Sopenharmony_ci            break;
842cb93a386Sopenharmony_ci        default:
843cb93a386Sopenharmony_ci            SkDebugf("Unhandled join %d\n", fJoin);
844cb93a386Sopenharmony_ci            miterJoin(common, innerRadius, outerRadius, prev, curr);
845cb93a386Sopenharmony_ci            break;
846cb93a386Sopenharmony_ci    }
847cb93a386Sopenharmony_ci}
848cb93a386Sopenharmony_ci
849cb93a386Sopenharmony_civoid SkVarWidthStroker::appendPathReversed(const SkPath& path, SkPath* result) {
850cb93a386Sopenharmony_ci    const int numVerbs = path.countVerbs();
851cb93a386Sopenharmony_ci    const int numPoints = path.countPoints();
852cb93a386Sopenharmony_ci    std::vector<uint8_t> verbs;
853cb93a386Sopenharmony_ci    std::vector<SkPoint> points;
854cb93a386Sopenharmony_ci    verbs.resize(numVerbs);
855cb93a386Sopenharmony_ci    points.resize(numPoints);
856cb93a386Sopenharmony_ci    path.getVerbs(verbs.data(), numVerbs);
857cb93a386Sopenharmony_ci    path.getPoints(points.data(), numPoints);
858cb93a386Sopenharmony_ci
859cb93a386Sopenharmony_ci    for (int i = numVerbs - 1, j = numPoints; i >= 0; i--) {
860cb93a386Sopenharmony_ci        auto verb = static_cast<SkPath::Verb>(verbs[i]);
861cb93a386Sopenharmony_ci        switch (verb) {
862cb93a386Sopenharmony_ci            case SkPath::kLine_Verb: {
863cb93a386Sopenharmony_ci                j -= 1;
864cb93a386Sopenharmony_ci                SkASSERT(j >= 1);
865cb93a386Sopenharmony_ci                result->lineTo(points[j - 1]);
866cb93a386Sopenharmony_ci                break;
867cb93a386Sopenharmony_ci            }
868cb93a386Sopenharmony_ci            case SkPath::kQuad_Verb: {
869cb93a386Sopenharmony_ci                j -= 1;
870cb93a386Sopenharmony_ci                SkASSERT(j >= 2);
871cb93a386Sopenharmony_ci                result->quadTo(points[j - 1], points[j - 2]);
872cb93a386Sopenharmony_ci                j -= 1;
873cb93a386Sopenharmony_ci                break;
874cb93a386Sopenharmony_ci            }
875cb93a386Sopenharmony_ci            case SkPath::kMove_Verb:
876cb93a386Sopenharmony_ci                // Ignore
877cb93a386Sopenharmony_ci                break;
878cb93a386Sopenharmony_ci            default:
879cb93a386Sopenharmony_ci                SkASSERT(false);
880cb93a386Sopenharmony_ci                break;
881cb93a386Sopenharmony_ci        }
882cb93a386Sopenharmony_ci    }
883cb93a386Sopenharmony_ci}
884cb93a386Sopenharmony_ci
885cb93a386Sopenharmony_ciint SkVarWidthStroker::segmentDegree(const PathSegment& seg) {
886cb93a386Sopenharmony_ci    static constexpr int lut[] = {
887cb93a386Sopenharmony_ci            -1,  // move,
888cb93a386Sopenharmony_ci            1,   // line
889cb93a386Sopenharmony_ci            2,   // quad
890cb93a386Sopenharmony_ci            -1,  // conic
891cb93a386Sopenharmony_ci            3,   // cubic
892cb93a386Sopenharmony_ci            -1   // done
893cb93a386Sopenharmony_ci    };
894cb93a386Sopenharmony_ci    const int deg = lut[static_cast<uint8_t>(seg.fVerb)];
895cb93a386Sopenharmony_ci    SkASSERT(deg > 0);
896cb93a386Sopenharmony_ci    return deg;
897cb93a386Sopenharmony_ci}
898cb93a386Sopenharmony_ci
899cb93a386Sopenharmony_civoid SkVarWidthStroker::splitSegment(const PathSegment& seg,
900cb93a386Sopenharmony_ci                                     float t,
901cb93a386Sopenharmony_ci                                     PathSegment* segA,
902cb93a386Sopenharmony_ci                                     PathSegment* segB) {
903cb93a386Sopenharmony_ci    // TODO: although general, this is a pretty slow way to do this
904cb93a386Sopenharmony_ci    const int degree = segmentDegree(seg);
905cb93a386Sopenharmony_ci    ScalarBezCurve x(degree), y(degree);
906cb93a386Sopenharmony_ci    for (int i = 0; i <= degree; i++) {
907cb93a386Sopenharmony_ci        x[i] = seg.fPoints[i].fX;
908cb93a386Sopenharmony_ci        y[i] = seg.fPoints[i].fY;
909cb93a386Sopenharmony_ci    }
910cb93a386Sopenharmony_ci
911cb93a386Sopenharmony_ci    ScalarBezCurve leftX(degree), rightX(degree), leftY(degree), rightY(degree);
912cb93a386Sopenharmony_ci    x.split(t, &leftX, &rightX);
913cb93a386Sopenharmony_ci    y.split(t, &leftY, &rightY);
914cb93a386Sopenharmony_ci
915cb93a386Sopenharmony_ci    segA->fVerb = segB->fVerb = seg.fVerb;
916cb93a386Sopenharmony_ci    for (int i = 0; i <= degree; i++) {
917cb93a386Sopenharmony_ci        segA->fPoints[i] = {leftX[i], leftY[i]};
918cb93a386Sopenharmony_ci        segB->fPoints[i] = {rightX[i], rightY[i]};
919cb93a386Sopenharmony_ci    }
920cb93a386Sopenharmony_ci}
921cb93a386Sopenharmony_ci
922cb93a386Sopenharmony_civoid SkVarWidthStroker::approximateSegment(const PathSegment& seg,
923cb93a386Sopenharmony_ci                                           const ScalarBezCurve& distFnc,
924cb93a386Sopenharmony_ci                                           PathSegment* approxQuad) {
925cb93a386Sopenharmony_ci    // This is a simple control polygon transformation.
926cb93a386Sopenharmony_ci    // From F. Yzerman. "Precise offsetting of quadratic Bezier curves". 2019.
927cb93a386Sopenharmony_ci    // TODO: detect and handle more degenerate cases (e.g. linear)
928cb93a386Sopenharmony_ci    // TODO: Tiller-Hanson works better in many cases but does not generalize well
929cb93a386Sopenharmony_ci    SkPoint tangentStart, tangentEnd;
930cb93a386Sopenharmony_ci    SkPoint offsetStart = unitNormal(seg, 0, &tangentStart);
931cb93a386Sopenharmony_ci    SkPoint offsetEnd = unitNormal(seg, 1, &tangentEnd);
932cb93a386Sopenharmony_ci    SkPoint offsetMid = offsetStart + offsetEnd;
933cb93a386Sopenharmony_ci
934cb93a386Sopenharmony_ci    const float radiusStart = distFnc.eval(0);
935cb93a386Sopenharmony_ci    const float radiusMid = distFnc.eval(0.5f);
936cb93a386Sopenharmony_ci    const float radiusEnd = distFnc.eval(1);
937cb93a386Sopenharmony_ci
938cb93a386Sopenharmony_ci    offsetStart = radiusStart == 0 ? SkPoint::Make(0, 0) : setLength(offsetStart, radiusStart);
939cb93a386Sopenharmony_ci    offsetMid = radiusMid == 0 ? SkPoint::Make(0, 0) : setLength(offsetMid, radiusMid);
940cb93a386Sopenharmony_ci    offsetEnd = radiusEnd == 0 ? SkPoint::Make(0, 0) : setLength(offsetEnd, radiusEnd);
941cb93a386Sopenharmony_ci
942cb93a386Sopenharmony_ci    SkPoint start, mid, end;
943cb93a386Sopenharmony_ci    switch (segmentDegree(seg)) {
944cb93a386Sopenharmony_ci        case 1:
945cb93a386Sopenharmony_ci            start = seg.fPoints[0];
946cb93a386Sopenharmony_ci            end = seg.fPoints[1];
947cb93a386Sopenharmony_ci            mid = (start + end) * 0.5f;
948cb93a386Sopenharmony_ci            break;
949cb93a386Sopenharmony_ci        case 2:
950cb93a386Sopenharmony_ci            start = seg.fPoints[0];
951cb93a386Sopenharmony_ci            mid = seg.fPoints[1];
952cb93a386Sopenharmony_ci            end = seg.fPoints[2];
953cb93a386Sopenharmony_ci            break;
954cb93a386Sopenharmony_ci        case 3:
955cb93a386Sopenharmony_ci            start = seg.fPoints[0];
956cb93a386Sopenharmony_ci            mid = (seg.fPoints[1] + seg.fPoints[2]) * 0.5f;
957cb93a386Sopenharmony_ci            end = seg.fPoints[3];
958cb93a386Sopenharmony_ci            break;
959cb93a386Sopenharmony_ci        default:
960cb93a386Sopenharmony_ci            SkDebugf("Unhandled degree for segment approximation");
961cb93a386Sopenharmony_ci            SkASSERT(false);
962cb93a386Sopenharmony_ci            break;
963cb93a386Sopenharmony_ci    }
964cb93a386Sopenharmony_ci
965cb93a386Sopenharmony_ci    approxQuad->fPoints[0] = start + offsetStart;
966cb93a386Sopenharmony_ci    approxQuad->fPoints[1] = mid + offsetMid;
967cb93a386Sopenharmony_ci    approxQuad->fPoints[2] = end + offsetEnd;
968cb93a386Sopenharmony_ci}
969cb93a386Sopenharmony_ci
970cb93a386Sopenharmony_ciSkPoint SkVarWidthStroker::unitNormal(const PathSegment& seg, float t, SkPoint* tangentOut) {
971cb93a386Sopenharmony_ci    switch (seg.fVerb) {
972cb93a386Sopenharmony_ci        case SkPath::kLine_Verb: {
973cb93a386Sopenharmony_ci            const SkPoint tangent = setLength(seg.fPoints[1] - seg.fPoints[0], 1);
974cb93a386Sopenharmony_ci            const SkPoint normal = rotate90(tangent);
975cb93a386Sopenharmony_ci            if (tangentOut) {
976cb93a386Sopenharmony_ci                *tangentOut = tangent;
977cb93a386Sopenharmony_ci            }
978cb93a386Sopenharmony_ci            return normal;
979cb93a386Sopenharmony_ci        }
980cb93a386Sopenharmony_ci        case SkPath::kQuad_Verb: {
981cb93a386Sopenharmony_ci            SkPoint tangent;
982cb93a386Sopenharmony_ci            if (t == 0) {
983cb93a386Sopenharmony_ci                tangent = seg.fPoints[1] - seg.fPoints[0];
984cb93a386Sopenharmony_ci            } else if (t == 1) {
985cb93a386Sopenharmony_ci                tangent = seg.fPoints[2] - seg.fPoints[1];
986cb93a386Sopenharmony_ci            } else {
987cb93a386Sopenharmony_ci                tangent = ((seg.fPoints[1] - seg.fPoints[0]) * (1 - t) +
988cb93a386Sopenharmony_ci                           (seg.fPoints[2] - seg.fPoints[1]) * t) *
989cb93a386Sopenharmony_ci                          2;
990cb93a386Sopenharmony_ci            }
991cb93a386Sopenharmony_ci            if (!tangent.normalize()) {
992cb93a386Sopenharmony_ci                SkDebugf("Failed to normalize quad tangent\n");
993cb93a386Sopenharmony_ci                SkASSERT(false);
994cb93a386Sopenharmony_ci            }
995cb93a386Sopenharmony_ci            if (tangentOut) {
996cb93a386Sopenharmony_ci                *tangentOut = tangent;
997cb93a386Sopenharmony_ci            }
998cb93a386Sopenharmony_ci            return rotate90(tangent);
999cb93a386Sopenharmony_ci        }
1000cb93a386Sopenharmony_ci        case SkPath::kCubic_Verb: {
1001cb93a386Sopenharmony_ci            SkPoint tangent;
1002cb93a386Sopenharmony_ci            SkEvalCubicAt(seg.fPoints.data(), t, nullptr, &tangent, nullptr);
1003cb93a386Sopenharmony_ci            if (!tangent.normalize()) {
1004cb93a386Sopenharmony_ci                SkDebugf("Failed to normalize cubic tangent\n");
1005cb93a386Sopenharmony_ci                SkASSERT(false);
1006cb93a386Sopenharmony_ci            }
1007cb93a386Sopenharmony_ci            if (tangentOut) {
1008cb93a386Sopenharmony_ci                *tangentOut = tangent;
1009cb93a386Sopenharmony_ci            }
1010cb93a386Sopenharmony_ci            return rotate90(tangent);
1011cb93a386Sopenharmony_ci        }
1012cb93a386Sopenharmony_ci        default:
1013cb93a386Sopenharmony_ci            SkDebugf("Unhandled verb for unit normal %d\n", seg.fVerb);
1014cb93a386Sopenharmony_ci            SkASSERT(false);
1015cb93a386Sopenharmony_ci            return {};
1016cb93a386Sopenharmony_ci    }
1017cb93a386Sopenharmony_ci}
1018cb93a386Sopenharmony_ci
1019cb93a386Sopenharmony_ci}  // namespace
1020cb93a386Sopenharmony_ci
1021cb93a386Sopenharmony_ci//////////////////////////////////////////////////////////////////////////////
1022cb93a386Sopenharmony_ci
1023cb93a386Sopenharmony_ciclass VariableWidthStroker : public Sample {
1024cb93a386Sopenharmony_cipublic:
1025cb93a386Sopenharmony_ci    VariableWidthStroker()
1026cb93a386Sopenharmony_ci            : fShowHidden(true)
1027cb93a386Sopenharmony_ci            , fShowSkeleton(true)
1028cb93a386Sopenharmony_ci            , fShowStrokePoints(false)
1029cb93a386Sopenharmony_ci            , fShowUI(false)
1030cb93a386Sopenharmony_ci            , fDifferentInnerFunc(false)
1031cb93a386Sopenharmony_ci            , fShowErrorCurve(false) {
1032cb93a386Sopenharmony_ci        resetToDefaults();
1033cb93a386Sopenharmony_ci
1034cb93a386Sopenharmony_ci        fPtsPaint.setAntiAlias(true);
1035cb93a386Sopenharmony_ci        fPtsPaint.setStrokeWidth(10);
1036cb93a386Sopenharmony_ci        fPtsPaint.setStrokeCap(SkPaint::kRound_Cap);
1037cb93a386Sopenharmony_ci
1038cb93a386Sopenharmony_ci        fStrokePointsPaint.setAntiAlias(true);
1039cb93a386Sopenharmony_ci        fStrokePointsPaint.setStrokeWidth(5);
1040cb93a386Sopenharmony_ci        fStrokePointsPaint.setStrokeCap(SkPaint::kRound_Cap);
1041cb93a386Sopenharmony_ci
1042cb93a386Sopenharmony_ci        fStrokePaint.setAntiAlias(true);
1043cb93a386Sopenharmony_ci        fStrokePaint.setStyle(SkPaint::kStroke_Style);
1044cb93a386Sopenharmony_ci        fStrokePaint.setColor(0x80FF0000);
1045cb93a386Sopenharmony_ci
1046cb93a386Sopenharmony_ci        fNewFillPaint.setAntiAlias(true);
1047cb93a386Sopenharmony_ci        fNewFillPaint.setColor(0x8000FF00);
1048cb93a386Sopenharmony_ci
1049cb93a386Sopenharmony_ci        fHiddenPaint.setAntiAlias(true);
1050cb93a386Sopenharmony_ci        fHiddenPaint.setStyle(SkPaint::kStroke_Style);
1051cb93a386Sopenharmony_ci        fHiddenPaint.setColor(0xFF0000FF);
1052cb93a386Sopenharmony_ci
1053cb93a386Sopenharmony_ci        fSkeletonPaint.setAntiAlias(true);
1054cb93a386Sopenharmony_ci        fSkeletonPaint.setStyle(SkPaint::kStroke_Style);
1055cb93a386Sopenharmony_ci        fSkeletonPaint.setColor(SK_ColorRED);
1056cb93a386Sopenharmony_ci    }
1057cb93a386Sopenharmony_ci
1058cb93a386Sopenharmony_ciprivate:
1059cb93a386Sopenharmony_ci    /** Selectable menu item for choosing distance functions */
1060cb93a386Sopenharmony_ci    struct DistFncMenuItem {
1061cb93a386Sopenharmony_ci        std::string fName;
1062cb93a386Sopenharmony_ci        int fDegree;
1063cb93a386Sopenharmony_ci        bool fSelected;
1064cb93a386Sopenharmony_ci        std::vector<float> fWeights;
1065cb93a386Sopenharmony_ci
1066cb93a386Sopenharmony_ci        DistFncMenuItem(const std::string& name, int degree, bool selected) {
1067cb93a386Sopenharmony_ci            fName = name;
1068cb93a386Sopenharmony_ci            fDegree = degree;
1069cb93a386Sopenharmony_ci            fSelected = selected;
1070cb93a386Sopenharmony_ci            fWeights.resize(degree + 1, 1.0f);
1071cb93a386Sopenharmony_ci        }
1072cb93a386Sopenharmony_ci    };
1073cb93a386Sopenharmony_ci
1074cb93a386Sopenharmony_ci    SkString name() override { return SkString("VariableWidthStroker"); }
1075cb93a386Sopenharmony_ci
1076cb93a386Sopenharmony_ci    void onSizeChange() override {
1077cb93a386Sopenharmony_ci        fWinSize = SkSize::Make(this->width(), this->height());
1078cb93a386Sopenharmony_ci        INHERITED::onSizeChange();
1079cb93a386Sopenharmony_ci    }
1080cb93a386Sopenharmony_ci
1081cb93a386Sopenharmony_ci    bool onChar(SkUnichar uni) override {
1082cb93a386Sopenharmony_ci        switch (uni) {
1083cb93a386Sopenharmony_ci            case '0':
1084cb93a386Sopenharmony_ci                this->toggle(fShowUI);
1085cb93a386Sopenharmony_ci                return true;
1086cb93a386Sopenharmony_ci            case '1':
1087cb93a386Sopenharmony_ci                this->toggle(fShowSkeleton);
1088cb93a386Sopenharmony_ci                return true;
1089cb93a386Sopenharmony_ci            case '2':
1090cb93a386Sopenharmony_ci                this->toggle(fShowHidden);
1091cb93a386Sopenharmony_ci                return true;
1092cb93a386Sopenharmony_ci            case '3':
1093cb93a386Sopenharmony_ci                this->toggle(fShowStrokePoints);
1094cb93a386Sopenharmony_ci                return true;
1095cb93a386Sopenharmony_ci            case '4':
1096cb93a386Sopenharmony_ci                this->toggle(fShowErrorCurve);
1097cb93a386Sopenharmony_ci                return true;
1098cb93a386Sopenharmony_ci            case '5':
1099cb93a386Sopenharmony_ci                this->toggle(fLengthMetric);
1100cb93a386Sopenharmony_ci                return true;
1101cb93a386Sopenharmony_ci            case 'x':
1102cb93a386Sopenharmony_ci                resetToDefaults();
1103cb93a386Sopenharmony_ci                return true;
1104cb93a386Sopenharmony_ci            case '-':
1105cb93a386Sopenharmony_ci                fWidth -= 5;
1106cb93a386Sopenharmony_ci                return true;
1107cb93a386Sopenharmony_ci            case '=':
1108cb93a386Sopenharmony_ci                fWidth += 5;
1109cb93a386Sopenharmony_ci                return true;
1110cb93a386Sopenharmony_ci            default:
1111cb93a386Sopenharmony_ci                break;
1112cb93a386Sopenharmony_ci        }
1113cb93a386Sopenharmony_ci        return false;
1114cb93a386Sopenharmony_ci    }
1115cb93a386Sopenharmony_ci
1116cb93a386Sopenharmony_ci    void toggle(bool& value) { value = !value; }
1117cb93a386Sopenharmony_ci    void toggle(SkVarWidthStroker::LengthMetric& value) {
1118cb93a386Sopenharmony_ci        value = value == SkVarWidthStroker::LengthMetric::kPathLength
1119cb93a386Sopenharmony_ci                        ? SkVarWidthStroker::LengthMetric::kNumSegments
1120cb93a386Sopenharmony_ci                        : SkVarWidthStroker::LengthMetric::kPathLength;
1121cb93a386Sopenharmony_ci    }
1122cb93a386Sopenharmony_ci
1123cb93a386Sopenharmony_ci    void resetToDefaults() {
1124cb93a386Sopenharmony_ci        fPathPts[0] = {300, 400};
1125cb93a386Sopenharmony_ci        fPathPts[1] = {500, 400};
1126cb93a386Sopenharmony_ci        fPathPts[2] = {700, 400};
1127cb93a386Sopenharmony_ci        fPathPts[3] = {900, 400};
1128cb93a386Sopenharmony_ci        fPathPts[4] = {1100, 400};
1129cb93a386Sopenharmony_ci
1130cb93a386Sopenharmony_ci        fWidth = 175;
1131cb93a386Sopenharmony_ci
1132cb93a386Sopenharmony_ci        fLengthMetric = SkVarWidthStroker::LengthMetric::kPathLength;
1133cb93a386Sopenharmony_ci        fDistFncs = fDefaultsDistFncs;
1134cb93a386Sopenharmony_ci        fDistFncsInner = fDefaultsDistFncs;
1135cb93a386Sopenharmony_ci    }
1136cb93a386Sopenharmony_ci
1137cb93a386Sopenharmony_ci    void makePath(SkPath* path) {
1138cb93a386Sopenharmony_ci        path->moveTo(fPathPts[0]);
1139cb93a386Sopenharmony_ci        path->quadTo(fPathPts[1], fPathPts[2]);
1140cb93a386Sopenharmony_ci        path->quadTo(fPathPts[3], fPathPts[4]);
1141cb93a386Sopenharmony_ci    }
1142cb93a386Sopenharmony_ci
1143cb93a386Sopenharmony_ci    static ScalarBezCurve makeDistFnc(const std::vector<DistFncMenuItem>& fncs, float strokeWidth) {
1144cb93a386Sopenharmony_ci        const float radius = strokeWidth / 2;
1145cb93a386Sopenharmony_ci        for (const auto& df : fncs) {
1146cb93a386Sopenharmony_ci            if (df.fSelected) {
1147cb93a386Sopenharmony_ci                return ScalarBezCurve::Mul(ScalarBezCurve(df.fDegree, df.fWeights), radius);
1148cb93a386Sopenharmony_ci            }
1149cb93a386Sopenharmony_ci        }
1150cb93a386Sopenharmony_ci        SkASSERT(false);
1151cb93a386Sopenharmony_ci        return ScalarBezCurve(0, {radius});
1152cb93a386Sopenharmony_ci    }
1153cb93a386Sopenharmony_ci
1154cb93a386Sopenharmony_ci    void onDrawContent(SkCanvas* canvas) override {
1155cb93a386Sopenharmony_ci        canvas->drawColor(0xFFEEEEEE);
1156cb93a386Sopenharmony_ci
1157cb93a386Sopenharmony_ci        SkPath path;
1158cb93a386Sopenharmony_ci        this->makePath(&path);
1159cb93a386Sopenharmony_ci
1160cb93a386Sopenharmony_ci        fStrokePaint.setStrokeWidth(fWidth);
1161cb93a386Sopenharmony_ci
1162cb93a386Sopenharmony_ci        // Elber-Cohen stroker result
1163cb93a386Sopenharmony_ci        ScalarBezCurve distFnc = makeDistFnc(fDistFncs, fWidth);
1164cb93a386Sopenharmony_ci        ScalarBezCurve distFncInner =
1165cb93a386Sopenharmony_ci                fDifferentInnerFunc ? makeDistFnc(fDistFncsInner, fWidth) : distFnc;
1166cb93a386Sopenharmony_ci        SkVarWidthStroker stroker;
1167cb93a386Sopenharmony_ci        SkPath fillPath =
1168cb93a386Sopenharmony_ci                stroker.getFillPath(path, fStrokePaint, distFnc, distFncInner, fLengthMetric);
1169cb93a386Sopenharmony_ci        fillPath.setFillType(SkPathFillType::kWinding);
1170cb93a386Sopenharmony_ci        canvas->drawPath(fillPath, fNewFillPaint);
1171cb93a386Sopenharmony_ci
1172cb93a386Sopenharmony_ci        if (fShowHidden) {
1173cb93a386Sopenharmony_ci            canvas->drawPath(fillPath, fHiddenPaint);
1174cb93a386Sopenharmony_ci        }
1175cb93a386Sopenharmony_ci
1176cb93a386Sopenharmony_ci        if (fShowSkeleton) {
1177cb93a386Sopenharmony_ci            canvas->drawPath(path, fSkeletonPaint);
1178cb93a386Sopenharmony_ci            canvas->drawPoints(SkCanvas::kPoints_PointMode, fPathPts.size(), fPathPts.data(),
1179cb93a386Sopenharmony_ci                               fPtsPaint);
1180cb93a386Sopenharmony_ci        }
1181cb93a386Sopenharmony_ci
1182cb93a386Sopenharmony_ci        if (fShowStrokePoints) {
1183cb93a386Sopenharmony_ci            drawStrokePoints(canvas, fillPath);
1184cb93a386Sopenharmony_ci        }
1185cb93a386Sopenharmony_ci
1186cb93a386Sopenharmony_ci        if (fShowUI) {
1187cb93a386Sopenharmony_ci            drawUI();
1188cb93a386Sopenharmony_ci        }
1189cb93a386Sopenharmony_ci
1190cb93a386Sopenharmony_ci        if (fShowErrorCurve && viz::outerErr != nullptr) {
1191cb93a386Sopenharmony_ci            SkPaint firstApproxPaint;
1192cb93a386Sopenharmony_ci            firstApproxPaint.setStrokeWidth(4);
1193cb93a386Sopenharmony_ci            firstApproxPaint.setStyle(SkPaint::kStroke_Style);
1194cb93a386Sopenharmony_ci            firstApproxPaint.setColor(SK_ColorRED);
1195cb93a386Sopenharmony_ci            canvas->drawPath(viz::outerFirstApprox, firstApproxPaint);
1196cb93a386Sopenharmony_ci            drawErrorCurve(canvas, *viz::outerErr);
1197cb93a386Sopenharmony_ci        }
1198cb93a386Sopenharmony_ci    }
1199cb93a386Sopenharmony_ci
1200cb93a386Sopenharmony_ci    Sample::Click* onFindClickHandler(SkScalar x, SkScalar y, skui::ModifierKey modi) override {
1201cb93a386Sopenharmony_ci        const SkScalar tol = 4;
1202cb93a386Sopenharmony_ci        const SkRect r = SkRect::MakeXYWH(x - tol, y - tol, tol * 2, tol * 2);
1203cb93a386Sopenharmony_ci        for (size_t i = 0; i < fPathPts.size(); ++i) {
1204cb93a386Sopenharmony_ci            if (r.intersects(SkRect::MakeXYWH(fPathPts[i].fX, fPathPts[i].fY, 1, 1))) {
1205cb93a386Sopenharmony_ci                return new Click([this, i](Click* c) {
1206cb93a386Sopenharmony_ci                    fPathPts[i] = c->fCurr;
1207cb93a386Sopenharmony_ci                    return true;
1208cb93a386Sopenharmony_ci                });
1209cb93a386Sopenharmony_ci            }
1210cb93a386Sopenharmony_ci        }
1211cb93a386Sopenharmony_ci        return nullptr;
1212cb93a386Sopenharmony_ci    }
1213cb93a386Sopenharmony_ci
1214cb93a386Sopenharmony_ci    void drawStrokePoints(SkCanvas* canvas, const SkPath& fillPath) {
1215cb93a386Sopenharmony_ci        SkPath::Iter it(fillPath, false);
1216cb93a386Sopenharmony_ci        SkPoint points[4];
1217cb93a386Sopenharmony_ci        SkPath::Verb verb;
1218cb93a386Sopenharmony_ci        std::vector<SkPoint> pointsVec, ctrlPts;
1219cb93a386Sopenharmony_ci        while ((verb = it.next(&points[0])) != SkPath::kDone_Verb) {
1220cb93a386Sopenharmony_ci            switch (verb) {
1221cb93a386Sopenharmony_ci                case SkPath::kLine_Verb:
1222cb93a386Sopenharmony_ci                    pointsVec.push_back(points[1]);
1223cb93a386Sopenharmony_ci                    break;
1224cb93a386Sopenharmony_ci                case SkPath::kQuad_Verb:
1225cb93a386Sopenharmony_ci                    ctrlPts.push_back(points[1]);
1226cb93a386Sopenharmony_ci                    pointsVec.push_back(points[2]);
1227cb93a386Sopenharmony_ci                    break;
1228cb93a386Sopenharmony_ci                case SkPath::kMove_Verb:
1229cb93a386Sopenharmony_ci                    pointsVec.push_back(points[0]);
1230cb93a386Sopenharmony_ci                    break;
1231cb93a386Sopenharmony_ci                case SkPath::kClose_Verb:
1232cb93a386Sopenharmony_ci                    break;
1233cb93a386Sopenharmony_ci                default:
1234cb93a386Sopenharmony_ci                    SkDebugf("Unhandled path verb %d for stroke points\n", verb);
1235cb93a386Sopenharmony_ci                    SkASSERT(false);
1236cb93a386Sopenharmony_ci                    break;
1237cb93a386Sopenharmony_ci            }
1238cb93a386Sopenharmony_ci        }
1239cb93a386Sopenharmony_ci
1240cb93a386Sopenharmony_ci        canvas->drawPoints(SkCanvas::kPoints_PointMode, pointsVec.size(), pointsVec.data(),
1241cb93a386Sopenharmony_ci                           fStrokePointsPaint);
1242cb93a386Sopenharmony_ci        fStrokePointsPaint.setColor(SK_ColorBLUE);
1243cb93a386Sopenharmony_ci        fStrokePointsPaint.setStrokeWidth(3);
1244cb93a386Sopenharmony_ci        canvas->drawPoints(SkCanvas::kPoints_PointMode, ctrlPts.size(), ctrlPts.data(),
1245cb93a386Sopenharmony_ci                           fStrokePointsPaint);
1246cb93a386Sopenharmony_ci        fStrokePointsPaint.setColor(SK_ColorBLACK);
1247cb93a386Sopenharmony_ci        fStrokePointsPaint.setStrokeWidth(5);
1248cb93a386Sopenharmony_ci    }
1249cb93a386Sopenharmony_ci
1250cb93a386Sopenharmony_ci    void drawErrorCurve(SkCanvas* canvas, const ScalarBezCurve& E) {
1251cb93a386Sopenharmony_ci        const float winW = fWinSize.width() * 0.75f, winH = fWinSize.height() * 0.25f;
1252cb93a386Sopenharmony_ci        const float padding = 25;
1253cb93a386Sopenharmony_ci        const SkRect box = SkRect::MakeXYWH(padding, fWinSize.height() - winH - padding,
1254cb93a386Sopenharmony_ci                                            winW - 2 * padding, winH);
1255cb93a386Sopenharmony_ci        constexpr int nsegs = 100;
1256cb93a386Sopenharmony_ci        constexpr float dt = 1.0f / nsegs;
1257cb93a386Sopenharmony_ci        constexpr float dx = 10.0f;
1258cb93a386Sopenharmony_ci        const int deg = E.degree();
1259cb93a386Sopenharmony_ci        SkPath path;
1260cb93a386Sopenharmony_ci        for (int i = 0; i < nsegs; i++) {
1261cb93a386Sopenharmony_ci            const float tmin = i * dt, tmax = (i + 1) * dt;
1262cb93a386Sopenharmony_ci            ScalarBezCurve left(deg), right(deg);
1263cb93a386Sopenharmony_ci            E.split(tmax, &left, &right);
1264cb93a386Sopenharmony_ci            const float tRel = tmin / tmax;
1265cb93a386Sopenharmony_ci            ScalarBezCurve rl(deg), rr(deg);
1266cb93a386Sopenharmony_ci            left.split(tRel, &rl, &rr);
1267cb93a386Sopenharmony_ci
1268cb93a386Sopenharmony_ci            const float x = i * dx;
1269cb93a386Sopenharmony_ci            if (i == 0) {
1270cb93a386Sopenharmony_ci                path.moveTo(x, -rr[0]);
1271cb93a386Sopenharmony_ci            }
1272cb93a386Sopenharmony_ci            path.lineTo(x + dx, -rr[deg]);
1273cb93a386Sopenharmony_ci        }
1274cb93a386Sopenharmony_ci
1275cb93a386Sopenharmony_ci        SkPaint paint;
1276cb93a386Sopenharmony_ci        paint.setStyle(SkPaint::kStroke_Style);
1277cb93a386Sopenharmony_ci        paint.setAntiAlias(true);
1278cb93a386Sopenharmony_ci        paint.setStrokeWidth(0);
1279cb93a386Sopenharmony_ci        paint.setColor(SK_ColorRED);
1280cb93a386Sopenharmony_ci        const SkRect pathBounds = path.computeTightBounds();
1281cb93a386Sopenharmony_ci        constexpr float yAxisMax = 8000;
1282cb93a386Sopenharmony_ci        const float sx = box.width() / pathBounds.width();
1283cb93a386Sopenharmony_ci        const float sy = box.height() / (2 * yAxisMax);
1284cb93a386Sopenharmony_ci        canvas->save();
1285cb93a386Sopenharmony_ci        canvas->translate(box.left(), box.top() + box.height() / 2);
1286cb93a386Sopenharmony_ci        canvas->scale(sx, sy);
1287cb93a386Sopenharmony_ci        canvas->drawPath(path, paint);
1288cb93a386Sopenharmony_ci
1289cb93a386Sopenharmony_ci        SkPath axes;
1290cb93a386Sopenharmony_ci        axes.moveTo(0, 0);
1291cb93a386Sopenharmony_ci        axes.lineTo(pathBounds.width(), 0);
1292cb93a386Sopenharmony_ci        axes.moveTo(0, -yAxisMax);
1293cb93a386Sopenharmony_ci        axes.lineTo(0, yAxisMax);
1294cb93a386Sopenharmony_ci        paint.setColor(SK_ColorBLACK);
1295cb93a386Sopenharmony_ci        paint.setAntiAlias(false);
1296cb93a386Sopenharmony_ci        canvas->drawPath(axes, paint);
1297cb93a386Sopenharmony_ci
1298cb93a386Sopenharmony_ci        canvas->restore();
1299cb93a386Sopenharmony_ci    }
1300cb93a386Sopenharmony_ci
1301cb93a386Sopenharmony_ci    void drawUI() {
1302cb93a386Sopenharmony_ci        static constexpr auto kUIOpacity = 0.35f;
1303cb93a386Sopenharmony_ci        static constexpr float kUIWidth = 200.0f, kUIHeight = 400.0f;
1304cb93a386Sopenharmony_ci        ImGui::SetNextWindowBgAlpha(kUIOpacity);
1305cb93a386Sopenharmony_ci        if (ImGui::Begin("E-C Controls", nullptr,
1306cb93a386Sopenharmony_ci                         ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoResize |
1307cb93a386Sopenharmony_ci                                 ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoSavedSettings |
1308cb93a386Sopenharmony_ci                                 ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav)) {
1309cb93a386Sopenharmony_ci            const SkRect uiArea = SkRect::MakeXYWH(10, 10, kUIWidth, kUIHeight);
1310cb93a386Sopenharmony_ci            ImGui::SetWindowPos(ImVec2(uiArea.x(), uiArea.y()));
1311cb93a386Sopenharmony_ci            ImGui::SetWindowSize(ImVec2(uiArea.width(), uiArea.height()));
1312cb93a386Sopenharmony_ci
1313cb93a386Sopenharmony_ci            const auto drawControls = [](std::vector<DistFncMenuItem>& distFncs,
1314cb93a386Sopenharmony_ci                                         const std::string& menuPfx,
1315cb93a386Sopenharmony_ci                                         const std::string& ptPfx) {
1316cb93a386Sopenharmony_ci                std::string degreeMenuLabel = menuPfx + ": ";
1317cb93a386Sopenharmony_ci                for (const auto& df : distFncs) {
1318cb93a386Sopenharmony_ci                    if (df.fSelected) {
1319cb93a386Sopenharmony_ci                        degreeMenuLabel += df.fName;
1320cb93a386Sopenharmony_ci                        break;
1321cb93a386Sopenharmony_ci                    }
1322cb93a386Sopenharmony_ci                }
1323cb93a386Sopenharmony_ci                if (ImGui::BeginMenu(degreeMenuLabel.c_str())) {
1324cb93a386Sopenharmony_ci                    for (size_t i = 0; i < distFncs.size(); i++) {
1325cb93a386Sopenharmony_ci                        if (ImGui::MenuItem(distFncs[i].fName.c_str(), nullptr,
1326cb93a386Sopenharmony_ci                                            distFncs[i].fSelected)) {
1327cb93a386Sopenharmony_ci                            for (size_t j = 0; j < distFncs.size(); j++) {
1328cb93a386Sopenharmony_ci                                distFncs[j].fSelected = j == i;
1329cb93a386Sopenharmony_ci                            }
1330cb93a386Sopenharmony_ci                        }
1331cb93a386Sopenharmony_ci                    }
1332cb93a386Sopenharmony_ci                    ImGui::EndMenu();
1333cb93a386Sopenharmony_ci                }
1334cb93a386Sopenharmony_ci
1335cb93a386Sopenharmony_ci                for (auto& df : distFncs) {
1336cb93a386Sopenharmony_ci                    if (df.fSelected) {
1337cb93a386Sopenharmony_ci                        for (int i = 0; i <= df.fDegree; i++) {
1338cb93a386Sopenharmony_ci                            const std::string label = ptPfx + std::to_string(i);
1339cb93a386Sopenharmony_ci                            ImGui::SliderFloat(label.c_str(), &(df.fWeights[i]), 0, 1);
1340cb93a386Sopenharmony_ci                        }
1341cb93a386Sopenharmony_ci                    }
1342cb93a386Sopenharmony_ci                }
1343cb93a386Sopenharmony_ci            };
1344cb93a386Sopenharmony_ci
1345cb93a386Sopenharmony_ci            const std::array<std::pair<std::string, SkVarWidthStroker::LengthMetric>, 2> metrics = {
1346cb93a386Sopenharmony_ci                    std::make_pair("% path length", SkVarWidthStroker::LengthMetric::kPathLength),
1347cb93a386Sopenharmony_ci                    std::make_pair("% segment count",
1348cb93a386Sopenharmony_ci                                   SkVarWidthStroker::LengthMetric::kNumSegments),
1349cb93a386Sopenharmony_ci            };
1350cb93a386Sopenharmony_ci            if (ImGui::BeginMenu("Interpolation metric:")) {
1351cb93a386Sopenharmony_ci                for (const auto& metric : metrics) {
1352cb93a386Sopenharmony_ci                    if (ImGui::MenuItem(metric.first.c_str(), nullptr,
1353cb93a386Sopenharmony_ci                                        fLengthMetric == metric.second)) {
1354cb93a386Sopenharmony_ci                        fLengthMetric = metric.second;
1355cb93a386Sopenharmony_ci                    }
1356cb93a386Sopenharmony_ci                }
1357cb93a386Sopenharmony_ci                ImGui::EndMenu();
1358cb93a386Sopenharmony_ci            }
1359cb93a386Sopenharmony_ci
1360cb93a386Sopenharmony_ci            drawControls(fDistFncs, "Degree", "P");
1361cb93a386Sopenharmony_ci
1362cb93a386Sopenharmony_ci            if (ImGui::CollapsingHeader("Inner stroke", true)) {
1363cb93a386Sopenharmony_ci                fDifferentInnerFunc = true;
1364cb93a386Sopenharmony_ci                drawControls(fDistFncsInner, "Degree (inner)", "Q");
1365cb93a386Sopenharmony_ci            } else {
1366cb93a386Sopenharmony_ci                fDifferentInnerFunc = false;
1367cb93a386Sopenharmony_ci            }
1368cb93a386Sopenharmony_ci        }
1369cb93a386Sopenharmony_ci        ImGui::End();
1370cb93a386Sopenharmony_ci    }
1371cb93a386Sopenharmony_ci
1372cb93a386Sopenharmony_ci    bool fShowHidden, fShowSkeleton, fShowStrokePoints, fShowUI, fDifferentInnerFunc,
1373cb93a386Sopenharmony_ci            fShowErrorCurve;
1374cb93a386Sopenharmony_ci    float fWidth = 175;
1375cb93a386Sopenharmony_ci    SkPaint fPtsPaint, fStrokePaint, fNewFillPaint, fHiddenPaint, fSkeletonPaint,
1376cb93a386Sopenharmony_ci            fStrokePointsPaint;
1377cb93a386Sopenharmony_ci    inline static constexpr int kNPts = 5;
1378cb93a386Sopenharmony_ci    std::array<SkPoint, kNPts> fPathPts;
1379cb93a386Sopenharmony_ci    SkSize fWinSize;
1380cb93a386Sopenharmony_ci    SkVarWidthStroker::LengthMetric fLengthMetric;
1381cb93a386Sopenharmony_ci    const std::vector<DistFncMenuItem> fDefaultsDistFncs = {
1382cb93a386Sopenharmony_ci            DistFncMenuItem("Linear", 1, true), DistFncMenuItem("Quadratic", 2, false),
1383cb93a386Sopenharmony_ci            DistFncMenuItem("Cubic", 3, false), DistFncMenuItem("One Louder (11)", 11, false),
1384cb93a386Sopenharmony_ci            DistFncMenuItem("30?!", 30, false)};
1385cb93a386Sopenharmony_ci    std::vector<DistFncMenuItem> fDistFncs = fDefaultsDistFncs;
1386cb93a386Sopenharmony_ci    std::vector<DistFncMenuItem> fDistFncsInner = fDefaultsDistFncs;
1387cb93a386Sopenharmony_ci
1388cb93a386Sopenharmony_ci    using INHERITED = Sample;
1389cb93a386Sopenharmony_ci};
1390cb93a386Sopenharmony_ci
1391cb93a386Sopenharmony_ciDEF_SAMPLE(return new VariableWidthStroker;)
1392