Add implementation for conversion from conic to quadratic and use it on host platforms.

Bug: 422712992
Test: added robolectric tests to check the code running on host platform.
Change-Id: I3a36a3e7b31e75e34041b0c4ba202192cce787cf
diff --git a/graphics/graphics-path/build.gradle b/graphics/graphics-path/build.gradle
index b67834a..c6107a7 100644
--- a/graphics/graphics-path/build.gradle
+++ b/graphics/graphics-path/build.gradle
@@ -34,6 +34,12 @@
 
     implementation("androidx.core:core:1.12.0")
 
+    testImplementation(libs.junit)
+    testImplementation(libs.testExtJunit)
+    testImplementation(libs.testCore)
+    testImplementation(libs.testRunner)
+    testImplementation("androidx.core:core-ktx:1.12.0")
+
     androidTestImplementation("androidx.annotation:annotation:1.8.1")
     androidTestImplementation("androidx.core:core-ktx:1.12.0")
     androidTestImplementation("androidx.test:core:1.5.0@aar")
@@ -89,4 +95,5 @@
     inceptionYear = "2022"
     description = "Query segment data for android.graphics.Path objects"
     legacyDisableKotlinStrictApiMode = true
+    enableRobolectric()
 }
diff --git a/graphics/graphics-path/src/main/java/androidx/graphics/path/ConicConverter.kt b/graphics/graphics-path/src/main/java/androidx/graphics/path/ConicConverter.kt
index b4456e6..b04fd8c 100644
--- a/graphics/graphics-path/src/main/java/androidx/graphics/path/ConicConverter.kt
+++ b/graphics/graphics-path/src/main/java/androidx/graphics/path/ConicConverter.kt
@@ -65,18 +65,39 @@
 
     /** Converts the conic in [points] to a series of quadratics, which will all be stored */
     fun convert(points: FloatArray, weight: Float, tolerance: Float, offset: Int = 0) {
-        quadraticCount = internalConicToQuadratics(points, offset, quadraticData, weight, tolerance)
+        quadraticCount = conicToQuadraticsCompat(points, offset, quadraticData, weight, tolerance)
         // 3 points per quadratic, 2 floats per point, with one point of overlap
         val newDataSize = quadraticCount * 2 * 2 + 2
         if (newDataSize > quadraticData.size) {
             quadraticData = FloatArray(newDataSize)
             quadraticCount =
-                internalConicToQuadratics(points, offset, quadraticData, weight, tolerance)
+                conicToQuadraticsCompat(points, offset, quadraticData, weight, tolerance)
         }
         currentQuadratic = 0
     }
 
     /**
+     * Calls the appropriate function for the conversion from conic to quadratic.
+     *
+     * As the native library is only available for Android, it uses our own implementation for host
+     * platforms, e.g. LayoutLib and Robolectric.
+     */
+    private fun conicToQuadraticsCompat(
+        conicPoints: FloatArray,
+        offset: Int,
+        quadraticPoints: FloatArray,
+        weight: Float,
+        tolerance: Float,
+    ): Int {
+        val isDalvik = "dalvik".equals(System.getProperty("java.vm.name"), ignoreCase = true)
+        return if (isDalvik) {
+            internalConicToQuadratics(conicPoints, offset, quadraticPoints, weight, tolerance)
+        } else {
+            conicToQuadratics(conicPoints, offset, quadraticPoints, weight, tolerance)
+        }
+    }
+
+    /**
      * The actual conversion from conic to quadratic data happens in native code, in the library
      * loaded elsewhere. This JNI function wraps that native functionality.
      */
