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