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