1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.graphics;
18 
19 import static java.lang.annotation.RetentionPolicy.SOURCE;
20 
21 import android.annotation.IntDef;
22 import android.annotation.NonNull;
23 
24 import dalvik.annotation.optimization.CriticalNative;
25 import dalvik.system.VMRuntime;
26 
27 import libcore.util.NativeAllocationRegistry;
28 
29 import java.lang.annotation.Retention;
30 import java.util.ConcurrentModificationException;
31 import java.util.Iterator;
32 
33 /**
34  * <code>PathIterator</code> can be used to query a given {@link Path} object, to discover its
35  * operations and point values.
36  */
37 public class PathIterator implements Iterator<PathIterator.Segment> {
38 
39     private final float[] mPointsArray;
40     private final long mPointsAddress;
41     private int mCachedVerb = -1;
42     private boolean mDone = false;
43     private final long mNativeIterator;
44     private final Path mPath;
45     private final int mPathGenerationId;
46     private static final int POINT_ARRAY_SIZE = 8;
47 
48     private static final NativeAllocationRegistry sRegistry =
49             NativeAllocationRegistry.createMalloced(
50                     PathIterator.class.getClassLoader(), nGetFinalizer());
51 
52     /**
53      * The <code>Verb</code> indicates the operation for a given segment of a path. These
54      * operations correspond exactly to the primitive operations on {@link Path}, such as
55      * {@link Path#moveTo(float, float)} and {@link Path#lineTo(float, float)}, except for
56      * {@link #VERB_DONE}, which means that there are no more operations in this path.
57      */
58     @Retention(SOURCE)
59     @IntDef({VERB_MOVE, VERB_LINE, VERB_QUAD, VERB_CONIC, VERB_CUBIC, VERB_CLOSE, VERB_DONE})
60     @interface Verb {}
61     // these must match the values in SkPath.h
62     public static final int VERB_MOVE = 0;
63     public static final int VERB_LINE = 1;
64     public static final int VERB_QUAD = 2;
65     public static final int VERB_CONIC = 3;
66     public static final int VERB_CUBIC = 4;
67     public static final int VERB_CLOSE = 5;
68     public static final int VERB_DONE = 6;
69 
70     /**
71      * Returns a {@link PathIterator} object for this path, which can be used to query the
72      * data (operations and points) in the path. Iterators can only be used on Path objects
73      * that have not been modified since the iterator was created. Calling
74      * {@link #next(float[], int)}, {@link #next()}, or {@link #hasNext()} on an
75      * iterator for a modified path will result in a {@link ConcurrentModificationException}.
76      *
77      * @param path The {@link Path} for which this iterator can be queried.
78      */
PathIterator(@onNull Path path)79     PathIterator(@NonNull Path path) {
80         mPath = path;
81         mNativeIterator = nCreate(mPath.mNativePath);
82         mPathGenerationId = mPath.getGenerationId();
83         final VMRuntime runtime = VMRuntime.getRuntime();
84         mPointsArray = (float[]) runtime.newNonMovableArray(float.class, POINT_ARRAY_SIZE);
85         mPointsAddress = runtime.addressOf(mPointsArray);
86         sRegistry.registerNativeAllocation(this, mNativeIterator);
87     }
88 
89     /**
90      * Returns the next verb in this iterator's {@link Path}, and fills entries in the
91      * <code>points</code> array with the point data (if any) for that operation.
92      * Each two floats represent the data for a single point of that operation.
93      * The number of pairs of floats supplied in the resulting array depends on the verb:
94      * <ul>
95      * <li>{@link #VERB_MOVE}: 1 pair (indices 0 to 1)</li>
96      * <li>{@link #VERB_LINE}: 2 pairs (indices 0 to 3)</li>
97      * <li>{@link #VERB_QUAD}: 3 pairs (indices 0 to 5)</li>
98      * <li>{@link #VERB_CONIC}: 3.5 pairs (indices 0 to 6), the seventh entry has the conic
99      * weight</li>
100      * <li>{@link #VERB_CUBIC}: 4 pairs (indices 0 to 7)</li>
101      * <li>{@link #VERB_CLOSE}: 0 pairs</li>
102      * <li>{@link #VERB_DONE}: 0 pairs</li>
103      * </ul>
104      * @param points The point data for this operation, must have at least
105      *               8 items available to hold up to 4 pairs of point values
106      * @param offset An offset into the <code>points</code> array where entries should be placed.
107      * @return the operation for the next element in the iteration
108      * @throws ArrayIndexOutOfBoundsException if the points array is too small
109      * @throws ConcurrentModificationException if the underlying path was modified
110      * since this iterator was created.
111      */
112     @NonNull
next(@onNull float[] points, int offset)113     public @Verb int next(@NonNull float[] points, int offset) {
114         if (points.length < offset + POINT_ARRAY_SIZE) {
115             throw new ArrayIndexOutOfBoundsException("points array must be able to "
116                     + "hold at least 8 entries");
117         }
118         @Verb int returnVerb = getReturnVerb(mCachedVerb);
119         mCachedVerb = -1;
120         System.arraycopy(mPointsArray, 0, points, offset, POINT_ARRAY_SIZE);
121         return returnVerb;
122     }
123 
124     /**
125      * Returns true if the there are more elements in this iterator to be returned.
126      * A return value of <code>false</code> means there are no more elements, and an
127      * ensuing call to {@link #next()} or {@link #next(float[], int)} )} will return
128      * {@link #VERB_DONE}.
129      *
130      * @return true if there are more elements to be iterated through, false otherwise
131      * @throws ConcurrentModificationException if the underlying path was modified
132      * since this iterator was created.
133      */
134     @Override
hasNext()135     public boolean hasNext() {
136         if (mCachedVerb == -1) {
137             mCachedVerb = nextInternal();
138         }
139         return mCachedVerb != VERB_DONE;
140     }
141 
142     /**
143      * Returns the next verb in the iteration, or {@link #VERB_DONE} if there are no more
144      * elements.
145      *
146      * @return the next verb in the iteration, or {@link #VERB_DONE} if there are no more
147      * elements
148      * @throws ConcurrentModificationException if the underlying path was modified
149      * since this iterator was created.
150      */
151     @NonNull
peek()152     public @Verb int peek() {
153         if (mPathGenerationId != mPath.getGenerationId()) {
154             throw new ConcurrentModificationException(
155                     "Iterator cannot be used on modified Path");
156         }
157         if (mDone) {
158             return VERB_DONE;
159         }
160         return nPeek(mNativeIterator);
161     }
162 
163     /**
164      * This is where the work is done for {@link #next()}. Using this internal method
165      * is helfpul for managing the cached segment used by {@link #hasNext()}.
166      *
167      * @return the segment to be returned by {@link #next()}
168      * @throws ConcurrentModificationException if the underlying path was modified
169      * since this iterator was created.
170      */
171     @NonNull
nextInternal()172     private @Verb int nextInternal() {
173         if (mDone) {
174             return VERB_DONE;
175         }
176         if (mPathGenerationId != mPath.getGenerationId()) {
177             throw new ConcurrentModificationException(
178                     "Iterator cannot be used on modified Path");
179         }
180         @Verb int verb = nNext(mNativeIterator, mPointsAddress);
181         if (verb == VERB_DONE) {
182             mDone = true;
183         }
184         return verb;
185     }
186 
187     /**
188      * Returns the next {@link Segment} element in this iterator.
189      *
190      * There are two versions of <code>next()</code>. This version is slightly more
191      * expensive at runtime, since it allocates a new {@link Segment} object with
192      * every call. The other version, {@link #next(float[], int)} requires no such allocation, but
193      * requires a little more manual effort to use.
194      *
195      * @return the next segment in this iterator
196      * @throws ConcurrentModificationException if the underlying path was modified
197      * since this iterator was created.
198      */
199     @NonNull
200     @Override
next()201     public Segment next() {
202         @Verb int returnVerb = getReturnVerb(mCachedVerb);
203         mCachedVerb = -1;
204         float conicWeight = 0f;
205         if (returnVerb == VERB_CONIC) {
206             conicWeight = mPointsArray[6];
207         }
208         float[] returnPoints = new float[8];
209         System.arraycopy(mPointsArray, 0, returnPoints, 0, POINT_ARRAY_SIZE);
210         return new Segment(returnVerb, returnPoints, conicWeight);
211     }
212 
getReturnVerb(int cachedVerb)213     private @Verb int getReturnVerb(int cachedVerb) {
214         switch (cachedVerb) {
215             case VERB_MOVE: return VERB_MOVE;
216             case VERB_LINE: return VERB_LINE;
217             case VERB_QUAD: return VERB_QUAD;
218             case VERB_CONIC: return VERB_CONIC;
219             case VERB_CUBIC: return VERB_CUBIC;
220             case VERB_CLOSE: return VERB_CLOSE;
221             case VERB_DONE: return VERB_DONE;
222         }
223         return nextInternal();
224     }
225 
226     /**
227      * This class holds the data for a given segment in a path, as returned by
228      * {@link #next()}.
229      */
230     public static class Segment {
231         private final @Verb int mVerb;
232         private final float[] mPoints;
233         private final float mConicWeight;
234 
235         /**
236          * The operation for this segment.
237          *
238          * @return the verb which indicates the operation happening in this segment
239          */
240         @NonNull
getVerb()241         public @Verb int getVerb() {
242             return mVerb;
243         }
244 
245         /**
246          * The point data for this segment.
247          *
248          * Each two floats represent the data for a single point of that operation.
249          * The number of pairs of floats supplied in the resulting array depends on the verb:
250          * <ul>
251          * <li>{@link #VERB_MOVE}: 1 pair (indices 0 to 1)</li>
252          * <li>{@link #VERB_LINE}: 2 pairs (indices 0 to 3)</li>
253          * <li>{@link #VERB_QUAD}: 3 pairs (indices 0 to 5)</li>
254          * <li>{@link #VERB_CONIC}: 4 pairs (indices 0 to 7), the last pair contains the
255          * conic weight twice</li>
256          * <li>{@link #VERB_CUBIC}: 4 pairs (indices 0 to 7)</li>
257          * <li>{@link #VERB_CLOSE}: 0 pairs</li>
258          * <li>{@link #VERB_DONE}: 0 pairs</li>
259          * </ul>
260          * @return the point data for this segment
261          */
262         @NonNull
getPoints()263         public float[] getPoints() {
264             return mPoints;
265         }
266 
267         /**
268          * The weight for the conic operation in this segment. If the verb in this segment
269          * is not equal to {@link #VERB_CONIC}, the weight value is undefined.
270          *
271          * @see Path#conicTo(float, float, float, float, float)
272          * @return the weight for the conic operation in this segment, if any
273          */
getConicWeight()274         public float getConicWeight() {
275             return mConicWeight;
276         }
277 
Segment(@onNull @erb int verb, @NonNull float[] points, float conicWeight)278         Segment(@NonNull @Verb int verb, @NonNull float[] points, float conicWeight) {
279             mVerb = verb;
280             mPoints = points;
281             mConicWeight = conicWeight;
282         }
283     }
284 
285     // ------------------ Regular JNI ------------------------
286 
nCreate(long nativePath)287     private static native long nCreate(long nativePath);
nGetFinalizer()288     private static native long nGetFinalizer();
289 
290     // ------------------ Critical JNI ------------------------
291 
292     @CriticalNative
nNext(long nativeIterator, long pointsAddress)293     private static native int nNext(long nativeIterator, long pointsAddress);
294 
295     @CriticalNative
nPeek(long nativeIterator)296     private static native int nPeek(long nativeIterator);
297 }
298