1 /*
2  * Copyright 2024 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 @file:Suppress("NOTHING_TO_INLINE", "KotlinRedundantDiagnosticSuppress")
18 
19 package androidx.compose.ui.graphics
20 
21 import androidx.annotation.RestrictTo
22 import androidx.collection.FloatFloatPair
23 import androidx.compose.ui.util.fastCbrt
24 import androidx.compose.ui.util.fastCoerceIn
25 import androidx.compose.ui.util.fastMaxOf
26 import androidx.compose.ui.util.fastMinOf
27 import androidx.compose.ui.util.lerp
28 import kotlin.math.PI
29 import kotlin.math.abs
30 import kotlin.math.acos
31 import kotlin.math.cos
32 import kotlin.math.max
33 import kotlin.math.min
34 import kotlin.math.sign
35 import kotlin.math.sqrt
36 
37 private const val Tau = PI * 2.0
38 private const val Epsilon = 1e-7
39 // We use a fairly high epsilon here because it's post double->float conversion
40 // and because we use a fast approximation of cbrt(). The epsilon we use here is
41 // slightly larger than the max error of fastCbrt() in the -1f..1f range
42 // (8.3446500e-7f) but smaller than 1.0f.ulp * 10.
43 private const val FloatEpsilon = 1.05e-6f
44 
45 /**
46  * Evaluate the specified [segment] at position [t] and returns the X coordinate of the segment's
47  * curve at that position.
48  */
evaluateXnull49 private fun evaluateX(segment: PathSegment, t: Float): Float {
50     val points = segment.points
51 
52     return when (segment.type) {
53         PathSegment.Type.Move -> points[0]
54         PathSegment.Type.Line -> {
55             evaluateLine(points[0], points[2], t)
56         }
57         PathSegment.Type.Quadratic -> {
58             evaluateQuadratic(points[0], points[2], points[4], t)
59         }
60         PathSegment.Type.Cubic -> {
61             evaluateCubic(points[0], points[2], points[4], points[6], t)
62         }
63         // Conic (converted to Cubic), Close, Done
64         else -> Float.NaN
65     }
66 }
67 
68 /**
69  * Evaluate the specified [segment] at position [t] and returns the Y coordinate of the segment's
70  * curve at that position.
71  */
72 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
evaluateYnull73 fun evaluateY(segment: PathSegment, t: Float): Float {
74     val points = segment.points
75 
76     return when (segment.type) {
77         PathSegment.Type.Move -> points[1]
78         PathSegment.Type.Line -> {
79             evaluateLine(points[1], points[3], t)
80         }
81         PathSegment.Type.Quadratic -> {
82             evaluateQuadratic(points[1], points[3], points[5], t)
83         }
84         PathSegment.Type.Cubic -> {
85             evaluateCubic(points[1], points[3], points[5], points[7], t)
86         }
87         // Conic (converted to Cubic), Close, Done
88         else -> Float.NaN
89     }
90 }
91 
evaluateLinenull92 private fun evaluateLine(p0y: Float, p1y: Float, t: Float) = (p1y - p0y) * t + p0y
93 
94 private fun evaluateQuadratic(p0: Float, p1: Float, p2: Float, t: Float): Float {
95     val by = 2.0f * (p1 - p0)
96     val ay = p2 - 2.0f * p1 + p0
97     return (ay * t + by) * t + p0
98 }
99 
evaluateCubicnull100 private fun evaluateCubic(p0: Float, p1: Float, p2: Float, p3: Float, t: Float): Float {
101     val a = p3 + 3.0f * (p1 - p2) - p0
102     val b = 3.0f * (p2 - 2.0f * p1 + p0)
103     val c = 3.0f * (p1 - p0)
104     return ((a * t + b) * t + c) * t + p0
105 }
106 
107 /**
108  * Evaluates a cubic Bézier curve at position [t] along the curve. The curve is defined by the start
109  * point (0, 0), the end point (0, 0) and two control points of respective coordinates [p1] and
110  * [p2].
111  */
112 @Suppress("UnnecessaryVariable")
113 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
evaluateCubicnull114 fun evaluateCubic(p1: Float, p2: Float, t: Float): Float {
115     val a = 1.0f / 3.0f + (p1 - p2)
116     val b = (p2 - 2.0f * p1)
117     val c = p1
118     return 3.0f * ((a * t + b) * t + c) * t
119 }
120 
121 /**
122  * Finds the first real root of the specified [segment]. If no root can be found, this method
123  * returns [Float.NaN].
124  */
125 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
findFirstRootnull126 fun findFirstRoot(segment: PathSegment, fraction: Float): Float {
127     val points = segment.points
128     return when (segment.type) {
129         PathSegment.Type.Move -> Float.NaN
130         PathSegment.Type.Line -> {
131             findFirstLineRoot(
132                 points[0] - fraction,
133                 points[2] - fraction,
134             )
135         }
136         PathSegment.Type.Quadratic ->
137             findFirstQuadraticRoot(points[0] - fraction, points[2] - fraction, points[4] - fraction)
138 
139         // We convert all conics to cubics, won't happen
140         PathSegment.Type.Conic -> Float.NaN
141         PathSegment.Type.Cubic ->
142             findFirstCubicRoot(
143                 points[0] - fraction,
144                 points[2] - fraction,
145                 points[4] - fraction,
146                 points[6] - fraction
147             )
148         PathSegment.Type.Close -> Float.NaN
149         PathSegment.Type.Done -> Float.NaN
150     }
151 }
152 
findFirstLineRootnull153 private inline fun findFirstLineRoot(p0: Float, p1: Float) =
154     clampValidRootInUnitRange(-p0 / (p1 - p0))
155 
156 /**
157  * Finds the first real root of a quadratic Bézier curve:
158  * - [p0]: coordinate of the start point
159  * - [p1]: coordinate of the control point
160  * - [p2]: coordinate of the end point
161  *
162  * If no root can be found, this method returns [Float.NaN].
163  */
164 private fun findFirstQuadraticRoot(p0: Float, p1: Float, p2: Float): Float {
165     val a = p0.toDouble()
166     val b = p1.toDouble()
167     val c = p2.toDouble()
168     val d = a - 2.0 * b + c
169 
170     if (d != 0.0) {
171         val v1 = -sqrt(b * b - a * c)
172         val v2 = -a + b
173 
174         val root = clampValidRootInUnitRange((-(v1 + v2) / d).toFloat())
175         if (!root.isNaN()) return root
176 
177         return clampValidRootInUnitRange(((v1 - v2) / d).toFloat())
178     } else if (b != c) {
179         return clampValidRootInUnitRange(((2.0 * b - c) / (2.0 * b - 2.0 * c)).toFloat())
180     }
181 
182     return Float.NaN
183 }
184 
185 /**
186  * Finds the first real root of a cubic Bézier curve:
187  * - [p0]: coordinate of the start point
188  * - [p1]: coordinate of the first control point
189  * - [p2]: coordinate of the second control point
190  * - [p3]: coordinate of the end point
191  *
192  * If no root can be found, this method returns [Float.NaN].
193  */
194 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
findFirstCubicRootnull195 fun findFirstCubicRoot(p0: Float, p1: Float, p2: Float, p3: Float): Float {
196     // This function implements Cardano's algorithm as described in "A Primer on Bézier Curves":
197     // https://pomax.github.io/bezierinfo/#yforx
198     //
199     // The math used to find the roots is explained in "Solving the Cubic Equation":
200     // http://www.trans4mind.com/personal_development/mathematics/polynomials/cubicAlgebra.htm
201 
202     var a = 3.0 * (p0 - 2.0 * p1 + p2)
203     var b = 3.0 * (p1 - p0)
204     var c = p0.toDouble()
205     val d = -p0 + 3.0 * (p1 - p2) + p3
206 
207     // Not a cubic
208     if (d.closeTo(0.0)) {
209         // Not a quadratic
210         if (a.closeTo(0.0)) {
211             // No solutions
212             if (b.closeTo(0.0)) {
213                 return Float.NaN
214             }
215             return clampValidRootInUnitRange((-c / b).toFloat())
216         } else {
217             val q = sqrt(b * b - 4.0 * a * c)
218             val a2 = 2.0 * a
219 
220             val root = clampValidRootInUnitRange(((q - b) / a2).toFloat())
221             if (!root.isNaN()) return root
222 
223             return clampValidRootInUnitRange(((-b - q) / a2).toFloat())
224         }
225     }
226 
227     a /= d
228     b /= d
229     c /= d
230 
231     val o3 = (3.0 * b - a * a) / 9.0
232     val q2 = (2.0 * a * a * a - 9.0 * a * b + 27.0 * c) / 54.0
233     val discriminant = q2 * q2 + o3 * o3 * o3
234     val a3 = a / 3.0
235 
236     if (discriminant < 0.0) {
237         val mp33 = -(o3 * o3 * o3)
238         val r = sqrt(mp33)
239         val t = -q2 / r
240         val cosPhi = t.fastCoerceIn(-1.0, 1.0)
241         val phi = acos(cosPhi)
242         val t1 = 2.0f * fastCbrt(r.toFloat())
243 
244         var root = clampValidRootInUnitRange((t1 * cos(phi / 3.0) - a3).toFloat())
245         if (!root.isNaN()) return root
246 
247         root = clampValidRootInUnitRange((t1 * cos((phi + Tau) / 3.0) - a3).toFloat())
248         if (!root.isNaN()) return root
249 
250         return clampValidRootInUnitRange((t1 * cos((phi + 2.0 * Tau) / 3.0) - a3).toFloat())
251     } else if (discriminant == 0.0) { // TODO: closeTo(0.0)?
252         val u1 = -fastCbrt(q2.toFloat())
253 
254         val root = clampValidRootInUnitRange(2.0f * u1 - a3.toFloat())
255         if (!root.isNaN()) return root
256 
257         return clampValidRootInUnitRange(-u1 - a3.toFloat())
258     }
259 
260     val sd = sqrt(discriminant)
261     val u1 = fastCbrt((-q2 + sd).toFloat())
262     val v1 = fastCbrt((q2 + sd).toFloat())
263 
264     return clampValidRootInUnitRange((u1 - v1 - a3).toFloat())
265 }
266 
267 /**
268  * Finds the real root of a line defined by the X coordinates of its start ([p0]) and end ([p1])
269  * points. The root, if any, is written in the [roots] array at [index]. Returns 1 if a root was
270  * found, 0 otherwise.
271  */
findLineRootnull272 private inline fun findLineRoot(p0: Float, p1: Float, roots: FloatArray, index: Int = 0) =
273     writeValidRootInUnitRange(-p0 / (p1 - p0), roots, index)
274 
275 /**
276  * Finds the real roots of a quadratic Bézier curve. To find the roots, only the X coordinates of
277  * the four points are required:
278  * - [p0]: x coordinate of the start point
279  * - [p1]: x coordinate of the control point
280  * - [p2]: x coordinate of the end point
281  *
282  * Any root found is written in the [roots] array, starting at [index]. The function returns the
283  * number of roots found and written to the array.
284  */
285 private fun findQuadraticRoots(
286     p0: Float,
287     p1: Float,
288     p2: Float,
289     roots: FloatArray,
290     index: Int = 0
291 ): Int {
292     val a = p0.toDouble()
293     val b = p1.toDouble()
294     val c = p2.toDouble()
295     val d = a - 2.0 * b + c
296 
297     var rootCount = 0
298 
299     if (d != 0.0) {
300         val v1 = -sqrt(b * b - a * c)
301         val v2 = -a + b
302 
303         rootCount += writeValidRootInUnitRange((-(v1 + v2) / d).toFloat(), roots, index)
304         rootCount += writeValidRootInUnitRange(((v1 - v2) / d).toFloat(), roots, index + rootCount)
305 
306         // Returns the roots sorted
307         if (rootCount > 1) {
308             val s = roots[index]
309             val t = roots[index + 1]
310             if (s > t) {
311                 roots[index] = t
312                 roots[index + 1] = s
313             } else if (s == t) {
314                 // Don't report identical roots
315                 rootCount--
316             }
317         }
318     } else if (b != c) {
319         rootCount +=
320             writeValidRootInUnitRange(((2.0 * b - c) / (2.0 * b - 2.0 * c)).toFloat(), roots, index)
321     }
322 
323     return rootCount
324 }
325 
326 /**
327  * Finds the roots of the derivative of the curve described by [segment]. The roots, if any, are
328  * written in the [roots] array starting at [index]. The function returns the number of roots founds
329  * and written into the array. The [roots] array must be able to hold at least 5 floats starting at
330  * [index].
331  */
findDerivativeRootsnull332 private fun findDerivativeRoots(
333     segment: PathSegment,
334     horizontal: Boolean,
335     roots: FloatArray,
336     index: Int
337 ): Int {
338     val offset = if (horizontal) 0 else 1
339     val points = segment.points
340     return when (segment.type) {
341         PathSegment.Type.Quadratic -> {
342             // Line derivative of a quadratic function
343             // We do the computation inline to avoid using arrays of other data
344             // structures to return the result
345             val d0 = 2 * (points[offset + 2] - points[offset + 0])
346             val d1 = 2 * (points[offset + 4] - points[offset + 2])
347             findLineRoot(d0, d1, roots, index)
348         }
349         PathSegment.Type.Cubic -> {
350             // Quadratic derivative of a cubic function
351             // We do the computation inline to avoid using arrays of other data
352             // structures to return the result
353             val d0 = 3.0f * (points[offset + 2] - points[offset + 0])
354             val d1 = 3.0f * (points[offset + 4] - points[offset + 2])
355             val d2 = 3.0f * (points[offset + 6] - points[offset + 4])
356             val count = findQuadraticRoots(d0, d1, d2, roots, index)
357 
358             // Compute the second derivative as a line
359             val dd0 = 2.0f * (d1 - d0)
360             val dd1 = 2.0f * (d2 - d1)
361             // Return the sum of the roots count
362             count + findLineRoot(dd0, dd1, roots, index + count)
363         }
364         else -> 0
365     }
366 }
367 
368 /**
369  * Computes the horizontal bounds of the specified [segment] and returns a pair of floats containing
370  * the lowest bound as the first value, and the highest bound as the second value.
371  *
372  * The [roots] array is used as a scratch array and must be able to hold at least 5 floats.
373  */
374 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
computeHorizontalBoundsnull375 fun computeHorizontalBounds(
376     segment: PathSegment,
377     roots: FloatArray,
378     index: Int = 0
379 ): FloatFloatPair {
380     val count = findDerivativeRoots(segment, true, roots, index)
381     var minX = min(segment.startX, segment.endX)
382     var maxX = max(segment.startX, segment.endX)
383 
384     for (i in 0 until count) {
385         val t = roots[i]
386         val x = evaluateX(segment, t)
387         minX = min(minX, x)
388         maxX = max(maxX, x)
389     }
390 
391     return FloatFloatPair(minX, maxX)
392 }
393 
394 /**
395  * Computes the vertical bounds of the specified [segment] and returns a pair of floats containing
396  * the lowest bound as the first value, and the highest bound as the second value.
397  *
398  * The [roots] array is used as a scratch array and must be able to hold at least 5 floats.
399  */
computeVerticalBoundsnull400 internal fun computeVerticalBounds(
401     segment: PathSegment,
402     roots: FloatArray,
403     index: Int = 0
404 ): FloatFloatPair {
405     val count = findDerivativeRoots(segment, false, roots, index)
406     var minY = min(segment.startY, segment.endY)
407     var maxY = max(segment.startY, segment.endY)
408 
409     for (i in 0 until count) {
410         val t = roots[i]
411         val x = evaluateY(segment, t)
412         minY = min(minY, x)
413         maxY = max(maxY, x)
414     }
415 
416     return FloatFloatPair(minY, maxY)
417 }
418 
419 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
computeCubicVerticalBoundsnull420 fun computeCubicVerticalBounds(
421     p0y: Float,
422     p1y: Float,
423     p2y: Float,
424     p3y: Float,
425     roots: FloatArray,
426     index: Int = 0
427 ): FloatFloatPair {
428     // Quadratic derivative of a cubic function
429     // We do the computation inline to avoid using arrays of other data
430     // structures to return the result
431     val d0 = 3.0f * (p1y - p0y)
432     val d1 = 3.0f * (p2y - p1y)
433     val d2 = 3.0f * (p3y - p2y)
434     var count = findQuadraticRoots(d0, d1, d2, roots, index)
435 
436     // Compute the second derivative as a line
437     val dd0 = 2.0f * (d1 - d0)
438     val dd1 = 2.0f * (d2 - d1)
439     count += findLineRoot(dd0, dd1, roots, index + count)
440 
441     var minY = min(p0y, p3y)
442     var maxY = max(p0y, p3y)
443 
444     for (i in 0 until count) {
445         val t = roots[i]
446         val y = evaluateCubic(p0y, p1y, p2y, p3y, t)
447         minY = min(minY, y)
448         maxY = max(maxY, y)
449     }
450 
451     return FloatFloatPair(minY, maxY)
452 }
453 
closeTonull454 internal inline fun Double.closeTo(b: Double) = abs(this - b) < Epsilon
455 
456 internal inline fun Float.closeTo(b: Float) = abs(this - b) < FloatEpsilon
457 
458 /**
459  * Returns [r] if it's in the [0..1] range, and [Float.NaN] otherwise. To account for numerical
460  * imprecision in computations, values in the [-FloatEpsilon..1+FloatEpsilon] range are considered
461  * to be in the [0..1] range and clamped appropriately.
462  */
463 private inline fun clampValidRootInUnitRange(r: Float): Float {
464     // The code below is a branchless version of:
465     // if (r < 0.0f) {
466     //     if (r >= -FloatEpsilon) 0.0f else Float.NaN
467     // } else if (r > 1.0f) {
468     //     if (r <= 1.0f + FloatEpsilon) 1.0f else Float.NaN
469     // } else {
470     //     r
471     // }
472     val s = r.fastCoerceIn(0f, 1f)
473     return if (abs(s - r) > FloatEpsilon) Float.NaN else s
474 }
475 
476 /**
477  * Writes [r] in the [roots] array at [index], if it's in the [0..1] range. To account for numerical
478  * imprecision in computations, values in the [-FloatEpsilon..1+FloatEpsilon] range are considered
479  * to be in the [0..1] range and clamped appropriately. Returns 0 if no value was written, 1
480  * otherwise.
481  */
writeValidRootInUnitRangenull482 private fun writeValidRootInUnitRange(r: Float, roots: FloatArray, index: Int): Int {
483     val v = clampValidRootInUnitRange(r)
484     roots[index] = v
485     return if (v.isNaN()) 0 else 1
486 }
487 
488 /**
489  * Computes the winding value for a position [x]/[y] and the line defined by the [points] array. The
490  * array must contain at least 4 floats defining the start and end points of the line as pairs of
491  * x/y coordinates.
492  */
lineWindingnull493 internal fun lineWinding(points: FloatArray, x: Float, y: Float): Int {
494     if (points.size < 4) return 0
495 
496     val x0 = points[0]
497     var y0 = points[1]
498     val yo = y0
499     val x1 = points[2]
500     var y1 = points[3]
501 
502     // Compute dy before we swap
503     val dy = y1 - y0
504     var direction = 1
505 
506     if (y0 > y1) {
507         y0 = y1
508         y1 = yo
509         direction = -1
510     }
511 
512     // We exclude the end point
513     if (y < y0 || y >= y1) {
514         return 0
515     }
516 
517     // TODO: check if on the curve
518 
519     val crossProduct = (x1 - x0) * (y - yo) - dy * (x - x0)
520     // The point is on the line
521     if (crossProduct == 0.0f) {
522         // TODO: check if we are on the line but exclude x1 and y1
523         direction = 0
524     } else if (crossProduct.sign.toInt() == direction) {
525         direction = 0
526     }
527 
528     return direction
529 }
530 
531 /**
532  * Returns whether the quadratic Bézier curve defined the start, control, and points [y0], [y1], and
533  * [y2] is monotonic on the Y axis.
534  */
isQuadraticMonotonicnull535 private fun isQuadraticMonotonic(y0: Float, y1: Float, y2: Float): Boolean =
536     (y0 - y1).sign + (y1 - y2).sign != 0.0f
537 
538 /**
539  * Computes the winding value for a position [x]/[y] and the quadratic Bézier curve defined by the
540  * [points] array. The array must contain at least 6 floats defining the start, control, and end
541  * points of the curve as pairs of x/y coordinates.
542  *
543  * The [tmpQuadratics] array is a scratch array used to hold temporary values and must contain at
544  * least 10 floats. Its content can be ignored after calling this function.
545  *
546  * The [tmpRoots] array is a scratch array that must contain at least 2 values. It is used to hold
547  * temporary values and its content can be ignored after calling this function.
548  */
549 internal fun quadraticWinding(
550     points: FloatArray,
551     x: Float,
552     y: Float,
553     tmpQuadratics: FloatArray,
554     tmpRoots: FloatArray
555 ): Int {
556     val y0 = points[1]
557     val y1 = points[3]
558     val y2 = points[5]
559 
560     if (isQuadraticMonotonic(y0, y1, y2)) {
561         return monotonicQuadraticWinding(points, 0, x, y, tmpRoots)
562     }
563 
564     val rootCount = quadraticToMonotonicQuadratics(points, tmpQuadratics)
565 
566     var winding = monotonicQuadraticWinding(tmpQuadratics, 0, x, y, tmpRoots)
567     if (rootCount > 0) {
568         winding += monotonicQuadraticWinding(tmpQuadratics, 4, x, y, tmpRoots)
569     }
570     return winding
571 }
572 
573 /**
574  * Computes the winding value of a _monotonic_ quadratic Bézier curve for the given [x] and [y]
575  * coordinates. The curve is defined as 6 floats in the [points] array corresponding to the start,
576  * control, and end points. The floats are stored at position [offset] in the array, meaning the
577  * array must hold at least [offset] + 6 values.
578  *
579  * The [tmpRoots] array is a scratch array that must contain at least 2 values. It is used to hold
580  * temporary values and its content can be ignored after calling this function.
581  */
monotonicQuadraticWindingnull582 private fun monotonicQuadraticWinding(
583     points: FloatArray,
584     offset: Int,
585     x: Float,
586     y: Float,
587     tmpRoots: FloatArray
588 ): Int {
589     var y0 = points[offset + 1]
590     var y2 = points[offset + 5]
591 
592     var direction = 1
593     if (y0 > y2) {
594         val swap = y2
595         y2 = y0
596         y0 = swap
597         direction = -1
598     }
599 
600     // Exclude the end point
601     if (y < y0 || y >= y2) return 0
602 
603     // TODO: check if on the curve
604 
605     y0 = points[offset + 1]
606     val y1 = points[offset + 3]
607     y2 = points[offset + 5]
608 
609     val rootCount = findQuadraticRoots(y0 - 2.0f * y1 + y2, 2.0f * (y1 - y0), y0 - y, tmpRoots)
610 
611     val xt =
612         if (rootCount == 0) {
613             points[(1 - direction) * 2]
614         } else {
615             evaluateQuadratic(points[0], points[2], points[4], tmpRoots[0])
616         }
617 
618     if (xt.closeTo(x)) {
619         if (x != points[4] || y != y2) {
620             // TODO: on the curve
621             return 0
622         }
623     }
624 
625     return if (xt < x) direction else 0
626 }
627 
628 /**
629  * Splits the specified [quadratic] Bézier curve into 1 or 2 monotonic quadratic Bézier curves. The
630  * results are stored in the [dst] array. Both the input [quadratic] and the output [dst] arrays
631  * store the curves as 3 pairs of floats defined by the start, control, and end points. In the [dst]
632  * array, successive curves share a point: the end point of the first curve is the start point of
633  * the second curve. As a result this function will output at most 10 values in the [dst] array (6
634  * floats per curve, minus 2 for a shared point).
635  *
636  * The function returns the number of splits: if 0 is returned, the [dst] array contains a single
637  * quadratic curve, if 1 is returned, the array contains 2 curves with a shared point.
638  */
quadraticToMonotonicQuadraticsnull639 private fun quadraticToMonotonicQuadratics(quadratic: FloatArray, dst: FloatArray): Int {
640     if (quadratic.size < 6) return 0
641     if (dst.size < 6) return 0
642 
643     val y0 = quadratic[1]
644     var y1 = quadratic[3]
645     val y2 = quadratic[5]
646 
647     if (!isQuadraticMonotonic(y0, y1, y2)) {
648         val t = unitDivide(y0 - y1, y0 - y1 - y1 + y2)
649         if (!t.isNaN()) {
650             splitQuadraticAt(quadratic, dst, t)
651             return 1
652         }
653         // force the curve to be monotonic since the division above failed
654         y1 = if (abs(y0 - y1) < abs(y1 - y2)) y0 else y2
655     }
656 
657     quadratic.copyInto(dst, 0, 0, 6)
658     dst[3] = y1
659 
660     return 0
661 }
662 
663 /**
664  * Splits the specified [src] quadratic Bézier curve into two quadratic Bézier curves at position
665  * [t] (in the range 0..1), and stores the results in [dst]. The [dst] array must hold at least 10
666  * floats. See [quadraticToMonotonicQuadratics] for more details.
667  */
splitQuadraticAtnull668 private fun splitQuadraticAt(src: FloatArray, dst: FloatArray, t: Float) {
669     if (src.size < 6) return
670     if (dst.size < 10) return
671 
672     val p0x = src[0]
673     val p0y = src[1]
674     val p1x = src[2]
675     val p1y = src[3]
676     val p2x = src[4]
677     val p2y = src[5]
678 
679     val abx = lerp(p0x, p1x, t)
680     val aby = lerp(p0y, p1y, t)
681 
682     dst[0] = p0x
683     dst[1] = p0y
684     dst[2] = abx
685     dst[3] = aby
686 
687     val bcx = lerp(p1x, p2x, t)
688     val bcy = lerp(p1y, p2y, t)
689 
690     val abcx = lerp(abx, bcx, t)
691     val abcy = lerp(aby, bcy, t)
692 
693     dst[4] = abcx
694     dst[5] = abcy
695     dst[6] = bcx
696     dst[7] = bcy
697     dst[8] = p2x
698     dst[9] = p2y
699 }
700 
701 /**
702  * Performs the division [x]/[y] and returns the result. If the division is invalid, for instance if
703  * it would leads to [Float.POSITIVE_INFINITY] or if it underflows, this function returns
704  * [Float.NaN].
705  */
unitDividenull706 private fun unitDivide(x: Float, y: Float): Float {
707     var n = x
708     var d = y
709 
710     if (n < 0) {
711         n = -n
712         d = -d
713     }
714 
715     if (d == 0.0f || n == 0.0f || n >= d) {
716         return Float.NaN
717     }
718 
719     val r = n / d
720     if (r == 0.0f) {
721         return Float.NaN
722     }
723 
724     return r
725 }
726 
727 /**
728  * Computes the winding value for a position [x]/[y] and the cubic Bézier curve defined by the
729  * [points] array. The array must contain at least 8 floats defining the start, 2 control, and end
730  * points of the curve as pairs of x/y coordinates.
731  *
732  * The [tmpCubics] array is a scratch array used to hold temporary values and must contain at least
733  * 20 floats. Its content can be ignored after calling this function.
734  *
735  * The [tmpRoots] array is a scratch array that must contain at least 2 values. It is used to hold
736  * temporary values and its content can be ignored after calling this function.
737  */
cubicWindingnull738 internal fun cubicWinding(
739     points: FloatArray,
740     x: Float,
741     y: Float,
742     tmpCubics: FloatArray,
743     tmpRoots: FloatArray
744 ): Int {
745     val splits = cubicToMonotonicCubics(points, tmpCubics, tmpRoots)
746 
747     var winding = 0
748     for (i in 0..splits) {
749         winding += monotonicCubicWinding(tmpCubics, i * 3 * 2, x, y)
750     }
751     return winding
752 }
753 
754 /**
755  * Computes the winding value for a position [x]/[y] and the cubic Bézier curve defined by the
756  * [points] array, starting at the specified [offset]. The array must contain at least 10 floats
757  * after [offset] defining the start, control, and end points of the curve as pairs of x/y
758  * coordinates.
759  */
monotonicCubicWindingnull760 private fun monotonicCubicWinding(points: FloatArray, offset: Int, x: Float, y: Float): Int {
761     var y0 = points[offset + 1]
762     var y3 = points[offset + 7]
763 
764     var direction = 1
765     if (y0 > y3) {
766         val swap = y3
767         y3 = y0
768         y0 = swap
769         direction = -1
770     }
771 
772     // Exclude the end point
773     if (y < y0 || y >= y3) return 0
774 
775     // TODO: check if on the curve
776 
777     val x0 = points[offset + 0]
778     val x1 = points[offset + 2]
779     val x2 = points[offset + 4]
780     val x3 = points[offset + 6]
781 
782     // Reject if outside of the bounds
783     val min = fastMinOf(x0, x1, x2, x3)
784     if (x < min) return 0
785 
786     val max = fastMaxOf(x0, x1, x2, x3)
787     if (x > max) return direction
788 
789     // Re-fetch y0 and y3 since we may have swapped them
790     y0 = points[offset + 1]
791     val y1 = points[offset + 3]
792     val y2 = points[offset + 5]
793     y3 = points[offset + 7]
794 
795     val root =
796         findFirstCubicRoot(
797             y0 - y,
798             y1 - y,
799             y2 - y,
800             y3 - y,
801         )
802     if (root.isNaN()) return 0
803 
804     val xt = evaluateCubic(x0, x1, x2, x3, root)
805     if (xt.closeTo(x)) {
806         if (x != x3 || y != y3) {
807             // TODO: on the curve
808             return 0
809         }
810     }
811 
812     return if (xt < x) direction else 0
813 }
814 
815 /**
816  * Splits the specified [cubic] Bézier curve into 1, 2, or 3 monotonic cubic Bézier curves. The
817  * results are stored in the [dst] array. Both the input [cubic] and the output [dst] arrays store
818  * the curves as 4 pairs of floats defined by the start, 2 control, and end points. In the [dst]
819  * array, successive curves share a point: the end point of the first curve is the start point of
820  * the second curve. As a result this function will output at most 20 values in the [dst] array (8
821  * floats per curve, minus 2 for each shared point).
822  *
823  * The function returns the number of splits: if 0 is returned, the [dst] array contains a single
824  * cubic curve, if 1 is returned, the array contains 2 curves with a shared point, and if 2 is
825  * returned, the array contains 3 curves with 2 shared points.
826  *
827  * The [tmpRoot] array is a scratch array that must contain at least 2 values. It is used to hold
828  * temporary values and its content can be ignored after calling this function.
829  */
cubicToMonotonicCubicsnull830 private fun cubicToMonotonicCubics(cubic: FloatArray, dst: FloatArray, tmpRoot: FloatArray): Int {
831     val rootCount = findCubicExtremaY(cubic, tmpRoot)
832 
833     // Split the curve at the extrema
834     if (rootCount == 0) {
835         if (dst.size < 8) return 0
836         // The cubic segment is already monotonic, copy it as-is
837         cubic.copyInto(dst, 0, 0, 8)
838     } else {
839         var lastT = 0.0f
840         var dstOffset = 0
841         var src = cubic
842 
843         for (i in 0 until rootCount) {
844             var t = tmpRoot[i]
845             t = ((t - lastT) / (1.0f - lastT)).fastCoerceIn(0.0f, 1.0f)
846             lastT = t
847             splitCubicAt(src, dstOffset, dst, dstOffset, t)
848             src = dst
849             dstOffset += 6
850         }
851     }
852 
853     // NOTE: Should we flatten the extrema?
854 
855     return rootCount
856 }
857 
858 /**
859  * Finds the roots of the cubic function which coincide with the specified [cubic] Bézier curve's
860  * extrema on the Y axis. The roots are written in the specified [dstRoots] array which must hold at
861  * least 2 floats. This function returns the number of roots found: 0, 1, or 2.
862  */
findCubicExtremaYnull863 private fun findCubicExtremaY(cubic: FloatArray, dstRoots: FloatArray): Int {
864     val a = cubic[1]
865     val b = cubic[3]
866     val c = cubic[5]
867     val d = cubic[7]
868 
869     val A = d - a + 3.0f * (b - c)
870     val B = 2.0f * (a - b - b - c)
871     val C = b - a
872 
873     return findQuadraticRoots(A, B, C, dstRoots, 0)
874 }
875 
876 /**
877  * Splits the cubic Bézier curve, specified by 4 pairs of floats (8 values) in the [src] array
878  * starting at the index [srcOffset], at position [t] (in the 0..1 range). The results are written
879  * in the [dst] array starting at index [dstOffset]. This function always outputs 2 curves sharing a
880  * point in the [dst] array, for a total of 14 float values: 8 for the first curve, 7 for the second
881  * curve (the end point of the first curve is shared as the start point of the second curve).
882  */
splitCubicAtnull883 private fun splitCubicAt(
884     src: FloatArray,
885     srcOffset: Int,
886     dst: FloatArray,
887     dstOffset: Int,
888     t: Float
889 ) {
890     if (src.size < srcOffset + 8) return
891     if (dst.size < dstOffset + 14) return
892 
893     if (t >= 1.0f) {
894         src.copyInto(dst, dstOffset, srcOffset, 8)
895         val x = src[srcOffset + 6]
896         val y = src[srcOffset + 7]
897         dst[dstOffset + 8] = x
898         dst[dstOffset + 9] = y
899         dst[dstOffset + 10] = x
900         dst[dstOffset + 11] = y
901         dst[dstOffset + 12] = x
902         dst[dstOffset + 13] = y
903         return
904     }
905 
906     val p0x = src[srcOffset + 0]
907     val p0y = src[srcOffset + 1]
908 
909     dst[dstOffset + 0] = p0x
910     dst[dstOffset + 1] = p0y
911 
912     val p1x = src[srcOffset + 2]
913     val p1y = src[srcOffset + 3]
914 
915     val abx = lerp(p0x, p1x, t)
916     val aby = lerp(p0y, p1y, t)
917 
918     dst[dstOffset + 2] = abx
919     dst[dstOffset + 3] = aby
920 
921     val p2x = src[srcOffset + 4]
922     val p2y = src[srcOffset + 5]
923 
924     val bcx = lerp(p1x, p2x, t)
925     val bcy = lerp(p1y, p2y, t)
926     val abcx = lerp(abx, bcx, t)
927     val abcy = lerp(aby, bcy, t)
928 
929     dst[dstOffset + 4] = abcx
930     dst[dstOffset + 5] = abcy
931 
932     val p3x = src[srcOffset + 6]
933     val p3y = src[srcOffset + 7]
934 
935     val cdx = lerp(p2x, p3x, t)
936     val cdy = lerp(p2y, p3y, t)
937     val bcdx = lerp(bcx, cdx, t)
938     val bcdy = lerp(bcy, cdy, t)
939     val abcdx = lerp(abcx, bcdx, t)
940     val abcdy = lerp(abcy, bcdy, t)
941 
942     dst[dstOffset + 6] = abcdx
943     dst[dstOffset + 7] = abcdy
944 
945     dst[dstOffset + 8] = bcdx
946     dst[dstOffset + 9] = bcdy
947 
948     dst[dstOffset + 10] = cdx
949     dst[dstOffset + 11] = cdy
950 
951     dst[dstOffset + 12] = p3x
952     dst[dstOffset + 13] = p3y
953 }
954 
955 /**
956  * Returns the signed area of the specified cubic Bézier curve.
957  *
958  * @param x0 The x coordinate of the curve's start point
959  * @param y0 The y coordinate of the curve's start point
960  * @param x1 The x coordinate of the curve's first control point
961  * @param y1 The y coordinate of the curve's first control point
962  * @param x2 The x coordinate of the curve's second control point
963  * @param y2 The y coordinate of the curve's second control point
964  * @param x3 The x coordinate of the curve's end point
965  * @param y3 The y coordinate of the curve's end point
966  */
cubicAreanull967 internal fun cubicArea(
968     x0: Float,
969     y0: Float,
970     x1: Float,
971     y1: Float,
972     x2: Float,
973     y2: Float,
974     x3: Float,
975     y3: Float
976 ): Float {
977     // See "Computing the area and winding number for a Bézier curve", Jackowski 2012
978     // https://tug.org/TUGboat/tb33-1/tb103jackowski.pdf
979     return ((y3 - y0) * (x1 + x2) - (x3 - x0) * (y1 + y2) + y1 * (x0 - x2) - x1 * (y0 - y2) +
980         y3 * (x2 + x0 / 3.0f) - x3 * (y2 + y0 / 3.0f)) * 3.0f / 20.0f
981 }
982 
983 private inline val PathSegment.startX: Float
984     get() = points[0]
985 
986 private val PathSegment.endX: Float
987     get() =
988         points[
989             when (type) {
990                 PathSegment.Type.Line -> 2
991                 PathSegment.Type.Quadratic -> 4
992                 PathSegment.Type.Conic -> 4
993                 PathSegment.Type.Cubic -> 6
994                 else -> 0
995             }]
996 
997 private inline val PathSegment.startY: Float
998     get() = points[1]
999 
1000 private val PathSegment.endY: Float
1001     get() =
1002         points[
1003             when (type) {
1004                 PathSegment.Type.Line -> 3
1005                 PathSegment.Type.Quadratic -> 5
1006                 PathSegment.Type.Conic -> 5
1007                 PathSegment.Type.Cubic -> 7
1008                 else -> 0
1009             }]
1010