1 /* 2 * Copyright 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 @file:JvmName("PathUtilities") 17 18 package androidx.graphics.path 19 20 import android.graphics.Path 21 import android.os.Build 22 23 /** 24 * A path iterator can be used to iterate over all the [segments][PathSegment] that make up a path. 25 * Those segments may in turn define multiple contours inside the path. Conic segments are by 26 * default evaluated as approximated quadratic segments. To preserve conic segments as conics, set 27 * [conicEvaluation] to [AsConic][ConicEvaluation.AsConic]. The error of the approximation is 28 * controlled by [tolerance]. 29 * 30 * [PathIterator] objects are created implicitly through a given [Path] object; to create a 31 * [PathIterator], call one of the two [Path.iterator] extension functions. 32 */ 33 @Suppress("NotCloseable") 34 class PathIterator 35 constructor( 36 val path: Path, 37 val conicEvaluation: ConicEvaluation = ConicEvaluation.AsQuadratics, 38 val tolerance: Float = 0.25f 39 ) : Iterator<PathSegment> { 40 41 private val implementation: PathIteratorImpl = 42 when { 43 Build.VERSION.SDK_INT >= 34 -> PathIteratorApi34Impl(path, conicEvaluation, tolerance) 44 else -> PathIteratorPreApi34Impl(path, conicEvaluation, tolerance) 45 } 46 47 enum class ConicEvaluation { 48 /** Conic segments are returned as conic segments. */ 49 AsConic, 50 51 /** 52 * Conic segments are returned as quadratic approximations. The quality of the approximation 53 * is defined by a tolerance value. 54 */ 55 AsQuadratics 56 } 57 58 /** 59 * Returns the number of verbs present in this iterator, i.e. the number of calls to [next] 60 * required to complete the iteration. 61 * 62 * By default, [calculateSize] returns the true number of operations in the iterator. Deriving 63 * this result requires converting any conics to quadratics, if [conicEvaluation] is set to 64 * [ConicEvaluation.AsQuadratics], which takes extra processing time. Set 65 * [includeConvertedConics] to false if an approximate size, not including conic conversion, is 66 * sufficient. 67 * 68 * @param includeConvertedConics The returned size includes any required conic conversions. 69 * Default is true, so it will return the exact size, at the cost of iterating through all 70 * elements and converting any conics as appropriate. Set to false to save on processing, at 71 * the cost of a less exact result. 72 */ calculateSizenull73 fun calculateSize(includeConvertedConics: Boolean = true) = 74 implementation.calculateSize(includeConvertedConics) 75 76 /** Returns `true` if the iteration has more elements. */ 77 override fun hasNext(): Boolean = implementation.hasNext() 78 79 /** 80 * Returns the type of the current segment in the iteration, or [Done][PathSegment.Type.Done] if 81 * the iteration is finished. 82 */ 83 fun peek() = implementation.peek() 84 85 /** 86 * Returns the [type][PathSegment.Type] of the next [path segment][PathSegment] in the iteration 87 * and fills [points] with the points specific to the segment type. Each pair of floats in the 88 * [points] array represents a point for the given segment. The number of pairs of floats 89 * depends on the [PathSegment.Type]: 90 * - [Move][PathSegment.Type.Move]: 1 pair (indices 0 to 1) 91 * - [Line][PathSegment.Type.Line]: 2 pairs (indices 0 to 3) 92 * - [Quadratic][PathSegment.Type.Quadratic]: 3 pairs (indices 0 to 5) 93 * - [Conic][PathSegment.Type.Conic]: 3 pairs (indices 0 to 5), and the conic 94 * [weight][PathSegment.weight] at index 6. The value of the last float is undefined 95 * - [Cubic][PathSegment.Type.Cubic]: 4 pairs (indices 0 to 7) 96 * - [Close][PathSegment.Type.Close]: 0 pair 97 * - [Done][PathSegment.Type.Done]: 0 pair This method does not allocate any memory. 98 * 99 * @param points A [FloatArray] large enough to hold 8 floats starting at [offset], throws an 100 * [IllegalStateException] otherwise. 101 * @param offset Offset in [points] where to store the result 102 */ 103 @JvmOverloads 104 fun next(points: FloatArray, offset: Int = 0): PathSegment.Type = 105 implementation.next(points, offset) 106 107 /** 108 * Returns the next [path segment][PathSegment] in the iteration, or [DoneSegment] if the 109 * iteration is finished. To save on allocations, use the alternative [next] function, which 110 * takes a [FloatArray]. 111 */ 112 override fun next(): PathSegment = implementation.next() 113 } 114 115 /** 116 * Creates a new [PathIterator] for this [path][android.graphics.Path] that evaluates conics as 117 * quadratics. To preserve conics, use the [Path.iterator] function that takes a 118 * [PathIterator.ConicEvaluation] parameter. 119 */ 120 operator fun Path.iterator() = PathIterator(this) 121 122 /** 123 * Creates a new [PathIterator] for this [path][android.graphics.Path]. To preserve conics as conics 124 * (not convert them to quadratics), set [conicEvaluation] to 125 * [PathIterator.ConicEvaluation.AsConic]. 126 */ 127 fun Path.iterator(conicEvaluation: PathIterator.ConicEvaluation, tolerance: Float = 0.25f) = 128 PathIterator(this, conicEvaluation, tolerance) 129