Compose drawText going off screen

1.5k Views Asked by At

I'm using the following code to draw text onto a Canvas, but the text as you can see from the photo is going off the screen, rather than adhering to the width parameters i issue to the Canvas, why is this happening?

This doesn't seem to happen if i use the older android canvas, possible compose bug?

 Box(
    modifier = Modifier
        .fillMaxWidth()
) {
    Canvas(
        modifier = Modifier.width(500.dp),
        onDraw = {
            drawIntoCanvas {
                it.nativeCanvas.drawText(
                    text,
                    0f,
                    120.dp.toPx(),
                    textPaintStroke(context = context)
                )
                it.nativeCanvas.drawText(
                    text,
                    0f,
                    120.dp.toPx(),
                    textPaint(context = context)
                )
            }
        }
    )
}

fun textPaintStroke(
    context: Context
): NativePaint {
    val customTypeface = ResourcesCompat.getFont(context, R.font.baloo2_semi_bold)

    return Paint().asFrameworkPaint().apply {
        isAntiAlias = true
        style = android.graphics.Paint.Style.STROKE
        textSize = 64f
        color = android.graphics.Color.BLACK
        strokeWidth = 12f
        strokeMiter= 10f
        strokeJoin = android.graphics.Paint.Join.ROUND
        typeface = customTypeface
    }
}

fun textPaint(
    context: Context
): NativePaint {
    val customTypeface = ResourcesCompat.getFont(context, R.font.baloo2_semi_bold)

    return Paint().asFrameworkPaint().apply {
        isAntiAlias = true
        style = android.graphics.Paint.Style.FILL
        textSize = 64f
        color = android.graphics.Color.WHITE
        typeface = customTypeface
    }
}

enter image description here

4

There are 4 best solutions below

0
On BEST ANSWER

The 1.4.0-alpha01 added a DrawStyle parameter to TextStyle functions that enables drawing outlined text.

You can use something like:

val longText by remember {
    mutableStateOf("Choose the flash card which matches...!")
}

Box(
    modifier = Modifier
        .fillMaxWidth()
) {

    Canvas(modifier = Modifier.fillMaxSize()){
        //....
    }
    Text(
        text = longText,
        style = TextStyle.Default.copy(
            fontSize = 64.sp,
            drawStyle = Stroke(
                miter = 10f,
                width = 5f,
                join = StrokeJoin.Round
            )
        )
    )
}

enter image description here

3
On

You can use the following feature by updating your Compose to version 1.3.0:

import androidx.compose.foundation.Canvas
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.width
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.ExperimentalTextApi
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.drawText
import androidx.compose.ui.text.rememberTextMeasurer
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp

@OptIn(ExperimentalTextApi::class)
@Composable
fun Test() {
    val text by remember {
        mutableStateOf("Choose the flash with matechest!")
    }
    Box(
        modifier = Modifier
            .fillMaxSize()
            .background(Color.Green)
    ) {
        val textMeasurer = rememberTextMeasurer()
        Canvas(
            modifier = Modifier.width(10.dp),
            onDraw = {
                val textResult = textMeasurer.measure(
                    AnnotatedString(
                        text, spanStyle = SpanStyle(
                            fontSize = 24.sp
                        )
                    )
                )
                drawText(textResult)
            }
        )
    }
}

In this way, you can style the text and...

Although this function is now released as ExperimentalTextApi, but according to my one-year experience of using compose, I assure you that there will be no problem.

More information: As you know, before version 1.3.0 it was not possible to draw text on the canvas by the compose APIs.

This feature is available in the new version. In the new API you give any text you want, even emoji, to a composabls called rememberTextMeasurer().

First Step:

   val textMeasurer = rememberTextMeasurer()

Second Step: create new variable and call measure method for canvas calculation for demonstrate what to draw, where to draw and etc.

val textResult1 = textMeasurer.measure(
                    AnnotatedString(
                        text, spanStyle = SpanStyle(
                            fontSize = 24.sp,
                            shadow = Shadow(color = Color.Black),
                            color = Color.White
                        )
                    )
                )

Third Step: call drawText(textResult1) for drawing text in canvas drawText(textResult1)

you can create many variable also

Then you measure your text in your drawing scope.

With this, all the necessary operations, including padding, etc., are calculated by the api itself, and you only get involved in what you want.

You can make several copies of textResults with the styles you want and call different drawTexts to draw the text you want with different styles.

I hope it will work for you If my answer helped you, please choose my answer as the correct answer

0
On

In jetpack compose DrawScope doesn't have to abide any dimension you set via Modifier unless you use Modifier.clip or Modifier.clipToBounds. You can draw anything out of canvas bounds which is what you experience with draw text.

You can have a Composable with 0 width and 0 height but you can draw a rectangle with any size you want or offset any drawing out of Composable's borders and by default they are not cut of you just don't see them if they are out of screen bounds.

For example

Column {
    Canvas(
        modifier = Modifier
            .width(50.dp)
            .height(50.dp)
            .border(2.dp, Color.Red),
        onDraw = {
            drawCircle(
                color = Color.Green,
                center = Offset(500f, 300f),
                radius = 200f
            )
        }
    )

    Text("Hello World",
        modifier = Modifier.drawWithContent {
            drawContent()
            drawRect(Color.Cyan, style = Stroke(1.dp.toPx()))
            drawRoundRect(
                Color.Blue,
                topLeft = Offset(0f, 300f),
                size = Size(100f, 100f)
            )

        }
    )
}

enter image description here

Canvas draws green Circle out of bounds and with Text you will see that blue rectangle is drawn outside of Text bounds as well.

Keeping this in mind you can conclude that it's size only meaningful to do some calculation using it but not to limite text or any drawing.

You can solve this using rememberTextMeasurer() to measure text in a loop which words should fit in first line using current line width versus canvas width.

0
On

you can set overflow in drawText when text is too long to fit.

val textMeasure = rememberTextMeasurer()

Canvas(modifier = Modifier
        .size(150.dp)
        .background(Color.Cyan)
        .padding(5.dp), onDraw = {
        drawText(
            textMeasurer = textMeasure,
            text = "Long Long Long Text Is Here Hurrey " +
                    "Long Long Long Text Is Here Hurrey " +
                    "Long Long Long Text Is Here Hurrey",
            overflow = TextOverflow.Ellipsis,
        )
    })

OUTPUT

enter image description here