Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Add small padding/margin between text with AnnotatedString

Whenever I use the buildAnnotatedString I must write some sort of style using ParagraphStyle or SpanStlye. Sadly none of these 2 classes have modifiers to add some padding between every text (append)

This is what I'm getting right now. There should a small space between the background and the letters at the start and at the end of every text:

enter image description here

This is the code with the background:

val spanStyle = (
    MaterialTheme.typography.body1.copy(
     color = MaterialTheme.colors.onSurface,
     fontWeight = FontWeight.W400,
     fontSize = 17.sp,
     letterSpacing = 0.25.sp,
     background = MaterialTheme.colors.surface,
     baselineShift = BaselineShift(0.2f),
)).toSpanStyle()

buildAnnotatedString {
    withStyle(style = spanStyle) {
         append("I write text here with background")
    }
}

I've checked ParagraphStyle but the only thing related to vertical space is the lineHeight which obviously increase the height of every line but not the space between them.

Is there any way to add this little small space between every append() so background isn't coupled?

Edit: This is what I have right now: enter image description here

This is what I want to achieve, the horizontal padding at the start and end inside. TextIndent doesn't work because there's not a separation between the start background and start text. enter image description here

like image 489
Barrufet Avatar asked Sep 09 '26 12:09

Barrufet


1 Answers

I don't think it's possible with system methods, but you can draw background manually.

I'm adding only sample code here, fore more details of implementation see this answer.

@Composable
fun TestScreen(
) {
    val baselineShiftMultiplier = 0.2f
    val lineHeightMultiplier = 1.7f
    val spanStyle = MaterialTheme.typography.body1.copy(
        color = MaterialTheme.colors.onSurface,
        fontWeight = FontWeight.W400,
        fontSize = 17.sp,
        letterSpacing = 0.25.sp,
        baselineShift = BaselineShift(baselineShiftMultiplier),
    )
    var selectedPartPaths by remember { mutableStateOf(Path()) }
    Text(
        buildAnnotatedString {
            withStyle(ParagraphStyle(lineHeight = spanStyle.fontSize * lineHeightMultiplier)) {
                append("I write text here\n")
                withStyle(style = spanStyle.toSpanStyle()) {
                    append("I write text here with background\n")
                    append("I write text here with background\n")
                }
                append("I write text here\n")
                withStyle(style = spanStyle.toSpanStyle()) {
                    append("I write text here with background\n")
                }
                append("I write text here\n")
            }
        },
        onTextLayout = { layoutResult ->
            selectedPartPaths = Path().apply {
                layoutResult.layoutInput.text.spanStyles.forEach { spanRange ->
                    val boundingBoxes = layoutResult
                        .getBoundingBoxesForRange(
                            start = spanRange.start,
                            end = spanRange.end
                        )
                    for (i in boundingBoxes.indices) {
                        val boundingBox = boundingBoxes[i]
                        addRect(
                            boundingBox.copy(
                                top = boundingBox.top + boundingBox.height * (1 - 1 / lineHeightMultiplier - baselineShiftMultiplier),
                            )
                        )
                    }
                }
            }
        },
        modifier = Modifier.drawBehind {
            drawPath(selectedPartPaths, style = Fill, color = Color.LightGray)
        }
    )
}

fun TextLayoutResult.getBoundingBoxesForRange(start: Int, end: Int): List<Rect> {
    var prevRect: Rect? = null
    var firstLineCharRect: Rect? = null
    val boundingBoxes = mutableListOf<Rect>()
    for (i in start..end) {
        val rect = getBoundingBox(i)
        val isLastRect = i == end

        // single char case
        if (isLastRect && firstLineCharRect == null) {
            firstLineCharRect = rect
            prevRect = rect
        }

        // `rect.right` is zero for the last space in each line
        // looks like an issue to me, reported: https://issuetracker.google.com/issues/197146630
        if (!isLastRect && rect.right == 0f) continue

        if (firstLineCharRect == null) {
            firstLineCharRect = rect
        } else if (prevRect != null) {
            if (prevRect.bottom != rect.bottom || isLastRect) {
                boundingBoxes.add(
                    firstLineCharRect.copy(right = prevRect.right)
                )
                firstLineCharRect = rect
            }
        }
        prevRect = rect
    }
    return boundingBoxes
}

Result:

like image 139
Philip Dukhov Avatar answered Sep 12 '26 02:09

Philip Dukhov