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