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

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

// Copyright 2023, Saket Narayan
// SPDX-License-Identifier: Apache-2.0
// https://github.com/saket/extended-spans
package com.mikepenz.markdown.compose.extendedspans

import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.TextLayoutResult
import androidx.compose.ui.text.style.ResolvedTextDirection.Ltr
import com.mikepenz.markdown.compose.extendedspans.internal.fastMapRange

abstract class ExtendedSpanPainter {
    /**
     * Can be used for removing any existing spans from [text] so that they can be drawn manually.
     */
    abstract fun decorate(
        span: SpanStyle,
        start: Int,
        end: Int,
        text: AnnotatedString,
        builder: AnnotatedString.Builder
    ): SpanStyle

    abstract fun drawInstructionsFor(
        layoutResult: TextLayoutResult
    ): SpanDrawInstructions

    /**
     * Reads bounds for multiple lines. This can be removed once an
     * [official API](https://issuetracker.google.com/u/1/issues/237289433) is released.
     *
     * When [flattenForFullParagraphs] is available, the bounds for one or multiple
     * entire paragraphs is returned instead of separate lines if [startOffset]
     * and [endOffset] represent the extreme ends of those paragraph.
     */
    protected fun TextLayoutResult.getBoundingBoxes(
        startOffset: Int,
        endOffset: Int,
        flattenForFullParagraphs: Boolean = false
    ): List {
        if (startOffset == endOffset) {
            return emptyList()
        }

        val startLineNum = getLineForOffset(startOffset)
        val endLineNum = getLineForOffset(endOffset)

        if (flattenForFullParagraphs) {
            val isFullParagraph = (startLineNum != endLineNum)
                    && getLineStart(startLineNum) == startOffset
                    && multiParagraph.getLineEnd(endLineNum, visibleEnd = true) == endOffset

            if (isFullParagraph) {
                return listOf(
                    Rect(
                        top = getLineTop(startLineNum),
                        bottom = getLineBottom(endLineNum),
                        left = 0f,
                        right = size.width.toFloat()
                    )
                )
            }
        }

        // Compose UI does not offer any API for reading paragraph direction for an entire line.
        // So this code assumes that all paragraphs in the text will have the same direction.
        // It also assumes that this paragraph does not contain bi-directional text.
        val isLtr = multiParagraph.getParagraphDirection(offset = layoutInput.text.lastIndex) == Ltr

        return fastMapRange(startLineNum, endLineNum) { lineNum ->
            Rect(
                top = getLineTop(lineNum),
                bottom = getLineBottom(lineNum),
                left = if (lineNum == startLineNum) {
                    getHorizontalPosition(startOffset, usePrimaryDirection = isLtr)
                } else {
                    getLineLeft(lineNum)
                },
                right = if (lineNum == endLineNum) {
                    getHorizontalPosition(endOffset, usePrimaryDirection = isLtr)
                } else {
                    getLineRight(lineNum)
                }
            )
        }
    }
}

fun interface SpanDrawInstructions {
    fun DrawScope.draw()
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy