• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 @android.ravenwood.annotation.RavenwoodKeepWholeClass
38 public class PathIterator implements Iterator<PathIterator.Segment> {
39 
40     private final float[] mPointsArray;
41     private final long mPointsAddress;
42     private int mCachedVerb = -1;
43     private boolean mDone = false;
44     private final long mNativeIterator;
45     private final Path mPath;
46     private final int mPathGenerationId;
47     private static final int POINT_ARRAY_SIZE = 8;
48     private static final boolean IS_DALVIK = "dalvik".equalsIgnoreCase(
49             System.getProperty("java.vm.name"));
50 
51     private static class NoImagePreloadHolder {
52         private static final NativeAllocationRegistry sRegistry =
53                 NativeAllocationRegistry.createMalloced(
54                         PathIterator.class.getClassLoader(), nGetFinalizer());
55     }
56 
57     /**
58      * The <code>Verb</code> indicates the operation for a given segment of a path. These
59      * operations correspond exactly to the primitive operations on {@link Path}, such as
60      * {@link Path#moveTo(float, float)} and {@link Path#lineTo(float, float)}, except for
61      * {@link #VERB_DONE}, which means that there are no more operations in this path.
62      */
63     @Retention(SOURCE)
64     @IntDef({VERB_MOVE, VERB_LINE, VERB_QUAD, VERB_CONIC, VERB_CUBIC, VERB_CLOSE, VERB_DONE})
65     @interface Verb {}
66     // these must match the values in SkPath.h
67     public static final int VERB_MOVE = 0;
68     public static final int VERB_LINE = 1;
69     public static final int VERB_QUAD = 2;
70     public static final int VERB_CONIC = 3;
71     public static final int VERB_CUBIC = 4;
72     public static final int VERB_CLOSE = 5;
73     public static final int VERB_DONE = 6;
74 
75 
76     static {
77         // Keep <cinit> exist in bytecode
78     }
79 
80     /**
81      * Returns a {@link PathIterator} object for this path, which can be used to query the
82      * data (operations and points) in the path. Iterators can only be used on Path objects
83      * that have not been modified since the iterator was created. Calling
84      * {@link #next(float[], int)}, {@link #next()}, or {@link #hasNext()} on an
85      * iterator for a modified path will result in a {@link ConcurrentModificationException}.
86      *
87      * @param path The {@link Path} for which this iterator can be queried.
88      */
PathIterator(@onNull Path path)89     PathIterator(@NonNull Path path) {
90         mPath = path;
91         mNativeIterator = nCreate(mPath.mNativePath);
92         mPathGenerationId = mPath.getGenerationId();
93         if (IS_DALVIK) {
94             final VMRuntime runtime = VMRuntime.getRuntime();
95             mPointsArray = (float[]) runtime.newNonMovableArray(float.class, POINT_ARRAY_SIZE);
96             mPointsAddress = runtime.addressOf(mPointsArray);
97         } else {
98             mPointsArray = new float[POINT_ARRAY_SIZE];
99             mPointsAddress = 0;
100         }
101         NoImagePreloadHolder.sRegistry.registerNativeAllocation(this, mNativeIterator);
102     }
103 
104     /**
105      * Returns the next verb in this iterator's {@link Path}, and fills entries in the
106      * <code>points</code> array with the point data (if any) for that operation.
107      * Each two floats represent the data for a single point of that operation.
108      * The number of pairs of floats supplied in the resulting array depends on the verb:
109      * <ul>
110      * <li>{@link #VERB_MOVE}: 1 pair (indices 0 to 1)</li>
111      * <li>{@link #VERB_LINE}: 2 pairs (indices 0 to 3)</li>
112      * <li>{@link #VERB_QUAD}: 3 pairs (indices 0 to 5)</li>
113      * <li>{@link #VERB_CONIC}: 3.5 pairs (indices 0 to 6), the seventh entry has the conic
114      * weight</li>
115      * <li>{@link #VERB_CUBIC}: 4 pairs (indices 0 to 7)</li>
116      * <li>{@link #VERB_CLOSE}: 0 pairs</li>
117      * <li>{@link #VERB_DONE}: 0 pairs</li>
118      * </ul>
119      * @param points The point data for this operation, must have at least
120      *               8 items available to hold up to 4 pairs of point values
121      * @param offset An offset into the <code>points</code> array where entries should be placed.
122      * @return the operation for the next element in the iteration
123      * @throws ArrayIndexOutOfBoundsException if the points array is too small
124      * @throws ConcurrentModificationException if the underlying path was modified
125      * since this iterator was created.
126      */
127     @NonNull
next(@onNull float[] points, int offset)128     public @Verb int next(@NonNull float[] points, int offset) {
129         if (points.length < offset + POINT_ARRAY_SIZE) {
130             throw new ArrayIndexOutOfBoundsException("points array must be able to "
131                     + "hold at least 8 entries");
132         }
133         @Verb int returnVerb = getReturnVerb(mCachedVerb);
134         mCachedVerb = -1;
135         System.arraycopy(mPointsArray, 0, points, offset, POINT_ARRAY_SIZE);
136         return returnVerb;
137     }
138 
139     /**
140      * Returns true if the there are more elements in this iterator to be returned.
141      * A return value of <code>false</code> means there are no more elements, and an
142      * ensuing call to {@link #next()} or {@link #next(float[], int)} )} will return
143      * {@link #VERB_DONE}.
144      *
145      * @return true if there are more elements to be iterated through, false otherwise
146      * @throws ConcurrentModificationException if the underlying path was modified
147      * since this iterator was created.
148      */
149     @Override
hasNext()150     public boolean hasNext() {
151         if (mCachedVerb == -1) {
152             mCachedVerb = nextInternal();
153         }
154         return mCachedVerb != VERB_DONE;
155     }
156 
157     /**
158      * Returns the next verb in the iteration, or {@link #VERB_DONE} if there are no more
159      * elements.
160      *
161      * @return the next verb in the iteration, or {@link #VERB_DONE} if there are no more
162      * elements
163      * @throws ConcurrentModificationException if the underlying path was modified
164      * since this iterator was created.
165      */
166     @NonNull
peek()167     public @Verb int peek() {
168         if (mPathGenerationId != mPath.getGenerationId()) {
169             throw new ConcurrentModificationException(
170                     "Iterator cannot be used on modified Path");
171         }
172         if (mDone) {
173             return VERB_DONE;
174         }
175         return nPeek(mNativeIterator);
176     }
177 
178     /**
179      * This is where the work is done for {@link #next()}. Using this internal method
180      * is helfpul for managing the cached segment used by {@link #hasNext()}.
181      *
182      * @return the segment to be returned by {@link #next()}
183      * @throws ConcurrentModificationException if the underlying path was modified
184      * since this iterator was created.
185      */
186     @NonNull
nextInternal()187     private @Verb int nextInternal() {
188         if (mDone) {
189             return VERB_DONE;
190         }
191         if (mPathGenerationId != mPath.getGenerationId()) {
192             throw new ConcurrentModificationException(
193                     "Iterator cannot be used on modified Path");
194         }
195         @Verb int verb = IS_DALVIK
196             ? nNext(mNativeIterator, mPointsAddress) : nNextHost(mNativeIterator, mPointsArray);
197         if (verb == VERB_DONE) {
198             mDone = true;
199         }
200         return verb;
201     }
202 
203     /**
204      * Returns the next {@link Segment} element in this iterator.
205      *
206      * There are two versions of <code>next()</code>. This version is slightly more
207      * expensive at runtime, since it allocates a new {@link Segment} object with
208      * every call. The other version, {@link #next(float[], int)} requires no such allocation, but
209      * requires a little more manual effort to use.
210      *
211      * @return the next segment in this iterator
212      * @throws ConcurrentModificationException if the underlying path was modified
213      * since this iterator was created.
214      */
215     @NonNull
216     @Override
next()217     public Segment next() {
218         @Verb int returnVerb = getReturnVerb(mCachedVerb);
219         mCachedVerb = -1;
220         float conicWeight = 0f;
221         if (returnVerb == VERB_CONIC) {
222             conicWeight = mPointsArray[6];
223         }
224         float[] returnPoints = new float[8];
225         System.arraycopy(mPointsArray, 0, returnPoints, 0, POINT_ARRAY_SIZE);
226         return new Segment(returnVerb, returnPoints, conicWeight);
227     }
228 
getReturnVerb(int cachedVerb)229     private @Verb int getReturnVerb(int cachedVerb) {
230         switch (cachedVerb) {
231             case VERB_MOVE: return VERB_MOVE;
232             case VERB_LINE: return VERB_LINE;
233             case VERB_QUAD: return VERB_QUAD;
234             case VERB_CONIC: return VERB_CONIC;
235             case VERB_CUBIC: return VERB_CUBIC;
236             case VERB_CLOSE: return VERB_CLOSE;
237             case VERB_DONE: return VERB_DONE;
238         }
239         return nextInternal();
240     }
241 
242     /**
243      * This class holds the data for a given segment in a path, as returned by
244      * {@link #next()}.
245      */
246     public static class Segment {
247         private final @Verb int mVerb;
248         private final float[] mPoints;
249         private final float mConicWeight;
250 
251         /**
252          * The operation for this segment.
253          *
254          * @return the verb which indicates the operation happening in this segment
255          */
256         @NonNull
getVerb()257         public @Verb int getVerb() {
258             return mVerb;
259         }
260 
261         /**
262          * The point data for this segment.
263          *
264          * Each two floats represent the data for a single point of that operation.
265          * The number of pairs of floats supplied in the resulting array depends on the verb:
266          * <ul>
267          * <li>{@link #VERB_MOVE}: 1 pair (indices 0 to 1)</li>
268          * <li>{@link #VERB_LINE}: 2 pairs (indices 0 to 3)</li>
269          * <li>{@link #VERB_QUAD}: 3 pairs (indices 0 to 5)</li>
270          * <li>{@link #VERB_CONIC}: 4 pairs (indices 0 to 7), the last pair contains the
271          * conic weight twice</li>
272          * <li>{@link #VERB_CUBIC}: 4 pairs (indices 0 to 7)</li>
273          * <li>{@link #VERB_CLOSE}: 0 pairs</li>
274          * <li>{@link #VERB_DONE}: 0 pairs</li>
275          * </ul>
276          * @return the point data for this segment
277          */
278         @NonNull
getPoints()279         public float[] getPoints() {
280             return mPoints;
281         }
282 
283         /**
284          * The weight for the conic operation in this segment. If the verb in this segment
285          * is not equal to {@link #VERB_CONIC}, the weight value is undefined.
286          *
287          * @see Path#conicTo(float, float, float, float, float)
288          * @return the weight for the conic operation in this segment, if any
289          */
getConicWeight()290         public float getConicWeight() {
291             return mConicWeight;
292         }
293 
Segment(@onNull @erb int verb, @NonNull float[] points, float conicWeight)294         Segment(@NonNull @Verb int verb, @NonNull float[] points, float conicWeight) {
295             mVerb = verb;
296             mPoints = points;
297             mConicWeight = conicWeight;
298         }
299     }
300 
301     // ------------------ Regular JNI ------------------------
302 
nCreate(long nativePath)303     private static native long nCreate(long nativePath);
nGetFinalizer()304     private static native long nGetFinalizer();
305 
306     /* nNextHost should be used for host runtimes, e.g. LayoutLib */
nNextHost(long nativeIterator, float[] points)307     private static native int nNextHost(long nativeIterator, float[] points);
308 
309     // ------------------ Critical JNI ------------------------
310 
311     @CriticalNative
nNext(long nativeIterator, long pointsAddress)312     private static native int nNext(long nativeIterator, long pointsAddress);
313 
314     @CriticalNative
nPeek(long nativeIterator)315     private static native int nPeek(long nativeIterator);
316 }
317