JetBrains / compose-multiplatform

Compose Multiplatform, a modern UI framework for Kotlin that makes building performant and beautiful user interfaces easy and enjoyable.
https://jetbrains.com/lp/compose-multiplatform
Apache License 2.0
15.54k stars 1.13k forks source link

Outlined text #4221

Open Woren opened 5 months ago

Woren commented 5 months ago

I want to create text with for example black color outline and red fill color (see attachment). I can use code as below but in that way I can set only outline color and fill color is transparent / none. I'm now using ugly workaround with one Text red and second Text larger with black outline drawn on the same place. This is not working correctly on some display densities. So the request for enhancement is to support outlined text with only one Text and be able to set color of outline and fill color.

Why is this important? It's used for example for better readability of subtitles on different backgrounds (white text, black outline). The difference in better readability can be enormous.

Text(
    text = "Sample",
    color = "<only one color>"
    style = TextStyle.Default.copy(
        fontSize = 64.sp,
        drawStyle = Stroke(
            miter = 10f,
            width = 5f,
            join = StrokeJoin.Round
        )
    )
)

Sample_outline

m-sasha commented 5 months ago

This isn't supported by the underlying text rendering engine (on Android too). Drawing the text twice is the way to go here.

As to why it looks bad on some densities, that's worth investigating. Perhaps the outline text should not be larger.

Woren commented 5 months ago

That are bad news that there is no support for this directly. But thanks for info, I hope it will be added in the future.

I have prepared minimal example of used code. I believe it should produce same good or bad (if I messed up something) result on each platform. But the results are very different even if outline width is transformed to sp to have same "scale" as font size. Changing outline width based directly on density helps a little but it's just added "magic number" which needs to be tested. Also sorry for different sizes and visible pixels caused by resizing to prepare all-in-one screenshot. Problem are overlaps inside the characters which are clearly visible and mostly on Samsung device.

Used device - density Desktop PC - 2.0 Samsung Tab S9 Ultra -1.875 Pixel 6 emulator - 3.5 iPhone 15 Pro Max - 3.0

Outlined_text_examples

@Composable
fun OutlinedText() {
    Box(
        modifier = Modifier.background(Color.White)
            .padding(8.dp)
    )
    {
        val textSize = 20.sp
        val textOutlineWidth: Float
        with(LocalDensity.current) {
            textOutlineWidth = 2f.toSp().value // 2f without conversion - same result
        }

        val liningStyle = TextStyle.Default.copy(
            drawStyle = Stroke(
                width = textOutlineWidth,
            ),
            fontSize = textSize,
        )

        val fillStyle = liningStyle.copy(drawStyle = Fill)
        val fillColor = Color.Red
        val liningColor = Color.Black

        Text(
            text = "Sample",
            color = fillColor,
            style = fillStyle,
        )

        Text(
            text = "Sample",
            color = liningColor,
            style = liningStyle,
        )
    }
}
paulocoutinhox commented 4 months ago

I need stroke effect too, but it is impossible: https://github.com/paulocoutinhox/dataxow/blob/main/src/main/kotlin/com/dataxow/ui/components/AutoSizeText.kt