DEV Community

Cover image for Drawing and painting in Jetpack Compose #2: About text
Thomas Künneth
Thomas Künneth

Posted on

Drawing and painting in Jetpack Compose #2: About text

Welcome to part 2 of Drawing and painting in Jetpack Compose. Today I will be taking a look at text. Given how easy it is to draw dots, lines, arcs and curves, printing some text should be piece of cake, right? Something like

@Composable
fun TextDemo() {
  Canvas(modifier = Modifier.fillMaxWidth().preferredHeight(128.dp),
    onDraw = {
      drawText(Color.Red, center, "Hello Compose")
    })
}
Enter fullscreen mode Exit fullscreen mode

Sadly, though, there is no drawText().

That's too bad.

But wait, Android's native canvas class (android.graphics.Canvas) does have several drawText() versions, one of the simplest ones being drawText(String text, float x, float y, Paint paint). Let's see if we can utilize this.

Please recall that our onDraw() lamba acts as an extension function of androidx.compose.ui.graphics.drawscope.DrawScope. Hence, this interface and accompanying extension functions define what we can call inside our code. As it turns out there is drawIntoCanvas(). This function allows us to draw directly into the underlying androidx.compose.ui.graphics.Canvas. If we peek into its source code we find:

Source code of Canvas.nativeCanvas

Finally.

So here is how we can draw text onto our canvas:

@Composable
fun TextDemo() {
  Canvas(modifier = Modifier.fillMaxWidth().preferredHeight(128.dp),
    onDraw = {
      val paint = android.graphics.Paint()
      paint.textAlign = Paint.Align.CENTER
      paint.textSize = 64f
      paint.color = 0xffff0000.toInt()
      drawIntoCanvas {
        it.nativeCanvas.drawText("Hello", center.x, center.y, paint)
      }
    })
}
Enter fullscreen mode Exit fullscreen mode

Instead of passing a lambda to drawIntoCanvas() you could also write drawContext.canvas.nativeCanvas.drawText(). We'll see this in the next example. As we are using the native canvas, we need native ways to customize output. For example, to set an individual font, put the .ttf file in res/font. Please keep in mind that the filename must meet certain rules (lowercase letters, numbers and the underscore only).

Using a custom font

@Composable
fun C64TextDemo() {
  Canvas(modifier = Modifier.fillMaxWidth().preferredHeight(128.dp),
    onDraw = {
      drawRect(Color(0xff525ce6))
      val paint = Paint()
      paint.textAlign = Paint.Align.CENTER
      paint.textSize = 64f
      paint.color = 0xffb0b3ff.toInt()
      paint.typeface = customTypeface
      drawContext.canvas.nativeCanvas.drawText(
        "HELLO WORLD!",
        center.x, center.y, paint
      )
    })
}
Enter fullscreen mode Exit fullscreen mode

customTypeface is declared like this:

private lateinit var customTypeface: android.graphics.Typeface
Enter fullscreen mode Exit fullscreen mode

And then initialized as follows:

customTypeface = resources.getFont(R.font.c64_pro_mono_style)
Enter fullscreen mode Exit fullscreen mode

Please be careful not to mix platform classes with Compose functions, classes or interfaces. For example there is androidx.compose.ui.text.font.Typeface, which of course cannot be used together with android.graphics.Paint. To separate the composable and the old platform world, I chose a toplevel variable, but you could also pass it to your composables as needed. The wonderful C64 font can by the way be downloaded at style64.org.

Before I close this post, let's pay some more attention to styling. For example, to unterline your text, just add

paint.flags = Paint.UNDERLINE_TEXT_FLAG
Enter fullscreen mode Exit fullscreen mode

Paint.STRIKE_THRU_TEXT_FLAG, well, strikes through the text. Please remember that this is not Compose-specific but ordinary Android framework api.

As Jetpack Compose offers the Text() composable, when would we use text on canvas? Generally I suggest to use Text() whenever possible. If you recall my sin() plotter, which has its own cartesian coordinate system, it may make sense, though, to add labels to its axes using the method I have shown you in this post.

What are your thoughts on using nativeCanvas? Please share them in the comments.


source

Oldest comments (1)

Collapse
 
hakanai profile image
Hakanai

The correct way to do this seems to be to create a Paragraph and then call its paint method, passing in drawContext.canvas.

The way documented in this article assumes Android, so it will not work on desktop.