All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.com.mikepenz.markdown.compose.extendedspans.ExtendedSpans.kt Maven / Gradle / Ivy

// Copyright 2023, Saket Narayan
// SPDX-License-Identifier: Apache-2.0
// https://github.com/saket/extended-spans
@file:OptIn(ExperimentalTextApi::class)

package com.mikepenz.markdown.compose.extendedspans

import androidx.compose.runtime.Stable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.ExperimentalTextApi
import androidx.compose.ui.text.TextLayoutResult
import androidx.compose.ui.text.buildAnnotatedString
import com.mikepenz.markdown.compose.extendedspans.internal.fastFold
import com.mikepenz.markdown.compose.extendedspans.internal.fastForEach
import com.mikepenz.markdown.compose.extendedspans.internal.fastMap

@Stable
class ExtendedSpans(
    vararg painters: ExtendedSpanPainter
) {
    private val painters = painters.toList()
    internal var drawInstructions = emptyList()

    /**
     * Prepares [text] to be rendered by [painters]. [RoundedCornerSpanPainter] and [SquigglyUnderlineSpanPainter]
     * use this for removing background and underline spans so that they can be drawn manually.
     */
    fun extend(text: AnnotatedString): AnnotatedString {
        return buildAnnotatedString {
            append(text.text)

            // For onTextLayout to be called if a new instance of ExtendedSpans is applied with the same text.
            val uniqueKey = [email protected]().toString()
            addStringAnnotation(
                EXTENDED_SPANS_MARKER_TAG,
                annotation = uniqueKey,
                start = 0,
                end = 0
            )

            text.spanStyles.fastForEach {
                val decorated = painters.fastFold(initial = it.item) { updated, painter ->
                    painter.decorate(updated, it.start, it.end, text = text, builder = this)
                }
                addStyle(decorated, it.start, it.end)
            }
            text.paragraphStyles.fastForEach {
                addStyle(it.item, it.start, it.end)
            }
            text.getStringAnnotations(start = 0, end = text.length).fastForEach {
                addStringAnnotation(
                    tag = it.tag,
                    annotation = it.item,
                    start = it.start,
                    end = it.end
                )
            }
            text.getTtsAnnotations(start = 0, end = text.length).fastForEach {
                addTtsAnnotation(it.item, it.start, it.end)
            }
        }
    }

    fun onTextLayout(layoutResult: TextLayoutResult) {
        layoutResult.checkIfExtendWasCalled()
        drawInstructions = painters.fastMap {
            it.drawInstructionsFor(layoutResult)
        }
    }

    private fun TextLayoutResult.checkIfExtendWasCalled() {
        val wasExtendCalled = layoutInput.text.getStringAnnotations(
            tag = EXTENDED_SPANS_MARKER_TAG,
            start = 0,
            end = 0
        ).isNotEmpty()
        check(wasExtendCalled) {
            "ExtendedSpans#extend(AnnotatedString) wasn't called for this Text()."
        }
    }

    companion object {
        private const val EXTENDED_SPANS_MARKER_TAG = "extended_spans_marker"
    }
}

fun Modifier.drawBehind(spans: ExtendedSpans): Modifier {
    return drawBehind {
        spans.drawInstructions.fastForEach { instructions ->
            with(instructions) {
                draw()
            }
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy