Shapes and Drawing

Use backgrounds, borders, clips, and Canvas drawing to create rich PDF content.


Shapes and drawing example Shapes and drawing example — download PDF


Backgrounds

Box(Modifier.fillMaxWidth().height(60.dp).background(Color(0xFF1565C0))) {
    Text("White on blue", color = Color.White, modifier = Modifier.padding(16.dp))
}

Semi-transparent backgrounds

Box(Modifier.size(100.dp).background(Color.Red.copy(alpha = 0.5f)))

Borders

// Simple border
Box(Modifier.size(100.dp).border(2.dp, Color.Black))

// Rounded border
Box(Modifier.size(100.dp).border(2.dp, Color.Blue, RoundedCornerShape(8.dp)))

Rounded corners

Uniform corners

Standard RoundedCornerShape works perfectly for uniform corners:

Box(
    Modifier
        .size(100.dp)
        .clip(RoundedCornerShape(12.dp))
        .background(Color.Blue)
)

Circle

Box(
    Modifier
        .size(80.dp)
        .clip(CircleShape)
        .background(Color.Red)
)

Non-uniform corners (PdfRoundedCornerShape)

When corners have different radii, use PdfRoundedCornerShape for correct PDF output:

Box(
    Modifier
        .size(100.dp)
        .clip(PdfRoundedCornerShape(topStart = 24.dp, bottomEnd = 24.dp))
        .background(Color(0xFFF57C00))
)

Standard RoundedCornerShape with non-uniform corners will render all corners identically in vector mode. This is a Skia SVGCanvas limitation – it serializes rounded rects as <rect rx ry> which only supports a single radius. Use PdfRoundedCornerShape when corners differ.

Wrapping existing shapes

Use asPdfSafe() to wrap any shape:

val myShape = RoundedCornerShape(topStart = 16.dp, bottomEnd = 16.dp)
Box(Modifier.clip(myShape.asPdfSafe()).background(Color.Green))

Canvas drawing

Use Canvas for custom vector drawing:

Canvas(Modifier.fillMaxWidth().height(200.dp)) {
    // Filled circle
    drawCircle(
        color = Color(0xFF1976D2),
        radius = 40f,
        center = Offset(60f, 80f),
    )

    // Stroked circle
    drawCircle(
        color = Color(0xFF388E3C),
        radius = 40f,
        center = Offset(160f, 80f),
        style = Stroke(width = 3f),
    )

    // Rectangle
    drawRect(
        color = Color(0xFFF57C00),
        topLeft = Offset(220f, 40f),
        size = Size(80f, 80f),
    )

    // Line
    drawLine(
        color = Color.Black,
        start = Offset(330f, 40f),
        end = Offset(420f, 120f),
        strokeWidth = 2f,
    )

    // Arc (pie slice)
    drawArc(
        color = Color(0xFFD32F2F),
        startAngle = 0f,
        sweepAngle = 270f,
        useCenter = true,
        topLeft = Offset(60f, 140f),
        size = Size(80f, 80f),
    )

    // Custom path
    val triangle = Path().apply {
        moveTo(240f, 150f)
        lineTo(200f, 230f)
        lineTo(280f, 230f)
        close()
    }
    drawPath(triangle, Color(0xFF7B1FA2))
}

All Canvas drawing operations are converted to vector paths in the PDF (in VECTOR mode).


Opacity

// Via color alpha
Box(Modifier.size(100.dp).background(Color.Blue.copy(alpha = 0.3f)))

// Overlapping semi-transparent elements
Box(Modifier.size(200.dp)) {
    Box(Modifier.size(120.dp).offset(x = 0.dp).background(Color.Red.copy(alpha = 0.5f)))
    Box(Modifier.size(120.dp).offset(x = 60.dp, y = 40.dp).background(Color.Blue.copy(alpha = 0.5f)))
}

See also