import com.android.ide.common.rendering.api.LayoutLog;
import com.android.layoutlib.bridge.Bridge;
import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.layoutlib.bridge.util.CachedPathIteratorFactory;
import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+import com.android.layoutlib.bridge.util.CachedPathIteratorFactory.CachedPathIterator;
+
import java.awt.geom.PathIterator;
-import java.awt.geom.Point2D;
/**
* Delegate implementing the native methods of {@link android.graphics.PathMeasure}
* @see DelegateManager
*/
public final class PathMeasure_Delegate {
+
// ---- delegate manager ----
private static final DelegateManager<PathMeasure_Delegate> sManager =
new DelegateManager<PathMeasure_Delegate>(PathMeasure_Delegate.class);
// ---- delegate data ----
- // This governs how accurate the approximation of the Path is.
- private static final float PRECISION = 0.0002f;
-
- /**
- * Array containing the path points components. There are three components for each point:
- * <ul>
- * <li>Fraction along the length of the path that the point resides</li>
- * <li>The x coordinate of the point</li>
- * <li>The y coordinate of the point</li>
- * </ul>
- */
- private float mPathPoints[];
+ private CachedPathIteratorFactory mOriginalPathIterator;
+
private long mNativePath;
+
private PathMeasure_Delegate(long native_path, boolean forceClosed) {
mNativePath = native_path;
- if (forceClosed && mNativePath != 0) {
- // Copy the path and call close
- mNativePath = Path_Delegate.init2(native_path);
- Path_Delegate.native_close(mNativePath);
- }
+ if (native_path != 0) {
+ if (forceClosed) {
+ // Copy the path and call close
+ native_path = Path_Delegate.init2(native_path);
+ Path_Delegate.native_close(native_path);
+ }
- mPathPoints =
- mNativePath != 0 ? Path_Delegate.native_approximate(mNativePath, PRECISION) : null;
+ Path_Delegate pathDelegate = Path_Delegate.getDelegate(native_path);
+ mOriginalPathIterator = new CachedPathIteratorFactory(pathDelegate.getJavaShape()
+ .getPathIterator(null));
+ }
}
@LayoutlibDelegate
PathMeasure_Delegate pathMeasure = sManager.getDelegate(native_instance);
assert pathMeasure != null;
- if (forceClosed && native_path != 0) {
- // Copy the path and call close
- native_path = Path_Delegate.init2(native_path);
- Path_Delegate.native_close(native_path);
+ if (native_path != 0) {
+ if (forceClosed) {
+ // Copy the path and call close
+ native_path = Path_Delegate.init2(native_path);
+ Path_Delegate.native_close(native_path);
+ }
+
+ Path_Delegate pathDelegate = Path_Delegate.getDelegate(native_path);
+ pathMeasure.mOriginalPathIterator = new CachedPathIteratorFactory(pathDelegate.getJavaShape()
+ .getPathIterator(null));
}
+
pathMeasure.mNativePath = native_path;
- pathMeasure.mPathPoints = Path_Delegate.native_approximate(native_path, PRECISION);
}
@LayoutlibDelegate
PathMeasure_Delegate pathMeasure = sManager.getDelegate(native_instance);
assert pathMeasure != null;
- if (pathMeasure.mPathPoints == null) {
+ if (pathMeasure.mOriginalPathIterator == null) {
return 0;
}
- float length = 0;
- int nPoints = pathMeasure.mPathPoints.length / 3;
- for (int i = 1; i < nPoints; i++) {
- length += Point2D.distance(
- pathMeasure.mPathPoints[(i - 1) * 3 + 1],
- pathMeasure.mPathPoints[(i - 1) * 3 + 2],
- pathMeasure.mPathPoints[i*3 + 1],
- pathMeasure.mPathPoints[i*3 + 2]);
- }
-
- return length;
+ return pathMeasure.mOriginalPathIterator.iterator().getTotalLength();
}
@LayoutlibDelegate
return false;
}
- PathIterator pathIterator = path.getJavaShape().getPathIterator(null);
-
int type = 0;
float segment[] = new float[6];
- while (!pathIterator.isDone()) {
- type = pathIterator.currentSegment(segment);
- pathIterator.next();
+ for (PathIterator pi = path.getJavaShape().getPathIterator(null); !pi.isDone(); pi.next()) {
+ type = pi.currentSegment(segment);
}
// A path is a closed path if the last element is SEG_CLOSE
PathMeasure_Delegate pathMeasure = sManager.getDelegate(native_instance);
assert pathMeasure != null;
- if (pathMeasure.mPathPoints == null) {
- return false;
- }
-
- float accLength = 0;
+ CachedPathIterator iterator = pathMeasure.mOriginalPathIterator.iterator();
+ float accLength = startD;
boolean isZeroLength = true; // Whether the output has zero length or not
- int nPoints = pathMeasure.mPathPoints.length / 3;
- for (int i = 0; i < nPoints; i++) {
- float x = pathMeasure.mPathPoints[i * 3 + 1];
- float y = pathMeasure.mPathPoints[i * 3 + 2];
- if (accLength >= startD && accLength <= stopD) {
+ float[] points = new float[6];
+
+ iterator.jumpToSegment(accLength);
+ while (!iterator.isDone() && (stopD - accLength > 0.1f)) {
+ int type = iterator.currentSegment(points, stopD - accLength);
+
+ if (accLength - iterator.getCurrentSegmentLength() <= stopD) {
if (startWithMoveTo) {
startWithMoveTo = false;
- Path_Delegate.native_moveTo(native_dst_path, x, y);
- } else {
- isZeroLength = false;
- Path_Delegate.native_lineTo(native_dst_path, x, y);
+
+ // If this segment is a MOVETO, then we just use that one. If not, then we issue
+ // a first moveto
+ if (type != PathIterator.SEG_MOVETO) {
+ float[] lastPoint = new float[2];
+ iterator.getCurrentSegmentEnd(lastPoint);
+ Path_Delegate.native_moveTo(native_dst_path, lastPoint[0], lastPoint[1]);
+ }
}
- }
- if (i > 0) {
- accLength += Point2D.distance(
- pathMeasure.mPathPoints[(i - 1) * 3 + 1],
- pathMeasure.mPathPoints[(i - 1) * 3 + 2],
- pathMeasure.mPathPoints[i * 3 + 1],
- pathMeasure.mPathPoints[i * 3 + 2]);
+ isZeroLength = isZeroLength && iterator.getCurrentSegmentLength() > 0;
+ switch (type) {
+ case PathIterator.SEG_MOVETO:
+ Path_Delegate.native_moveTo(native_dst_path, points[0], points[1]);
+ break;
+ case PathIterator.SEG_LINETO:
+ Path_Delegate.native_lineTo(native_dst_path, points[0], points[1]);
+ break;
+ case PathIterator.SEG_CLOSE:
+ Path_Delegate.native_close(native_dst_path);
+ break;
+ case PathIterator.SEG_CUBICTO:
+ Path_Delegate.native_cubicTo(native_dst_path, points[0], points[1],
+ points[2], points[3],
+ points[4], points[5]);
+ break;
+ case PathIterator.SEG_QUADTO:
+ Path_Delegate.native_quadTo(native_dst_path, points[0], points[1],
+ points[2],
+ points[3]);
+ break;
+ default:
+ assert false;
+ }
}
+
+ accLength += iterator.getCurrentSegmentLength();
+ iterator.next();
}
return !isZeroLength;
private static final DelegateManager<Path_Delegate> sManager =
new DelegateManager<Path_Delegate>(Path_Delegate.class);
+ private static final float EPSILON = 1e-4f;
+
// ---- delegate data ----
private FillType mFillType = FillType.WINDING;
private Path2D mPath = new Path2D.Double();
private float mLastX = 0;
private float mLastY = 0;
+ // true if the path contains does not contain a curve or line.
+ private boolean mCachedIsEmpty = true;
+
// ---- Public Helper methods ----
public static Path_Delegate getDelegate(long nPath) {
}
public void setJavaShape(Shape shape) {
- mPath.reset();
+ reset();
mPath.append(shape, false /*connect*/);
}
}
public void setPathIterator(PathIterator iterator) {
- mPath.reset();
+ reset();
mPath.append(iterator, false /*connect*/);
}
/**
- * Returns whether the path is empty.
- * @return true if the path is empty.
+ * Returns whether the path already contains any points.
+ * Note that this is different to
+ * {@link #isEmpty} because if all elements are {@link PathIterator#SEG_MOVETO},
+ * {@link #isEmpty} will return true while hasPoints will return false.
*/
- private boolean isEmpty() {
- return mPath.getCurrentPoint() == null;
+ public boolean hasPoints() {
+ return !mPath.getPathIterator(null).isDone();
+ }
+
+ /**
+ * Returns whether the path is empty (contains no lines or curves).
+ * @see Path#isEmpty
+ */
+ public boolean isEmpty() {
+ if (!mCachedIsEmpty) {
+ return false;
+ }
+
+ float[] coords = new float[6];
+ mCachedIsEmpty = Boolean.TRUE;
+ for (PathIterator it = mPath.getPathIterator(null); !it.isDone(); it.next()) {
+ int type = it.currentSegment(coords);
+ if (type != PathIterator.SEG_MOVETO) {
+ // Once we know that the path is not empty, we do not need to check again unless
+ // Path#reset is called.
+ mCachedIsEmpty = false;
+ return false;
+ }
+ }
+
+ return true;
}
/**
* @param y The y-coordinate of the end of a line
*/
private void lineTo(float x, float y) {
- if (isEmpty()) {
+ if (!hasPoints()) {
mPath.moveTo(mLastX = 0, mLastY = 0);
}
mPath.lineTo(mLastX = x, mLastY = y);
* this contour, to specify a line
*/
private void rLineTo(float dx, float dy) {
- if (isEmpty()) {
+ if (!hasPoints()) {
mPath.moveTo(mLastX = 0, mLastY = 0);
}
+
+ if (Math.abs(dx) < EPSILON && Math.abs(dy) < EPSILON) {
+ // The delta is so small that this shouldn't generate a line
+ return;
+ }
+
dx += mLastX;
dy += mLastY;
mPath.lineTo(mLastX = dx, mLastY = dy);
* this contour, for the end point of a quadratic curve
*/
private void rQuadTo(float dx1, float dy1, float dx2, float dy2) {
- if (isEmpty()) {
+ if (!hasPoints()) {
mPath.moveTo(mLastX = 0, mLastY = 0);
}
dx1 += mLastX;
*/
private void cubicTo(float x1, float y1, float x2, float y2,
float x3, float y3) {
- if (isEmpty()) {
+ if (!hasPoints()) {
mPath.moveTo(0, 0);
}
mPath.curveTo(x1, y1, x2, y2, mLastX = x3, mLastY = y3);
*/
private void rCubicTo(float dx1, float dy1, float dx2, float dy2,
float dx3, float dy3) {
- if (isEmpty()) {
+ if (!hasPoints()) {
mPath.moveTo(mLastX = 0, mLastY = 0);
}
dx1 += mLastX;
--- /dev/null
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.layoutlib.bridge.util;
+
+import android.annotation.NonNull;
+
+import java.awt.geom.CubicCurve2D;
+import java.awt.geom.PathIterator;
+import java.awt.geom.Point2D;
+import java.awt.geom.QuadCurve2D;
+import java.util.ArrayList;
+
+import com.google.android.collect.Lists;
+
+/**
+ * Class that returns iterators for a given path. These iterators are lightweight and can be reused
+ * multiple times to iterate over the path.
+ */
+public class CachedPathIteratorFactory {
+ /*
+ * A few conventions used in the code:
+ * Coordinates or coords arrays store segment coordinates. They use the same format as
+ * PathIterator#currentSegment coordinates array.
+ * float arrays store always points where the first element is X and the second is Y.
+ */
+
+ // This governs how accurate the approximation of the Path is.
+ private static final float PRECISION = 0.002f;
+
+ private final int mWindingRule;
+ private final int[] mTypes;
+ private final float[][] mCoordinates;
+ private final float[] mSegmentsLength;
+ private final float mTotalLength;
+
+ public CachedPathIteratorFactory(@NonNull PathIterator iterator) {
+ mWindingRule = iterator.getWindingRule();
+
+ ArrayList<Integer> typesArray = Lists.newArrayList();
+ ArrayList<float[]> pointsArray = Lists.newArrayList();
+ float[] points = new float[6];
+ while (!iterator.isDone()) {
+ int type = iterator.currentSegment(points);
+ int nPoints = getNumberOfPoints(type) * 2; // 2 coordinates per point
+
+ typesArray.add(type);
+ float[] itemPoints = new float[nPoints];
+ System.arraycopy(points, 0, itemPoints, 0, nPoints);
+ pointsArray.add(itemPoints);
+ iterator.next();
+ }
+
+ mTypes = new int[typesArray.size()];
+ mCoordinates = new float[mTypes.length][];
+ for (int i = 0; i < typesArray.size(); i++) {
+ mTypes[i] = typesArray.get(i);
+ mCoordinates[i] = pointsArray.get(i);
+ }
+
+ // Do measurement
+ mSegmentsLength = new float[mTypes.length];
+
+ // Curves that we can reuse to estimate segments length
+ CubicCurve2D.Float cubicCurve = new CubicCurve2D.Float();
+ QuadCurve2D.Float quadCurve = new QuadCurve2D.Float();
+ float lastX = 0;
+ float lastY = 0;
+ float totalLength = 0;
+ for (int i = 0; i < mTypes.length; i++) {
+ switch (mTypes[i]) {
+ case PathIterator.SEG_CUBICTO:
+ cubicCurve.setCurve(lastX, lastY,
+ mCoordinates[i][0], mCoordinates[i][1], mCoordinates[i][2],
+ mCoordinates[i][3], lastX = mCoordinates[i][4],
+ lastY = mCoordinates[i][5]);
+ mSegmentsLength[i] =
+ getFlatPathLength(cubicCurve.getPathIterator(null, PRECISION));
+ break;
+ case PathIterator.SEG_QUADTO:
+ quadCurve.setCurve(lastX, lastY, mCoordinates[i][0], mCoordinates[i][1],
+ lastX = mCoordinates[i][2], lastY = mCoordinates[i][3]);
+ mSegmentsLength[i] =
+ getFlatPathLength(quadCurve.getPathIterator(null, PRECISION));
+ break;
+ case PathIterator.SEG_CLOSE:
+ mSegmentsLength[i] = (float) Point2D.distance(lastX, lastY,
+ lastX = mCoordinates[0][0],
+ lastY = mCoordinates[0][1]);
+ mCoordinates[i] = new float[2];
+ // We convert a SEG_CLOSE segment to a SEG_LINETO so we do not have to worry
+ // about this special case in the rest of the code.
+ mTypes[i] = PathIterator.SEG_LINETO;
+ mCoordinates[i][0] = mCoordinates[0][0];
+ mCoordinates[i][1] = mCoordinates[0][1];
+ break;
+ case PathIterator.SEG_MOVETO:
+ mSegmentsLength[i] = 0;
+ lastX = mCoordinates[i][0];
+ lastY = mCoordinates[i][1];
+ break;
+ case PathIterator.SEG_LINETO:
+ mSegmentsLength[i] = (float) Point2D.distance(lastX, lastY, mCoordinates[i][0],
+ mCoordinates[i][1]);
+ lastX = mCoordinates[i][0];
+ lastY = mCoordinates[i][1];
+ default:
+ }
+ totalLength += mSegmentsLength[i];
+ }
+
+ mTotalLength = totalLength;
+ }
+
+ private static void quadCurveSegment(float[] coords, float t0, float t1) {
+ // Calculate X and Y at 0.5 (We'll use this to reconstruct the control point later)
+ float mt = t0 + (t1 - t0) / 2;
+ float mu = 1 - mt;
+ float mx = mu * mu * coords[0] + 2 * mu * mt * coords[2] + mt * mt * coords[4];
+ float my = mu * mu * coords[1] + 2 * mu * mt * coords[3] + mt * mt * coords[5];
+
+ float u0 = 1 - t0;
+ float u1 = 1 - t1;
+
+ // coords at t0
+ coords[0] = coords[0] * u0 * u0 + coords[2] * 2 * t0 * u0 + coords[4] * t0 * t0;
+ coords[1] = coords[1] * u0 * u0 + coords[3] * 2 * t0 * u0 + coords[5] * t0 * t0;
+
+ // coords at t1
+ coords[4] = coords[0] * u1 * u1 + coords[2] * 2 * t1 * u1 + coords[4] * t1 * t1;
+ coords[5] = coords[1] * u1 * u1 + coords[3] * 2 * t1 * u1 + coords[5] * t1 * t1;
+
+ // estimated control point at t'=0.5
+ coords[2] = 2 * mx - coords[0] / 2 - coords[4] / 2;
+ coords[3] = 2 * my - coords[1] / 2 - coords[5] / 2;
+ }
+
+ private static void cubicCurveSegment(float[] coords, float t0, float t1) {
+ // http://stackoverflow.com/questions/11703283/cubic-bezier-curve-segment
+ float u0 = 1 - t0;
+ float u1 = 1 - t1;
+
+ // Calculate the points at t0 and t1 for the quadratic curves formed by (P0, P1, P2) and
+ // (P1, P2, P3)
+ float qxa = coords[0] * u0 * u0 + coords[2] * 2 * t0 * u0 + coords[4] * t0 * t0;
+ float qxb = coords[0] * u1 * u1 + coords[2] * 2 * t1 * u1 + coords[4] * t1 * t1;
+ float qxc = coords[2] * u0 * u0 + coords[4] * 2 * t0 * u0 + coords[6] * t0 * t0;
+ float qxd = coords[2] * u1 * u1 + coords[4] * 2 * t1 * u1 + coords[6] * t1 * t1;
+
+ float qya = coords[1] * u0 * u0 + coords[3] * 2 * t0 * u0 + coords[5] * t0 * t0;
+ float qyb = coords[1] * u1 * u1 + coords[3] * 2 * t1 * u1 + coords[5] * t1 * t1;
+ float qyc = coords[3] * u0 * u0 + coords[5] * 2 * t0 * u0 + coords[7] * t0 * t0;
+ float qyd = coords[3] * u1 * u1 + coords[5] * 2 * t1 * u1 + coords[7] * t1 * t1;
+
+ // Linear interpolation
+ coords[0] = qxa * u0 + qxc * t0;
+ coords[1] = qya * u0 + qyc * t0;
+
+ coords[2] = qxa * u1 + qxc * t1;
+ coords[3] = qya * u1 + qyc * t1;
+
+ coords[4] = qxb * u0 + qxd * t0;
+ coords[5] = qyb * u0 + qyd * t0;
+
+ coords[6] = qxb * u1 + qxd * t1;
+ coords[7] = qyb * u1 + qyd * t1;
+ }
+
+ /**
+ * Returns the end point of a given segment
+ *
+ * @param type the segment type
+ * @param coords the segment coordinates array
+ * @param point the return array where the point will be stored
+ */
+ private static void getShapeEndPoint(int type, @NonNull float[] coords, @NonNull float[]
+ point) {
+ // start index of the end point for the segment type
+ int pointIndex = (getNumberOfPoints(type) - 1) * 2;
+ point[0] = coords[pointIndex];
+ point[1] = coords[pointIndex + 1];
+ }
+
+ /**
+ * Returns the number of points stored in a coordinates array for the given segment type.
+ */
+ private static int getNumberOfPoints(int segmentType) {
+ switch (segmentType) {
+ case PathIterator.SEG_QUADTO:
+ return 2;
+ case PathIterator.SEG_CUBICTO:
+ return 3;
+ case PathIterator.SEG_CLOSE:
+ return 0;
+ default:
+ return 1;
+ }
+ }
+
+ /**
+ * Returns the estimated length of a flat path. If the passed path is not flat (i.e. contains a
+ * segment that is not {@link PathIterator#SEG_CLOSE}, {@link PathIterator#SEG_MOVETO} or {@link
+ * PathIterator#SEG_LINETO} this method will fail.
+ */
+ private static float getFlatPathLength(@NonNull PathIterator iterator) {
+ float segment[] = new float[6];
+ float totalLength = 0;
+ float[] previousPoint = new float[2];
+ boolean isFirstPoint = true;
+
+ while (!iterator.isDone()) {
+ int type = iterator.currentSegment(segment);
+ assert type == PathIterator.SEG_LINETO || type == PathIterator.SEG_CLOSE || type ==
+ PathIterator.SEG_MOVETO;
+
+ // MoveTo shouldn't affect the length
+ if (!isFirstPoint && type != PathIterator.SEG_MOVETO) {
+ totalLength += Point2D.distance(previousPoint[0], previousPoint[1], segment[0],
+ segment[1]);
+ } else {
+ isFirstPoint = false;
+ }
+ previousPoint[0] = segment[0];
+ previousPoint[1] = segment[1];
+ iterator.next();
+ }
+
+ return totalLength;
+ }
+
+ /**
+ * Returns the estimated position along a path of the given length.
+ */
+ private void getPointAtLength(int type, @NonNull float[] coords, float lastX, float
+ lastY, float t, @NonNull float[] point) {
+ if (type == PathIterator.SEG_LINETO) {
+ point[0] = lastX + (coords[0] - lastX) * t;
+ point[1] = lastY + (coords[1] - lastY) * t;
+ // Return here, since we do not need a shape to estimate
+ return;
+ }
+
+ float[] curve = new float[8];
+ int lastPointIndex = (getNumberOfPoints(type) - 1) * 2;
+
+ System.arraycopy(coords, 0, curve, 2, coords.length);
+ curve[0] = lastX;
+ curve[1] = lastY;
+ if (type == PathIterator.SEG_CUBICTO) {
+ cubicCurveSegment(curve, 0f, t);
+ } else {
+ quadCurveSegment(curve, 0f, t);
+ }
+
+ point[0] = curve[2 + lastPointIndex];
+ point[1] = curve[2 + lastPointIndex + 1];
+ }
+
+ public CachedPathIterator iterator() {
+ return new CachedPathIterator();
+ }
+
+ /**
+ * Class that allows us to iterate over a path multiple times
+ */
+ public class CachedPathIterator implements PathIterator {
+ private int mNextIndex;
+
+ /**
+ * Current segment type.
+ *
+ * @see PathIterator
+ */
+ private int mCurrentType;
+
+ /**
+ * Stores the coordinates array of the current segment. The number of points stored depends
+ * on the segment type.
+ *
+ * @see PathIterator
+ */
+ private float[] mCurrentCoords = new float[6];
+ private float mCurrentSegmentLength;
+
+ /**
+ * Current segment length offset. When asking for the length of the current segment, the
+ * length will be reduced by this amount. This is useful when we are only using portions of
+ * the segment.
+ *
+ * @see #jumpToSegment(float)
+ */
+ private float mOffsetLength;
+
+ /** Point where the current segment started */
+ private float[] mLastPoint = new float[2];
+ private boolean isIteratorDone;
+
+ private CachedPathIterator() {
+ next();
+ }
+
+ public float getCurrentSegmentLength() {
+ return mCurrentSegmentLength;
+ }
+
+ @Override
+ public int getWindingRule() {
+ return mWindingRule;
+ }
+
+ @Override
+ public boolean isDone() {
+ return isIteratorDone;
+ }
+
+ @Override
+ public void next() {
+ if (mNextIndex >= mTypes.length) {
+ isIteratorDone = true;
+ return;
+ }
+
+ if (mNextIndex >= 1) {
+ // We've already called next() once so there is a previous segment in this path.
+ // We want to get the coordinates where the path ends.
+ getShapeEndPoint(mCurrentType, mCurrentCoords, mLastPoint);
+ } else {
+ // This is the first segment, no previous point so initialize to 0, 0
+ mLastPoint[0] = mLastPoint[1] = 0f;
+ }
+ mCurrentType = mTypes[mNextIndex];
+ mCurrentSegmentLength = mSegmentsLength[mNextIndex] - mOffsetLength;
+
+ if (mOffsetLength > 0f && (mCurrentType == SEG_CUBICTO || mCurrentType == SEG_QUADTO)) {
+ // We need to skip part of the start of the current segment (because
+ // mOffsetLength > 0)
+ float[] points = new float[8];
+
+ if (mNextIndex < 1) {
+ points[0] = points[1] = 0f;
+ } else {
+ getShapeEndPoint(mTypes[mNextIndex - 1], mCoordinates[mNextIndex - 1], points);
+ }
+
+ System.arraycopy(mCoordinates[mNextIndex], 0, points, 2,
+ mCoordinates[mNextIndex].length);
+ float t0 = (mSegmentsLength[mNextIndex] - mCurrentSegmentLength) /
+ mSegmentsLength[mNextIndex];
+ if (mCurrentType == SEG_CUBICTO) {
+ cubicCurveSegment(points, t0, 1f);
+ } else {
+ quadCurveSegment(points, t0, 1f);
+ }
+ System.arraycopy(points, 2, mCurrentCoords, 0, mCoordinates[mNextIndex].length);
+ } else {
+ System.arraycopy(mCoordinates[mNextIndex], 0, mCurrentCoords, 0,
+ mCoordinates[mNextIndex].length);
+ }
+
+ mOffsetLength = 0f;
+ mNextIndex++;
+ }
+
+ @Override
+ public int currentSegment(float[] coords) {
+ System.arraycopy(mCurrentCoords, 0, coords, 0, getNumberOfPoints(mCurrentType) * 2);
+ return mCurrentType;
+ }
+
+ @Override
+ public int currentSegment(double[] coords) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Returns the point where the current segment ends
+ */
+ public void getCurrentSegmentEnd(float[] point) {
+ point[0] = mLastPoint[0];
+ point[1] = mLastPoint[1];
+ }
+
+ /**
+ * Restarts the iterator and jumps all the segments of this path up to the length value.
+ */
+ public void jumpToSegment(float length) {
+ isIteratorDone = false;
+ if (length <= 0f) {
+ mNextIndex = 0;
+ return;
+ }
+
+ float accLength = 0;
+ float lastPoint[] = new float[2];
+ for (mNextIndex = 0; mNextIndex < mTypes.length; mNextIndex++) {
+ float segmentLength = mSegmentsLength[mNextIndex];
+ if (accLength + segmentLength >= length && mTypes[mNextIndex] != SEG_MOVETO) {
+ float[] estimatedPoint = new float[2];
+ getPointAtLength(mTypes[mNextIndex],
+ mCoordinates[mNextIndex], lastPoint[0], lastPoint[1],
+ (length - accLength) / segmentLength,
+ estimatedPoint);
+
+ // This segment makes us go further than length so we go back one step,
+ // set a moveto and offset the length of the next segment by the length
+ // of this segment that we've already used.
+ mCurrentType = PathIterator.SEG_MOVETO;
+ mCurrentCoords[0] = estimatedPoint[0];
+ mCurrentCoords[1] = estimatedPoint[1];
+ mCurrentSegmentLength = 0;
+
+ // We need to offset next path length to account for the segment we've just
+ // skipped.
+ mOffsetLength = length - accLength;
+ return;
+ }
+ accLength += segmentLength;
+ getShapeEndPoint(mTypes[mNextIndex], mCoordinates[mNextIndex], lastPoint);
+ }
+ }
+
+ /**
+ * Returns the current segment up to certain length. If the current segment is shorter than
+ * length, then the whole segment is returned. The segment coordinates are copied into the
+ * coords array.
+ *
+ * @return the segment type
+ */
+ public int currentSegment(@NonNull float[] coords, float length) {
+ int type = currentSegment(coords);
+ // If the length is greater than the current segment length, no need to find
+ // the cut point. Same if this is a SEG_MOVETO.
+ if (mCurrentSegmentLength <= length || type == SEG_MOVETO) {
+ return type;
+ }
+
+ float t = length / getCurrentSegmentLength();
+
+ // We find at which offset the end point is located within the coords array and set
+ // a new end point to cut the segment short
+ switch (type) {
+ case SEG_CUBICTO:
+ case SEG_QUADTO:
+ float[] curve = new float[8];
+ curve[0] = mLastPoint[0];
+ curve[1] = mLastPoint[1];
+ System.arraycopy(coords, 0, curve, 2, coords.length);
+ if (type == SEG_CUBICTO) {
+ cubicCurveSegment(curve, 0f, t);
+ } else {
+ quadCurveSegment(curve, 0f, t);
+ }
+ System.arraycopy(curve, 2, coords, 0, coords.length);
+ break;
+ default:
+ float[] point = new float[2];
+ getPointAtLength(type, coords, mLastPoint[0], mLastPoint[1], t, point);
+ coords[0] = point[0];
+ coords[1] = point[1];
+ }
+
+ return type;
+ }
+
+ /**
+ * Returns the total length of the path
+ */
+ public float getTotalLength() {
+ return mTotalLength;
+ }
+ }
+}