diff --git a/graphics/graphics-path/src/main/java/androidx/graphics/path/ConicsImpl.kt b/graphics/graphics-path/src/main/java/androidx/graphics/path/ConicsImpl.kt
new file mode 100644
index 0000000..8ebac33
--- /dev/null
+++ b/graphics/graphics-path/src/main/java/androidx/graphics/path/ConicsImpl.kt
@@ -0,0 +1,193 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.graphics.path
+
+import java.util.ArrayList
+import kotlin.math.abs
+import kotlin.math.sqrt
+
+private const val MAX_CONIC_TO_QUAD_COUNT = 5
+
+internal fun conicToQuadratics(
+    conicPoints: FloatArray,
+    offset: Int,
+    quadraticPoints: FloatArray,
+    weight: Float,
+    tolerance: Float,
+): Int {
+    val conic =
+        Conic(
+            arrayOf(
+                Point(conicPoints[0 + offset], conicPoints[1 + offset]),
+                Point(conicPoints[2 + offset], conicPoints[3 + offset]),
+                Point(conicPoints[4 + offset], conicPoints[5 + offset]),
+            ),
+            weight,
+        )
+
+    val count = conic.computeQuadraticCount(tolerance)
+    val quadraticCount = 1 shl count
+    if (quadraticCount > quadraticPoints.size) {
+        // Buffer not large enough; return necessary size to resize and try again
+        return quadraticCount
+    }
+
+    val dstPoints = ArrayList<Point>()
+    val finalCount = conic.splitIntoQuadratics(dstPoints, count)
+
+    var index = 0
+    for (p in dstPoints) {
+        quadraticPoints[index++] = p.x
+        quadraticPoints[index++] = p.y
+    }
+
+    return finalCount
+}
+
+private class Point(val x: Float, val y: Float) {
+    fun isFinite() = x.isFinite() && y.isFinite()
+}
+
+private fun add(a: Point, b: Point) = Point(a.x + b.x, a.y + b.y)
+
+private fun mul(a: Point, b: Point) = Point(a.x * b.x, a.y * b.y)
+
+private fun approxEquals(a: Float, b: Float): Boolean = abs(a - b) < 0.0001f
+
+private fun approxEquals(a: Point, b: Point) = approxEquals(a.x, b.x) && approxEquals(a.y, b.y)
+
+private fun between(a: Float, b: Float, c: Float): Boolean = (a - b) * (c - b) <= 0.0f
+
+private fun subdivide(src: Conic, pts: ArrayList<Point>, level: Int) {
+    if (level == 0) {
+        pts.add(src.points[1])
+        pts.add(src.points[2])
+    } else {
+        val s = src.split()
+        val startY = src.points[0].y
+        val endY = src.points[2].y
+        if (between(startY, src.points[1].y, endY)) {
+            val midY = s.a.points[2].y
+            if (!between(startY, midY, endY)) {
+                val closerY = if (abs(midY - startY) < abs(midY - endY)) startY else endY
+                s.a.points[2] = Point(s.a.points[2].x, closerY)
+                s.b.points[0] = Point(s.b.points[0].x, closerY)
+            }
+            if (!between(startY, s.a.points[1].y, s.a.points[2].y)) {
+                s.a.points[1] = Point(s.a.points[1].x, startY)
+            }
+            if (!between(s.b.points[0].y, s.b.points[1].y, endY)) {
+                s.b.points[1] = Point(s.b.points[1].x, endY)
+            }
+        }
+        subdivide(s.a, pts, level - 1)
+        subdivide(s.b, pts, level - 1)
+    }
+}
+
+private class Conic(val points: Array<Point>, val weight: Float) {
+    fun computeQuadraticCount(tolerance: Float): Int {
+        val a = weight - 1.0f
+        val k = a / (4.0f * (2.0f + a))
+        val x = k * (points[0].x - 2.0f * points[1].x + points[2].x)
+        val y = k * (points[0].y - 2.0f * points[1].y + points[2].y)
+
+        var error = sqrt(x * x + y * y)
+        var count = 0
+        while (count < MAX_CONIC_TO_QUAD_COUNT) {
+            if (error <= tolerance) {
+                break
+            }
+            error *= 0.25f
+            count++
+        }
+        return count
+    }
+
+    class SplitResult(val a: Conic, val b: Conic)
+
+    fun split(): SplitResult {
+        val scale = Point(1.0f / (1.0f + weight), 1.0f / (1.0f + weight))
+        val newW = sqrt(0.5f + weight * 0.5f)
+
+        val p0 = points[0]
+        val p1 = points[1]
+        val p2 = points[2]
+        val ww = Point(weight, weight)
+
+        val wp1 = mul(ww, p1)
+        var m = mul(mul(add(add(p0, add(wp1, wp1)), p2), scale), Point(0.5f, 0.5f))
+        if (!m.isFinite()) {
+            val wD = weight.toDouble()
+            val w2 = wD * 2.0
+            val scaleHalf = 1.0 / (1.0 + wD) * 0.5
+            m =
+                Point(
+                    ((p0.x.toDouble() + w2 * p1.x.toDouble() + p2.x.toDouble()) * scaleHalf)
+                        .toFloat(),
+                    ((p0.y.toDouble() + w2 * p1.y.toDouble() + p2.y.toDouble()) * scaleHalf)
+                        .toFloat(),
+                )
+        }
+        return SplitResult(
+            Conic(arrayOf(p0, mul(add(p0, wp1), scale), m), newW),
+            Conic(arrayOf(m, mul(add(wp1, p2), scale), p2), newW),
+        )
+    }
+
+    fun commonFinitePointCheck(dstPoints: ArrayList<Point>, count: Int): Int {
+        val quadCount = 1 shl count
+        val pointCount = 2 * quadCount + 1
+
+        var isFinite = true
+        for (p in dstPoints) {
+            if (!p.isFinite()) {
+                isFinite = false
+                break
+            }
+        }
+
+        if (!isFinite) {
+            for (i in 1..<pointCount) {
+                dstPoints[i] = points[1]
+            }
+        }
+
+        return quadCount
+    }
+
+    fun splitIntoQuadratics(dstPoints: ArrayList<Point>, count: Int): Int {
+        dstPoints.add(points[0])
+
+        if (count > MAX_CONIC_TO_QUAD_COUNT) {
+            val s = split()
+
+            if (
+                approxEquals(s.a.points[1], s.a.points[2]) &&
+                    approxEquals(s.b.points[0], s.b.points[1])
+            ) {
+                dstPoints.add(s.a.points[1])
+                dstPoints.add(s.a.points[1])
+                dstPoints.add(s.a.points[1])
+                dstPoints.add(s.b.points[2])
+                return commonFinitePointCheck(dstPoints, 1)
+            }
+        }
+
+        subdivide(this, dstPoints, count)
+        return commonFinitePointCheck(dstPoints, count)
+    }
+}
diff --git a/graphics/graphics-path/src/test/java/androidx/graphics/path/PathIteratorJvmTest.kt b/graphics/graphics-path/src/test/java/androidx/graphics/path/PathIteratorJvmTest.kt
new file mode 100644
index 0000000..aba3a36
--- /dev/null
+++ b/graphics/graphics-path/src/test/java/androidx/graphics/path/PathIteratorJvmTest.kt
@@ -0,0 +1,527 @@
+/*
+ * Copyright 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.graphics.path
+
+import android.graphics.Bitmap
+import android.graphics.Color
+import android.graphics.Paint
+import android.graphics.Path
+import android.graphics.PointF
+import android.graphics.RectF
+import androidx.core.graphics.applyCanvas
+import androidx.core.graphics.createBitmap
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import kotlin.math.abs
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Assert.fail
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.annotation.Config
+import org.robolectric.annotation.GraphicsMode
+import org.robolectric.annotation.internal.DoNotInstrument
+
+@RunWith(AndroidJUnit4::class)
+@GraphicsMode(GraphicsMode.Mode.NATIVE)
+@Config(minSdk = 35)
+@DoNotInstrument
+class PathIteratorJvmTest {
+    @Test
+    fun emptyIterator() {
+        val path = Path()
+
+        val iterator = path.iterator()
+        assertFalse(iterator.hasNext())
+        val firstSegment = iterator.next()
+        assertEquals(PathSegment.Type.Done, firstSegment.type)
+
+        var count = 0
+        for (segment in path) {
+            count++
+        }
+
+        assertEquals(0, count)
+    }
+
+    @Test
+    fun emptyPeek() {
+        val path = Path()
+        val iterator = path.iterator()
+        assertEquals(PathSegment.Type.Done, iterator.peek())
+    }
+
+    @Test
+    fun nonEmptyIterator() {
+        val path =
+            Path().apply {
+                moveTo(1.0f, 1.0f)
+                lineTo(2.0f, 2.0f)
+                close()
+            }
+
+        val iterator = path.iterator()
+        assertTrue(iterator.hasNext())
+
+        val types =
+            arrayOf(
+                PathSegment.Type.Move,
+                PathSegment.Type.Line,
+                PathSegment.Type.Close,
+                PathSegment.Type.Done,
+            )
+        val points = arrayOf(PointF(1.0f, 1.0f), PointF(2.0f, 2.0f))
+
+        var count = 0
+        for (segment in path) {
+            assertEquals(types[count], segment.type)
+            when (segment.type) {
+                PathSegment.Type.Move -> {
+                    assertEquals(points[count], segment.points[0])
+                }
+                PathSegment.Type.Line -> {
+                    assertEquals(points[count - 1], segment.points[0])
+                    assertEquals(points[count], segment.points[1])
+                }
+                else -> {}
+            }
+            // TODO: remove condition and just auto-increment count when platform change is
+            // checked in which ignores DONE during iteration
+            if (segment.type != PathSegment.Type.Done) count++
+        }
+
+        assertEquals(3, count)
+    }
+
+    @Test
+    fun peek() {
+        val path =
+            Path().apply {
+                moveTo(1.0f, 1.0f)
+                lineTo(2.0f, 2.0f)
+                close()
+            }
+
+        val iterator = path.iterator()
+        assertEquals(PathSegment.Type.Move, iterator.peek())
+    }
+
+    @Test
+    fun peekBeyond() {
+        val path = Path()
+        assertEquals(PathSegment.Type.Done, path.iterator().peek())
+
+        path.apply {
+            moveTo(1.0f, 1.0f)
+            lineTo(2.0f, 2.0f)
+            close()
+        }
+
+        val iterator = path.iterator()
+        while (iterator.hasNext()) iterator.next()
+        assertEquals(PathSegment.Type.Done, iterator.peek())
+    }
+
+    @Test
+    fun iteratorStyles() {
+        val path =
+            Path().apply {
+                moveTo(1.0f, 1.0f)
+                lineTo(2.0f, 2.0f)
+                cubicTo(3.0f, 3.0f, 4.0f, 4.0f, 5.0f, 5.0f)
+                quadTo(7.0f, 7.0f, 8.0f, 8.0f)
+                moveTo(10.0f, 10.0f)
+                // addRoundRect() will generate conic curves on certain API levels
+                addRoundRect(RectF(12.0f, 12.0f, 36.0f, 36.0f), 8.0f, 8.0f, Path.Direction.CW)
+                close()
+            }
+
+        iteratorStylesImpl(path, PathIterator.ConicEvaluation.AsConic)
+        iteratorStylesImpl(path, PathIterator.ConicEvaluation.AsQuadratics)
+    }
+
+    private fun iteratorStylesImpl(path: Path, conicEvaluation: PathIterator.ConicEvaluation) {
+        val iterator1 = path.iterator(conicEvaluation)
+        val iterator2 = path.iterator(conicEvaluation)
+        val iterator3 = path.iterator(conicEvaluation)
+
+        val points = FloatArray(8)
+        val points2 = FloatArray(16)
+
+        while (iterator1.hasNext() || iterator2.hasNext() || iterator3.hasNext()) {
+            val segment = iterator1.next()
+            val type = iterator2.next(points)
+            val type2 = iterator3.next(points2, 8)
+
+            assertEquals(type, segment.type)
+            assertEquals(type2, segment.type)
+
+            when (type) {
+                PathSegment.Type.Move -> {
+                    assertPointsEquals(points, 0, segment.points[0])
+                    assertPointsEquals(points2, 4, segment.points[0])
+                }
+                PathSegment.Type.Line -> {
+                    assertPointsEquals(points, 0, segment.points[0])
+                    assertPointsEquals(points, 1, segment.points[1])
+                    assertPointsEquals(points2, 4, segment.points[0])
+                    assertPointsEquals(points2, 5, segment.points[1])
+                }
+                PathSegment.Type.Quadratic -> {
+                    assertPointsEquals(points, 0, segment.points[0])
+                    assertPointsEquals(points, 1, segment.points[1])
+                    assertPointsEquals(points, 2, segment.points[2])
+                    assertPointsEquals(points2, 4, segment.points[0])
+                    assertPointsEquals(points2, 5, segment.points[1])
+                    assertPointsEquals(points2, 6, segment.points[2])
+                }
+                PathSegment.Type.Conic -> {
+                    assertPointsEquals(points, 0, segment.points[0])
+                    assertPointsEquals(points, 1, segment.points[1])
+                    assertPointsEquals(points, 2, segment.points[2])
+                    // Weight is stored after all of the points
+                    assertEquals(points[6], segment.weight)
+
+                    assertPointsEquals(points2, 4, segment.points[0])
+                    assertPointsEquals(points2, 5, segment.points[1])
+                    assertPointsEquals(points2, 6, segment.points[2])
+                    // Weight is stored after all of the points
+                    assertEquals(points2[14], segment.weight)
+                }
+                PathSegment.Type.Cubic -> {
+                    assertPointsEquals(points, 0, segment.points[0])
+                    assertPointsEquals(points, 1, segment.points[1])
+                    assertPointsEquals(points, 2, segment.points[2])
+                    assertPointsEquals(points, 3, segment.points[3])
+
+                    assertPointsEquals(points2, 4, segment.points[0])
+                    assertPointsEquals(points2, 5, segment.points[1])
+                    assertPointsEquals(points2, 6, segment.points[2])
+                    assertPointsEquals(points2, 7, segment.points[3])
+                }
+                PathSegment.Type.Close -> {}
+                PathSegment.Type.Done -> {}
+            }
+        }
+    }
+
+    @Test
+    fun done() {
+        val path = Path().apply { close() }
+
+        val segment = path.iterator().next()
+
+        assertEquals(PathSegment.Type.Done, segment.type)
+        assertEquals(0, segment.points.size)
+        assertEquals(0.0f, segment.weight)
+    }
+
+    @Test
+    fun close() {
+        val path =
+            Path().apply {
+                lineTo(10.0f, 12.0f)
+                close()
+            }
+
+        val iterator = path.iterator()
+        // Swallow the move
+        iterator.next()
+        // Swallow the line
+        iterator.next()
+
+        val segment = iterator.next()
+
+        assertEquals(PathSegment.Type.Close, segment.type)
+        assertEquals(0, segment.points.size)
+        assertEquals(0.0f, segment.weight)
+    }
+
+    @Test
+    fun moveTo() {
+        val path = Path().apply { moveTo(10.0f, 12.0f) }
+
+        val segment = path.iterator().next()
+
+        assertEquals(PathSegment.Type.Move, segment.type)
+        assertEquals(1, segment.points.size)
+        assertPointsEquals(PointF(10.0f, 12.0f), segment.points[0])
+        assertEquals(0.0f, segment.weight)
+    }
+
+    @Test
+    fun lineTo() {
+        val path =
+            Path().apply {
+                moveTo(4.0f, 6.0f)
+                lineTo(10.0f, 12.0f)
+            }
+
+        val iterator = path.iterator()
+        // Swallow the move
+        iterator.next()
+
+        val segment = iterator.next()
+
+        assertEquals(PathSegment.Type.Line, segment.type)
+        assertEquals(2, segment.points.size)
+        assertPointsEquals(PointF(4.0f, 6.0f), segment.points[0])
+        assertPointsEquals(PointF(10.0f, 12.0f), segment.points[1])
+        assertEquals(0.0f, segment.weight)
+    }
+
+    @Test
+    fun quadraticTo() {
+        val path =
+            Path().apply {
+                moveTo(4.0f, 6.0f)
+                quadTo(10.0f, 12.0f, 20.0f, 24.0f)
+            }
+
+        val iterator = path.iterator()
+        // Swallow the move
+        iterator.next()
+
+        val segment = iterator.next()
+
+        assertEquals(PathSegment.Type.Quadratic, segment.type)
+        assertEquals(3, segment.points.size)
+        assertPointsEquals(PointF(4.0f, 6.0f), segment.points[0])
+        assertPointsEquals(PointF(10.0f, 12.0f), segment.points[1])
+        assertPointsEquals(PointF(20.0f, 24.0f), segment.points[2])
+        assertEquals(0.0f, segment.weight)
+    }
+
+    @Test
+    fun cubicTo() {
+        val path =
+            Path().apply {
+                moveTo(4.0f, 6.0f)
+                cubicTo(10.0f, 12.0f, 20.0f, 24.0f, 30.0f, 36.0f)
+            }
+
+        val iterator = path.iterator()
+        // Swallow the move
+        iterator.next()
+
+        val segment = iterator.next()
+
+        assertEquals(PathSegment.Type.Cubic, segment.type)
+        assertEquals(4, segment.points.size)
+        assertPointsEquals(PointF(4.0f, 6.0f), segment.points[0])
+        assertPointsEquals(PointF(10.0f, 12.0f), segment.points[1])
+        assertPointsEquals(PointF(20.0f, 24.0f), segment.points[2])
+        assertPointsEquals(PointF(30.0f, 36.0f), segment.points[3])
+        assertEquals(0.0f, segment.weight)
+    }
+
+    @Test
+    fun conicTo() {
+        val path =
+            Path().apply {
+                addRoundRect(RectF(12.0f, 12.0f, 24.0f, 24.0f), 8.0f, 8.0f, Path.Direction.CW)
+            }
+
+        val iterator = path.iterator(PathIterator.ConicEvaluation.AsConic)
+        // Swallow the move
+        iterator.next()
+
+        val segment = iterator.next()
+
+        assertEquals(PathSegment.Type.Conic, segment.type)
+        assertEquals(3, segment.points.size)
+
+        assertPointsEquals(PointF(12.0f, 18.0f), segment.points[0])
+        assertPointsEquals(PointF(12.0f, 12.0f), segment.points[1])
+        assertPointsEquals(PointF(18.0f, 12.0f), segment.points[2])
+        assertEquals(0.70710677f, segment.weight)
+    }
+
+    @Test
+    fun conicAsQuadratics() {
+        val path =
+            Path().apply {
+                addRoundRect(RectF(12.0f, 12.0f, 24.0f, 24.0f), 8.0f, 8.0f, Path.Direction.CW)
+            }
+
+        for (segment in path) {
+            if (segment.type == PathSegment.Type.Conic) fail("Found conic, none expected: $segment")
+        }
+    }
+
+    @Test
+    fun convertedConics() {
+        val path1 =
+            Path().apply {
+                addRoundRect(RectF(12.0f, 12.0f, 64.0f, 64.0f), 12.0f, 12.0f, Path.Direction.CW)
+            }
+
+        val path2 = Path()
+        for (segment in path1) {
+            when (segment.type) {
+                PathSegment.Type.Move -> path2.moveTo(segment.points[0].x, segment.points[0].y)
+                PathSegment.Type.Line -> path2.lineTo(segment.points[1].x, segment.points[1].y)
+                PathSegment.Type.Quadratic ->
+                    path2.quadTo(
+                        segment.points[1].x,
+                        segment.points[1].y,
+                        segment.points[2].x,
+                        segment.points[2].y,
+                    )
+                PathSegment.Type.Conic -> fail("Unexpected conic! $segment")
+                PathSegment.Type.Cubic ->
+                    path2.cubicTo(
+                        segment.points[1].x,
+                        segment.points[1].y,
+                        segment.points[2].x,
+                        segment.points[2].y,
+                        segment.points[3].x,
+                        segment.points[3].y,
+                    )
+                PathSegment.Type.Close -> path2.close()
+                PathSegment.Type.Done -> {}
+            }
+        }
+
+        // Now with smaller error tolerance
+        val path3 = Path()
+        for (segment in
+            path1.iterator(conicEvaluation = PathIterator.ConicEvaluation.AsQuadratics, 0.001f)) {
+            when (segment.type) {
+                PathSegment.Type.Move -> path3.moveTo(segment.points[0].x, segment.points[0].y)
+                PathSegment.Type.Line -> path3.lineTo(segment.points[1].x, segment.points[1].y)
+                PathSegment.Type.Quadratic ->
+                    path3.quadTo(
+                        segment.points[1].x,
+                        segment.points[1].y,
+                        segment.points[2].x,
+                        segment.points[2].y,
+                    )
+                PathSegment.Type.Conic -> fail("Unexpected conic! $segment")
+                PathSegment.Type.Cubic ->
+                    path3.cubicTo(
+                        segment.points[1].x,
+                        segment.points[1].y,
+                        segment.points[2].x,
+                        segment.points[2].y,
+                        segment.points[3].x,
+                        segment.points[3].y,
+                    )
+                PathSegment.Type.Close -> path3.close()
+                PathSegment.Type.Done -> {}
+            }
+        }
+
+        val b1 =
+            createBitmap(76, 76).applyCanvas {
+                drawARGB(255, 255, 255, 255)
+                drawPath(
+                    path1,
+                    Paint().apply {
+                        color = argb(1.0f, 0.0f, 0.0f, 1.0f)
+                        strokeWidth = 2.0f
+                        isAntiAlias = true
+                        style = Paint.Style.STROKE
+                    },
+                )
+            }
+
+        val b2 =
+            createBitmap(76, 76).applyCanvas {
+                drawARGB(255, 255, 255, 255)
+                drawPath(
+                    path2,
+                    Paint().apply {
+                        color = argb(1.0f, 0.0f, 0.0f, 1.0f)
+                        strokeWidth = 2.0f
+                        isAntiAlias = true
+                        style = Paint.Style.STROKE
+                    },
+                )
+            }
+
+        compareBitmaps(b1, b2)
+        // Note: b1-vs-b3 is not a valid comparison; default Skia rendering does not use an
+        // error tolerance that low. The test for fine-precision in path3 was just to
+        // ensure that the system could handle the extra data and operations required
+    }
+
+    @Test
+    fun sizes() {
+        val path = Path()
+        var iterator: PathIterator = path.iterator()
+
+        assertEquals(0, iterator.calculateSize())
+
+        path.addRoundRect(RectF(12.0f, 12.0f, 64.0f, 64.0f), 8.0f, 8.0f, Path.Direction.CW)
+
+        // Skia converted
+        // Preserve conics and count
+        iterator = path.iterator(PathIterator.ConicEvaluation.AsConic)
+        assertEquals(10, iterator.calculateSize())
+        assertEquals(iterator.calculateSize(false), iterator.calculateSize())
+
+        // Convert conics and count
+        iterator = path.iterator(PathIterator.ConicEvaluation.AsQuadratics)
+        // simple size, not including conic conversion
+        assertEquals(10, iterator.calculateSize(false))
+        // now get the size with converted conics
+        assertEquals(14, iterator.calculateSize())
+    }
+}
+
+fun argb(alpha: Float, red: Float, green: Float, blue: Float) =
+    ((alpha * 255.0f + 0.5f).toInt() shl 24) or
+        ((red * 255.0f + 0.5f).toInt() shl 16) or
+        ((green * 255.0f + 0.5f).toInt() shl 8) or
+        (blue * 255.0f + 0.5f).toInt()
+
+private fun assertPointsEquals(p1: PointF, p2: PointF) {
+    assertEquals(p1.x, p2.x, 1e-6f)
+    assertEquals(p1.y, p2.y, 1e-6f)
+}
+
+private fun assertPointsEquals(p1: FloatArray, offset: Int, p2: PointF) {
+    assertEquals(p1[0 + offset * 2], p2.x, 1e-6f)
+    assertEquals(p1[1 + offset * 2], p2.y, 1e-6f)
+}
+
+private fun compareBitmaps(b1: Bitmap, b2: Bitmap) {
+    val epsilon = 1
+
+    assertEquals(b1.width, b2.width)
+    assertEquals(b1.height, b2.height)
+
+    val p1 = IntArray(b1.width * b1.height)
+    b1.getPixels(p1, 0, b1.width, 0, 0, b1.width, b1.height)
+
+    val p2 = IntArray(b2.width * b2.height)
+    b2.getPixels(p2, 0, b2.width, 0, 0, b2.width, b2.height)
+
+    for (x in 0 until b1.width) {
+        for (y in 0 until b2.width) {
+            val index = y * b1.width + x
+
+            val c1 = p1[index]
+            val c2 = p2[index]
+
+            assertTrue(abs(Color.red(c1) - Color.red(c2)) <= epsilon)
+            assertTrue(abs(Color.green(c1) - Color.green(c2)) <= epsilon)
+            assertTrue(abs(Color.blue(c1) - Color.blue(c2)) <= epsilon)
+        }
+    }
+}