1b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez/*
2b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez * Copyright (C) 2015 The Android Open Source Project
3b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez *
4b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez * Licensed under the Apache License, Version 2.0 (the "License");
5b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez * you may not use this file except in compliance with the License.
6b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez * You may obtain a copy of the License at
7b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez *
8b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez *      http://www.apache.org/licenses/LICENSE-2.0
9b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez *
10b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez * Unless required by applicable law or agreed to in writing, software
11b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez * distributed under the License is distributed on an "AS IS" BASIS,
12b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez * See the License for the specific language governing permissions and
14b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez * limitations under the License.
15b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez */
16b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez
17b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perezpackage com.android.layoutlib.bridge.util;
18b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez
19b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perezimport android.annotation.NonNull;
20b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez
21b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perezimport java.awt.geom.CubicCurve2D;
22b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perezimport java.awt.geom.PathIterator;
23b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perezimport java.awt.geom.Point2D;
24b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perezimport java.awt.geom.QuadCurve2D;
25b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perezimport java.util.ArrayList;
26b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez
27b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perezimport com.google.android.collect.Lists;
28b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez
29b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez/**
30b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez * Class that returns iterators for a given path. These iterators are lightweight and can be reused
31b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez * multiple times to iterate over the path.
32b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez */
33b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perezpublic class CachedPathIteratorFactory {
34b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez    /*
35b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez     * A few conventions used in the code:
36b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez     * Coordinates or coords arrays store segment coordinates. They use the same format as
37b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez     * PathIterator#currentSegment coordinates array.
38b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez     * float arrays store always points where the first element is X and the second is Y.
39b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez     */
40b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez
41b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez    // This governs how accurate the approximation of the Path is.
42b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez    private static final float PRECISION = 0.002f;
43b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez
44b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez    private final int mWindingRule;
45b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez    private final int[] mTypes;
46b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez    private final float[][] mCoordinates;
47b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez    private final float[] mSegmentsLength;
48b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez    private final float mTotalLength;
49b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez
50b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez    public CachedPathIteratorFactory(@NonNull PathIterator iterator) {
51b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        mWindingRule = iterator.getWindingRule();
52b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez
53b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        ArrayList<Integer> typesArray = Lists.newArrayList();
54b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        ArrayList<float[]> pointsArray = Lists.newArrayList();
55b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        float[] points = new float[6];
56b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        while (!iterator.isDone()) {
57b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez            int type = iterator.currentSegment(points);
58b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez            int nPoints = getNumberOfPoints(type) * 2; // 2 coordinates per point
59b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez
60b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez            typesArray.add(type);
61b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez            float[] itemPoints = new float[nPoints];
62b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez            System.arraycopy(points, 0, itemPoints, 0, nPoints);
63b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez            pointsArray.add(itemPoints);
64b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez            iterator.next();
65b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        }
66b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez
67b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        mTypes = new int[typesArray.size()];
68b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        mCoordinates = new float[mTypes.length][];
69b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        for (int i = 0; i < typesArray.size(); i++) {
70b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez            mTypes[i] = typesArray.get(i);
71b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez            mCoordinates[i] = pointsArray.get(i);
72b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        }
73b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez
74b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        // Do measurement
75b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        mSegmentsLength = new float[mTypes.length];
76b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez
77b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        // Curves that we can reuse to estimate segments length
78b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        CubicCurve2D.Float cubicCurve = new CubicCurve2D.Float();
79b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        QuadCurve2D.Float quadCurve = new QuadCurve2D.Float();
80b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        float lastX = 0;
81b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        float lastY = 0;
82b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        float totalLength = 0;
83b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        for (int i = 0; i < mTypes.length; i++) {
84b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez            switch (mTypes[i]) {
85b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                case PathIterator.SEG_CUBICTO:
86b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                    cubicCurve.setCurve(lastX, lastY,
87b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                            mCoordinates[i][0], mCoordinates[i][1], mCoordinates[i][2],
88b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                            mCoordinates[i][3], lastX = mCoordinates[i][4],
89b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                            lastY = mCoordinates[i][5]);
90b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                    mSegmentsLength[i] =
91b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                            getFlatPathLength(cubicCurve.getPathIterator(null, PRECISION));
92b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                    break;
93b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                case PathIterator.SEG_QUADTO:
94b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                    quadCurve.setCurve(lastX, lastY, mCoordinates[i][0], mCoordinates[i][1],
95b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                            lastX = mCoordinates[i][2], lastY = mCoordinates[i][3]);
96b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                    mSegmentsLength[i] =
97b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                            getFlatPathLength(quadCurve.getPathIterator(null, PRECISION));
98b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                    break;
99b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                case PathIterator.SEG_CLOSE:
100b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                    mSegmentsLength[i] = (float) Point2D.distance(lastX, lastY,
101b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                            lastX = mCoordinates[0][0],
102b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                            lastY = mCoordinates[0][1]);
103b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                    mCoordinates[i] = new float[2];
104b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                    // We convert a SEG_CLOSE segment to a SEG_LINETO so we do not have to worry
105b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                    // about this special case in the rest of the code.
106b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                    mTypes[i] = PathIterator.SEG_LINETO;
107b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                    mCoordinates[i][0] = mCoordinates[0][0];
108b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                    mCoordinates[i][1] = mCoordinates[0][1];
109b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                    break;
110b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                case PathIterator.SEG_MOVETO:
111b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                    mSegmentsLength[i] = 0;
112b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                    lastX = mCoordinates[i][0];
113b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                    lastY = mCoordinates[i][1];
114b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                    break;
115b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                case PathIterator.SEG_LINETO:
116b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                    mSegmentsLength[i] = (float) Point2D.distance(lastX, lastY, mCoordinates[i][0],
117b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                            mCoordinates[i][1]);
118b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                    lastX = mCoordinates[i][0];
119b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                    lastY = mCoordinates[i][1];
120b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                default:
121b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez            }
122b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez            totalLength += mSegmentsLength[i];
123b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        }
124b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez
125b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        mTotalLength = totalLength;
126b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez    }
127b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez
128b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez    private static void quadCurveSegment(float[] coords, float t0, float t1) {
129b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        // Calculate X and Y at 0.5 (We'll use this to reconstruct the control point later)
130b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        float mt = t0 + (t1 - t0) / 2;
131b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        float mu = 1 - mt;
132b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        float mx = mu * mu * coords[0] + 2 * mu * mt * coords[2] + mt * mt * coords[4];
133b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        float my = mu * mu * coords[1] + 2 * mu * mt * coords[3] + mt * mt * coords[5];
134b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez
135b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        float u0 = 1 - t0;
136b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        float u1 = 1 - t1;
137b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez
138b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        // coords at t0
139b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        coords[0] = coords[0] * u0 * u0 + coords[2] * 2 * t0 * u0 + coords[4] * t0 * t0;
140b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        coords[1] = coords[1] * u0 * u0 + coords[3] * 2 * t0 * u0 + coords[5] * t0 * t0;
141b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez
142b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        // coords at t1
143b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        coords[4] = coords[0] * u1 * u1 + coords[2] * 2 * t1 * u1 + coords[4] * t1 * t1;
144b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        coords[5] = coords[1] * u1 * u1 + coords[3] * 2 * t1 * u1 + coords[5] * t1 * t1;
145b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez
146b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        // estimated control point at t'=0.5
147b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        coords[2] = 2 * mx - coords[0] / 2 - coords[4] / 2;
148b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        coords[3] = 2 * my - coords[1] / 2 - coords[5] / 2;
149b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez    }
150b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez
151b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez    private static void cubicCurveSegment(float[] coords, float t0, float t1) {
152b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        // http://stackoverflow.com/questions/11703283/cubic-bezier-curve-segment
153b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        float u0 = 1 - t0;
154b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        float u1 = 1 - t1;
155b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez
156b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        // Calculate the points at t0 and t1 for the quadratic curves formed by (P0, P1, P2) and
157b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        // (P1, P2, P3)
158b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        float qxa = coords[0] * u0 * u0 + coords[2] * 2 * t0 * u0 + coords[4] * t0 * t0;
159b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        float qxb = coords[0] * u1 * u1 + coords[2] * 2 * t1 * u1 + coords[4] * t1 * t1;
160b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        float qxc = coords[2] * u0 * u0 + coords[4] * 2 * t0 * u0 + coords[6] * t0 * t0;
161b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        float qxd = coords[2] * u1 * u1 + coords[4] * 2 * t1 * u1 + coords[6] * t1 * t1;
162b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez
163b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        float qya = coords[1] * u0 * u0 + coords[3] * 2 * t0 * u0 + coords[5] * t0 * t0;
164b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        float qyb = coords[1] * u1 * u1 + coords[3] * 2 * t1 * u1 + coords[5] * t1 * t1;
165b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        float qyc = coords[3] * u0 * u0 + coords[5] * 2 * t0 * u0 + coords[7] * t0 * t0;
166b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        float qyd = coords[3] * u1 * u1 + coords[5] * 2 * t1 * u1 + coords[7] * t1 * t1;
167b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez
168b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        // Linear interpolation
169b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        coords[0] = qxa * u0 + qxc * t0;
170b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        coords[1] = qya * u0 + qyc * t0;
171b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez
172b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        coords[2] = qxa * u1 + qxc * t1;
173b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        coords[3] = qya * u1 + qyc * t1;
174b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez
175b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        coords[4] = qxb * u0 + qxd * t0;
176b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        coords[5] = qyb * u0 + qyd * t0;
177b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez
178b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        coords[6] = qxb * u1 + qxd * t1;
179b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        coords[7] = qyb * u1 + qyd * t1;
180b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez    }
181b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez
182b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez    /**
183b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez     * Returns the end point of a given segment
184b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez     *
185b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez     * @param type the segment type
186b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez     * @param coords the segment coordinates array
187b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez     * @param point the return array where the point will be stored
188b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez     */
189b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez    private static void getShapeEndPoint(int type, @NonNull float[] coords, @NonNull float[]
190b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez            point) {
191b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        // start index of the end point for the segment type
192b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        int pointIndex = (getNumberOfPoints(type) - 1) * 2;
193b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        point[0] = coords[pointIndex];
194b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        point[1] = coords[pointIndex + 1];
195b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez    }
196b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez
197b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez    /**
198b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez     * Returns the number of points stored in a coordinates array for the given segment type.
199b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez     */
200b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez    private static int getNumberOfPoints(int segmentType) {
201b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        switch (segmentType) {
202b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez            case PathIterator.SEG_QUADTO:
203b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                return 2;
204b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez            case PathIterator.SEG_CUBICTO:
205b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                return 3;
206b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez            case PathIterator.SEG_CLOSE:
207b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                return 0;
208b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez            default:
209b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                return 1;
210b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        }
211b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez    }
212b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez
213b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez    /**
214b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez     * Returns the estimated length of a flat path. If the passed path is not flat (i.e. contains a
215b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez     * segment that is not {@link PathIterator#SEG_CLOSE}, {@link PathIterator#SEG_MOVETO} or {@link
216b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez     * PathIterator#SEG_LINETO} this method will fail.
217b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez     */
218b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez    private static float getFlatPathLength(@NonNull PathIterator iterator) {
219b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        float segment[] = new float[6];
220b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        float totalLength = 0;
221b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        float[] previousPoint = new float[2];
222b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        boolean isFirstPoint = true;
223b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez
224b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        while (!iterator.isDone()) {
225b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez            int type = iterator.currentSegment(segment);
226b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez            assert type == PathIterator.SEG_LINETO || type == PathIterator.SEG_CLOSE || type ==
227b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                    PathIterator.SEG_MOVETO;
228b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez
229b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez            // MoveTo shouldn't affect the length
230b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez            if (!isFirstPoint && type != PathIterator.SEG_MOVETO) {
231b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                totalLength += Point2D.distance(previousPoint[0], previousPoint[1], segment[0],
232b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                        segment[1]);
233b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez            } else {
234b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                isFirstPoint = false;
235b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez            }
236b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez            previousPoint[0] = segment[0];
237b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez            previousPoint[1] = segment[1];
238b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez            iterator.next();
239b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        }
240b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez
241b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        return totalLength;
242b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez    }
243b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez
244b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez    /**
245b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez     * Returns the estimated position along a path of the given length.
246b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez     */
247b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez    private void getPointAtLength(int type, @NonNull float[] coords, float lastX, float
248b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez            lastY, float t, @NonNull float[] point) {
249b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        if (type == PathIterator.SEG_LINETO) {
250b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez            point[0] = lastX + (coords[0] - lastX) * t;
251b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez            point[1] = lastY + (coords[1] - lastY) * t;
252b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez            // Return here, since we do not need a shape to estimate
253b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez            return;
254b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        }
255b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez
256b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        float[] curve = new float[8];
257b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        int lastPointIndex = (getNumberOfPoints(type) - 1) * 2;
258b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez
259b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        System.arraycopy(coords, 0, curve, 2, coords.length);
260b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        curve[0] = lastX;
261b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        curve[1] = lastY;
262b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        if (type == PathIterator.SEG_CUBICTO) {
263b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez            cubicCurveSegment(curve, 0f, t);
264b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        } else {
265b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez            quadCurveSegment(curve, 0f, t);
266b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        }
267b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez
268b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        point[0] = curve[2 + lastPointIndex];
269b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        point[1] = curve[2 + lastPointIndex + 1];
270b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez    }
271b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez
272b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez    public CachedPathIterator iterator() {
273b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        return new CachedPathIterator();
274b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez    }
275b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez
276b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez    /**
277b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez     * Class that allows us to iterate over a path multiple times
278b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez     */
279b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez    public class CachedPathIterator implements PathIterator {
280b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        private int mNextIndex;
281b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez
282b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        /**
283b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez         * Current segment type.
284b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez         *
285b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez         * @see PathIterator
286b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez         */
287b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        private int mCurrentType;
288b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez
289b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        /**
290b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez         * Stores the coordinates array of the current segment. The number of points stored depends
291b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez         * on the segment type.
292b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez         *
293b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez         * @see PathIterator
294b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez         */
295b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        private float[] mCurrentCoords = new float[6];
296b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        private float mCurrentSegmentLength;
297b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez
298b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        /**
299b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez         * Current segment length offset. When asking for the length of the current segment, the
300b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez         * length will be reduced by this amount. This is useful when we are only using portions of
301b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez         * the segment.
302b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez         *
303b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez         * @see #jumpToSegment(float)
304b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez         */
305b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        private float mOffsetLength;
306b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez
307b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        /** Point where the current segment started */
308b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        private float[] mLastPoint = new float[2];
309b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        private boolean isIteratorDone;
310b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez
311b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        private CachedPathIterator() {
312b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez            next();
313b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        }
314b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez
315b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        public float getCurrentSegmentLength() {
316b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez            return mCurrentSegmentLength;
317b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        }
318b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez
319b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        @Override
320b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        public int getWindingRule() {
321b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez            return mWindingRule;
322b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        }
323b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez
324b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        @Override
325b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        public boolean isDone() {
326b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez            return isIteratorDone;
327b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        }
328b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez
329b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        @Override
330b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        public void next() {
331b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez            if (mNextIndex >= mTypes.length) {
332b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                isIteratorDone = true;
333b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                return;
334b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez            }
335b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez
336b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez            if (mNextIndex >= 1) {
337b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                // We've already called next() once so there is a previous segment in this path.
338b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                // We want to get the coordinates where the path ends.
339b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                getShapeEndPoint(mCurrentType, mCurrentCoords, mLastPoint);
340b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez            } else {
341b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                // This is the first segment, no previous point so initialize to 0, 0
342b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                mLastPoint[0] = mLastPoint[1] = 0f;
343b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez            }
344b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez            mCurrentType = mTypes[mNextIndex];
345b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez            mCurrentSegmentLength = mSegmentsLength[mNextIndex] - mOffsetLength;
346b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez
347b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez            if (mOffsetLength > 0f && (mCurrentType == SEG_CUBICTO || mCurrentType == SEG_QUADTO)) {
348b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                // We need to skip part of the start of the current segment (because
349b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                // mOffsetLength > 0)
350b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                float[] points = new float[8];
351b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez
352b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                if (mNextIndex < 1) {
353b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                    points[0] = points[1] = 0f;
354b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                } else {
355b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                    getShapeEndPoint(mTypes[mNextIndex - 1], mCoordinates[mNextIndex - 1], points);
356b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                }
357b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez
358b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                System.arraycopy(mCoordinates[mNextIndex], 0, points, 2,
359b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                        mCoordinates[mNextIndex].length);
360b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                float t0 = (mSegmentsLength[mNextIndex] - mCurrentSegmentLength) /
361b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                        mSegmentsLength[mNextIndex];
362b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                if (mCurrentType == SEG_CUBICTO) {
363b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                    cubicCurveSegment(points, t0, 1f);
364b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                } else {
365b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                    quadCurveSegment(points, t0, 1f);
366b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                }
367b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                System.arraycopy(points, 2, mCurrentCoords, 0, mCoordinates[mNextIndex].length);
368b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez            } else {
369b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                System.arraycopy(mCoordinates[mNextIndex], 0, mCurrentCoords, 0,
370b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                        mCoordinates[mNextIndex].length);
371b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez            }
372b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez
373b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez            mOffsetLength = 0f;
374b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez            mNextIndex++;
375b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        }
376b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez
377b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        @Override
378b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        public int currentSegment(float[] coords) {
379b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez            System.arraycopy(mCurrentCoords, 0, coords, 0, getNumberOfPoints(mCurrentType) * 2);
380b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez            return mCurrentType;
381b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        }
382b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez
383b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        @Override
384b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        public int currentSegment(double[] coords) {
385b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez            throw new UnsupportedOperationException();
386b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        }
387b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez
388b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        /**
389b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez         * Returns the point where the current segment ends
390b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez         */
391b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        public void getCurrentSegmentEnd(float[] point) {
392b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez            point[0] = mLastPoint[0];
393b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez            point[1] = mLastPoint[1];
394b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        }
395b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez
396b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        /**
397b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez         * Restarts the iterator and jumps all the segments of this path up to the length value.
398b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez         */
399b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        public void jumpToSegment(float length) {
400b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez            isIteratorDone = false;
401b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez            if (length <= 0f) {
402b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                mNextIndex = 0;
403b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                return;
404b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez            }
405b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez
406b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez            float accLength = 0;
407b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez            float lastPoint[] = new float[2];
408b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez            for (mNextIndex = 0; mNextIndex < mTypes.length; mNextIndex++) {
409b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                float segmentLength = mSegmentsLength[mNextIndex];
410b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                if (accLength + segmentLength >= length && mTypes[mNextIndex] != SEG_MOVETO) {
411b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                    float[] estimatedPoint = new float[2];
412b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                    getPointAtLength(mTypes[mNextIndex],
413b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                            mCoordinates[mNextIndex], lastPoint[0], lastPoint[1],
414b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                            (length - accLength) / segmentLength,
415b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                            estimatedPoint);
416b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez
417b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                    // This segment makes us go further than length so we go back one step,
418b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                    // set a moveto and offset the length of the next segment by the length
419b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                    // of this segment that we've already used.
420b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                    mCurrentType = PathIterator.SEG_MOVETO;
421b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                    mCurrentCoords[0] = estimatedPoint[0];
422b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                    mCurrentCoords[1] = estimatedPoint[1];
423b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                    mCurrentSegmentLength = 0;
424b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez
425b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                    // We need to offset next path length to account for the segment we've just
426b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                    // skipped.
427b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                    mOffsetLength = length - accLength;
428b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                    return;
429b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                }
430b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                accLength += segmentLength;
431b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                getShapeEndPoint(mTypes[mNextIndex], mCoordinates[mNextIndex], lastPoint);
432b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez            }
433b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        }
434b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez
435b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        /**
436b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez         * Returns the current segment up to certain length. If the current segment is shorter than
437b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez         * length, then the whole segment is returned. The segment coordinates are copied into the
438b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez         * coords array.
439b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez         *
440b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez         * @return the segment type
441b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez         */
442b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        public int currentSegment(@NonNull float[] coords, float length) {
443b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez            int type = currentSegment(coords);
444b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez            // If the length is greater than the current segment length, no need to find
445b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez            // the cut point. Same if this is a SEG_MOVETO.
446b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez            if (mCurrentSegmentLength <= length || type == SEG_MOVETO) {
447b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                return type;
448b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez            }
449b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez
450b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez            float t = length / getCurrentSegmentLength();
451b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez
452b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez            // We find at which offset the end point is located within the coords array and set
453b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez            // a new end point to cut the segment short
454b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez            switch (type) {
455b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                case SEG_CUBICTO:
456b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                case SEG_QUADTO:
457b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                    float[] curve = new float[8];
458b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                    curve[0] = mLastPoint[0];
459b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                    curve[1] = mLastPoint[1];
460b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                    System.arraycopy(coords, 0, curve, 2, coords.length);
461b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                    if (type == SEG_CUBICTO) {
462b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                        cubicCurveSegment(curve, 0f, t);
463b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                    } else {
464b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                        quadCurveSegment(curve, 0f, t);
465b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                    }
466b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                    System.arraycopy(curve, 2, coords, 0, coords.length);
467b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                    break;
468b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                default:
469b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                    float[] point = new float[2];
470b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                    getPointAtLength(type, coords, mLastPoint[0], mLastPoint[1], t, point);
471b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                    coords[0] = point[0];
472b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez                    coords[1] = point[1];
473b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez            }
474b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez
475b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez            return type;
476b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        }
477b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez
478b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        /**
479b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez         * Returns the total length of the path
480b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez         */
481b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        public float getTotalLength() {
482b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez            return mTotalLength;
483b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez        }
484b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez    }
485b9c48d8f49d35e2682c7205a9d8d5fcc25d7c736Diego Perez}
486