ConicConverter.kt

/*
 * 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 android.util.Log

/**
 * This class converts a given Conic object to the equivalent set of Quadratic objects.
 * It stores all quadratics from a conversion in the call to [convert], but returns only
 * one at a time, from nextQuadratic(), storing the rest for later retrieval (since a
 * PathIterator only retrieves one object at a time).
 *
 * This object is stateful, using quadraticCount, currentQuadratic, and quadraticData
 * to send back the next quadratic when requested, in [nextQuadratic].
 */
internal class ConicConverter() {

    private val LOG_TAG = "ConicConverter"
    private val DEBUG = false

    /**
     * The total number of quadratics currently stored in the converter
     */
    var quadraticCount: Int = 0
        private set

    /**
     * The index of the current Quadratic; this is the next quadratic to be returned
     * in the call to nextQuadratic().
     */
    var currentQuadratic = 0

    /**
     * Storage for all quadratics for a particular conic. Set to reasonable
     * default size, will need to resize if we ever get a return count larger
     * than the current size.
     * Initial size holds up to 5 quadratics: 2 floats/point, 3 points/quadratic
     * where all quadratics overlap in one point except the ends.
     */
    private var quadraticData = FloatArray(1)

    /**
     * This function stores the next converted quadratic in the given points array,
     * returning true if this happened, false if there was no quadratic to be returned.
     */
    fun nextQuadratic(points: FloatArray, offset: Int = 0): Boolean {
        if (currentQuadratic < quadraticCount) {
            val index = currentQuadratic * 2 * 2
            points[0 + offset] = quadraticData[index]
            points[1 + offset] = quadraticData[index + 1]
            points[2 + offset] = quadraticData[index + 2]
            points[3 + offset] = quadraticData[index + 3]
            points[4 + offset] = quadraticData[index + 4]
            points[5 + offset] = quadraticData[index + 5]
            currentQuadratic++
            return true
        }
        return false
    }

    /**
     * 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, quadraticData, weight, tolerance, offset)
        if (quadraticCount > quadraticData.size) {
            if (DEBUG) Log.d(LOG_TAG, "Resizing quadraticData buffer to $quadraticCount")
            quadraticData = FloatArray(quadraticCount * 4 * 2)
            quadraticCount = internalConicToQuadratics(points, quadraticData, weight, tolerance,
                offset)
        }
        currentQuadratic = 0
        if (DEBUG) Log.d("ConicConverter", "internalConicToQuadratics returned " + quadraticCount)
    }

    /**
     * The actual conversion from conic to quadratic data happens in native code, in the library
     * loaded elsewhere. This JNI function wraps that native functionality.
     */
    @Suppress("KotlinJniMissingFunction")
    private external fun internalConicToQuadratics(
        conicPoints: FloatArray,
        quadraticPoints: FloatArray,
        weight: Float,
        tolerance: Float,
        offset: Int
    ): Int
}