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