1 /*
<lambda>null2  * Copyright 2019 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 androidx.compose.ui.graphics.vector
18 
19 import androidx.compose.runtime.Immutable
20 
21 /**
22  * Class representing a singular path command in a vector.
23  *
24  * @property isCurve whether this command is a curve command
25  * @property isQuad whether this command is a quad command
26  */
27 @Immutable
28 sealed class PathNode(val isCurve: Boolean = false, val isQuad: Boolean = false) {
29     // RelativeClose and Close are considered the same internally, so we represent both with Close
30     // for simplicity and to make equals comparisons robust.
31     @Immutable object Close : PathNode()
32 
33     @Immutable
34     @Suppress("DataClassDefinition")
35     data class RelativeMoveTo(val dx: Float, val dy: Float) : PathNode()
36 
37     @Immutable
38     @Suppress("DataClassDefinition")
39     data class MoveTo(val x: Float, val y: Float) : PathNode()
40 
41     @Immutable
42     @Suppress("DataClassDefinition")
43     data class RelativeLineTo(val dx: Float, val dy: Float) : PathNode()
44 
45     @Immutable
46     @Suppress("DataClassDefinition")
47     data class LineTo(val x: Float, val y: Float) : PathNode()
48 
49     @Immutable
50     @Suppress("DataClassDefinition")
51     data class RelativeHorizontalTo(val dx: Float) : PathNode()
52 
53     @Immutable @Suppress("DataClassDefinition") data class HorizontalTo(val x: Float) : PathNode()
54 
55     @Immutable
56     @Suppress("DataClassDefinition")
57     data class RelativeVerticalTo(val dy: Float) : PathNode()
58 
59     @Immutable @Suppress("DataClassDefinition") data class VerticalTo(val y: Float) : PathNode()
60 
61     @Immutable
62     @Suppress("DataClassDefinition")
63     data class RelativeCurveTo(
64         val dx1: Float,
65         val dy1: Float,
66         val dx2: Float,
67         val dy2: Float,
68         val dx3: Float,
69         val dy3: Float
70     ) : PathNode(isCurve = true)
71 
72     @Immutable
73     @Suppress("DataClassDefinition")
74     data class CurveTo(
75         val x1: Float,
76         val y1: Float,
77         val x2: Float,
78         val y2: Float,
79         val x3: Float,
80         val y3: Float
81     ) : PathNode(isCurve = true)
82 
83     @Immutable
84     @Suppress("DataClassDefinition")
85     data class RelativeReflectiveCurveTo(
86         val dx1: Float,
87         val dy1: Float,
88         val dx2: Float,
89         val dy2: Float
90     ) : PathNode(isCurve = true)
91 
92     @Immutable
93     @Suppress("DataClassDefinition")
94     data class ReflectiveCurveTo(val x1: Float, val y1: Float, val x2: Float, val y2: Float) :
95         PathNode(isCurve = true)
96 
97     @Immutable
98     @Suppress("DataClassDefinition")
99     data class RelativeQuadTo(val dx1: Float, val dy1: Float, val dx2: Float, val dy2: Float) :
100         PathNode(isQuad = true)
101 
102     @Immutable
103     @Suppress("DataClassDefinition")
104     data class QuadTo(val x1: Float, val y1: Float, val x2: Float, val y2: Float) :
105         PathNode(isQuad = true)
106 
107     @Immutable
108     @Suppress("DataClassDefinition")
109     data class RelativeReflectiveQuadTo(val dx: Float, val dy: Float) : PathNode(isQuad = true)
110 
111     @Immutable
112     @Suppress("DataClassDefinition")
113     data class ReflectiveQuadTo(val x: Float, val y: Float) : PathNode(isQuad = true)
114 
115     @Immutable
116     @Suppress("DataClassDefinition")
117     data class RelativeArcTo(
118         val horizontalEllipseRadius: Float,
119         val verticalEllipseRadius: Float,
120         val theta: Float,
121         val isMoreThanHalf: Boolean,
122         val isPositiveArc: Boolean,
123         val arcStartDx: Float,
124         val arcStartDy: Float
125     ) : PathNode()
126 
127     @Immutable
128     @Suppress("DataClassDefinition")
129     data class ArcTo(
130         val horizontalEllipseRadius: Float,
131         val verticalEllipseRadius: Float,
132         val theta: Float,
133         val isMoreThanHalf: Boolean,
134         val isPositiveArc: Boolean,
135         val arcStartX: Float,
136         val arcStartY: Float
137     ) : PathNode()
138 }
139 
140 /**
141  * Adds the corresponding [PathNode] for the given character key, if it exists, to [nodes]. If the
142  * key is unknown then [IllegalArgumentException] is thrown
143  *
144  * @throws IllegalArgumentException
145  */
addPathNodesnull146 internal fun Char.addPathNodes(nodes: ArrayList<PathNode>, args: FloatArray, count: Int) {
147     when (this) {
148         RelativeCloseKey,
149         CloseKey -> nodes.add(PathNode.Close)
150         RelativeMoveToKey -> pathRelativeMoveNodeFromArgs(nodes, args, count)
151         MoveToKey -> pathMoveNodeFromArgs(nodes, args, count)
152         RelativeLineToKey ->
153             pathNodesFromArgs(nodes, args, count, NUM_LINE_TO_ARGS) { array, start ->
154                 PathNode.RelativeLineTo(dx = array[start], dy = array[start + 1])
155             }
156         LineToKey ->
157             pathNodesFromArgs(nodes, args, count, NUM_LINE_TO_ARGS) { array, start ->
158                 PathNode.LineTo(x = array[start], y = array[start + 1])
159             }
160         RelativeHorizontalToKey ->
161             pathNodesFromArgs(nodes, args, count, NUM_HORIZONTAL_TO_ARGS) { array, start ->
162                 PathNode.RelativeHorizontalTo(dx = array[start])
163             }
164         HorizontalToKey ->
165             pathNodesFromArgs(nodes, args, count, NUM_HORIZONTAL_TO_ARGS) { array, start ->
166                 PathNode.HorizontalTo(x = array[start])
167             }
168         RelativeVerticalToKey ->
169             pathNodesFromArgs(nodes, args, count, NUM_VERTICAL_TO_ARGS) { array, start ->
170                 PathNode.RelativeVerticalTo(dy = array[start])
171             }
172         VerticalToKey ->
173             pathNodesFromArgs(nodes, args, count, NUM_VERTICAL_TO_ARGS) { array, start ->
174                 PathNode.VerticalTo(y = array[start])
175             }
176         RelativeCurveToKey ->
177             pathNodesFromArgs(nodes, args, count, NUM_CURVE_TO_ARGS) { array, start ->
178                 PathNode.RelativeCurveTo(
179                     dx1 = array[start],
180                     dy1 = array[start + 1],
181                     dx2 = array[start + 2],
182                     dy2 = array[start + 3],
183                     dx3 = array[start + 4],
184                     dy3 = array[start + 5]
185                 )
186             }
187         CurveToKey ->
188             pathNodesFromArgs(nodes, args, count, NUM_CURVE_TO_ARGS) { array, start ->
189                 PathNode.CurveTo(
190                     x1 = array[start],
191                     y1 = array[start + 1],
192                     x2 = array[start + 2],
193                     y2 = array[start + 3],
194                     x3 = array[start + 4],
195                     y3 = array[start + 5]
196                 )
197             }
198         RelativeReflectiveCurveToKey ->
199             pathNodesFromArgs(nodes, args, count, NUM_REFLECTIVE_CURVE_TO_ARGS) { array, start ->
200                 PathNode.RelativeReflectiveCurveTo(
201                     dx1 = array[start],
202                     dy1 = array[start + 1],
203                     dx2 = array[start + 2],
204                     dy2 = array[start + 3]
205                 )
206             }
207         ReflectiveCurveToKey ->
208             pathNodesFromArgs(nodes, args, count, NUM_REFLECTIVE_CURVE_TO_ARGS) { array, start ->
209                 PathNode.ReflectiveCurveTo(
210                     x1 = array[start],
211                     y1 = array[start + 1],
212                     x2 = array[start + 2],
213                     y2 = array[start + 3]
214                 )
215             }
216         RelativeQuadToKey ->
217             pathNodesFromArgs(nodes, args, count, NUM_QUAD_TO_ARGS) { array, start ->
218                 PathNode.RelativeQuadTo(
219                     dx1 = array[start],
220                     dy1 = array[start + 1],
221                     dx2 = array[start + 2],
222                     dy2 = array[start + 3]
223                 )
224             }
225         QuadToKey ->
226             pathNodesFromArgs(nodes, args, count, NUM_QUAD_TO_ARGS) { array, start ->
227                 PathNode.QuadTo(
228                     x1 = array[start],
229                     y1 = array[start + 1],
230                     x2 = array[start + 2],
231                     y2 = array[start + 3]
232                 )
233             }
234         RelativeReflectiveQuadToKey ->
235             pathNodesFromArgs(nodes, args, count, NUM_REFLECTIVE_QUAD_TO_ARGS) { array, start ->
236                 PathNode.RelativeReflectiveQuadTo(dx = array[start], dy = array[start + 1])
237             }
238         ReflectiveQuadToKey ->
239             pathNodesFromArgs(nodes, args, count, NUM_REFLECTIVE_QUAD_TO_ARGS) { array, start ->
240                 PathNode.ReflectiveQuadTo(x = array[start], y = array[start + 1])
241             }
242         RelativeArcToKey ->
243             pathNodesFromArgs(nodes, args, count, NUM_ARC_TO_ARGS) { array, start ->
244                 PathNode.RelativeArcTo(
245                     horizontalEllipseRadius = array[start],
246                     verticalEllipseRadius = array[start + 1],
247                     theta = array[start + 2],
248                     isMoreThanHalf = array[start + 3].compareTo(0.0f) != 0,
249                     isPositiveArc = array[start + 4].compareTo(0.0f) != 0,
250                     arcStartDx = array[start + 5],
251                     arcStartDy = array[start + 6]
252                 )
253             }
254         ArcToKey ->
255             pathNodesFromArgs(nodes, args, count, NUM_ARC_TO_ARGS) { array, start ->
256                 PathNode.ArcTo(
257                     horizontalEllipseRadius = array[start],
258                     verticalEllipseRadius = array[start + 1],
259                     theta = array[start + 2],
260                     isMoreThanHalf = array[start + 3].compareTo(0.0f) != 0,
261                     isPositiveArc = array[start + 4].compareTo(0.0f) != 0,
262                     arcStartX = array[start + 5],
263                     arcStartY = array[start + 6]
264                 )
265             }
266         else -> throw IllegalArgumentException("Unknown command for: $this")
267     }
268 }
269 
pathNodesFromArgsnull270 private inline fun pathNodesFromArgs(
271     nodes: MutableList<PathNode>,
272     args: FloatArray,
273     count: Int,
274     numArgs: Int,
275     crossinline nodeFor: (subArray: FloatArray, start: Int) -> PathNode
276 ) {
277     val end = count - numArgs
278     var index = 0
279     while (index <= end) {
280         nodes.add(nodeFor(args, index))
281         index += numArgs
282     }
283 }
284 
285 // According to the spec, if a MoveTo is followed by multiple pairs of coordinates,
286 // the subsequent pairs are treated as implicit corresponding LineTo commands.
pathMoveNodeFromArgsnull287 private fun pathMoveNodeFromArgs(nodes: MutableList<PathNode>, args: FloatArray, count: Int) {
288     val end = count - NUM_MOVE_TO_ARGS
289     if (end >= 0) {
290         nodes.add(PathNode.MoveTo(args[0], args[1]))
291         var index = NUM_MOVE_TO_ARGS
292         while (index <= end) {
293             nodes.add(PathNode.LineTo(args[index], args[index + 1]))
294             index += NUM_MOVE_TO_ARGS
295         }
296     }
297 }
298 
299 // According to the spec, if a RelativeMoveTo is followed by multiple pairs of coordinates,
300 // the subsequent pairs are treated as implicit corresponding RelativeLineTo commands.
pathRelativeMoveNodeFromArgsnull301 private fun pathRelativeMoveNodeFromArgs(
302     nodes: MutableList<PathNode>,
303     args: FloatArray,
304     count: Int
305 ) {
306     val end = count - NUM_MOVE_TO_ARGS
307     if (end >= 0) {
308         nodes.add(PathNode.RelativeMoveTo(args[0], args[1]))
309         var index = NUM_MOVE_TO_ARGS
310         while (index <= end) {
311             nodes.add(PathNode.RelativeLineTo(args[index], args[index + 1]))
312             index += NUM_MOVE_TO_ARGS
313         }
314     }
315 }
316 
317 /** Constants used by [Char.addPathNodes] for creating [PathNode]s from parsed paths. */
318 private const val RelativeCloseKey = 'z'
319 private const val CloseKey = 'Z'
320 private const val RelativeMoveToKey = 'm'
321 private const val MoveToKey = 'M'
322 private const val RelativeLineToKey = 'l'
323 private const val LineToKey = 'L'
324 private const val RelativeHorizontalToKey = 'h'
325 private const val HorizontalToKey = 'H'
326 private const val RelativeVerticalToKey = 'v'
327 private const val VerticalToKey = 'V'
328 private const val RelativeCurveToKey = 'c'
329 private const val CurveToKey = 'C'
330 private const val RelativeReflectiveCurveToKey = 's'
331 private const val ReflectiveCurveToKey = 'S'
332 private const val RelativeQuadToKey = 'q'
333 private const val QuadToKey = 'Q'
334 private const val RelativeReflectiveQuadToKey = 't'
335 private const val ReflectiveQuadToKey = 'T'
336 private const val RelativeArcToKey = 'a'
337 private const val ArcToKey = 'A'
338 
339 /**
340  * Constants for the number of expected arguments for a given node. If the number of received
341  * arguments is a multiple of these, the excess will be converted into additional path nodes.
342  */
343 private const val NUM_MOVE_TO_ARGS = 2
344 private const val NUM_LINE_TO_ARGS = 2
345 private const val NUM_HORIZONTAL_TO_ARGS = 1
346 private const val NUM_VERTICAL_TO_ARGS = 1
347 private const val NUM_CURVE_TO_ARGS = 6
348 private const val NUM_REFLECTIVE_CURVE_TO_ARGS = 4
349 private const val NUM_QUAD_TO_ARGS = 4
350 private const val NUM_REFLECTIVE_QUAD_TO_ARGS = 2
351 private const val NUM_ARC_TO_ARGS = 7
352