I'm trying to center a text within a rectangle according to its height and width, but it comes out off-center. Additionally, I need the text to adjust according to the size of this rectangle. However, I'm not finding very good solutions.
Would anyone know where the error might be?
Result:
My code for help:
@Composable
internal fun ScreenShotDrawSpeech(
bubbleDomain: ImmutableList<SpeechBubbleDomain>,
modifier: Modifier = Modifier,
) {
val textMeasurer = rememberTextMeasurer()
val zoom = rememberZoomableState()
Canvas(
modifier = modifier
.fillMaxSize()
.zoomable(zoom),
onDraw = {
bubbleDomain.forEach { prediction ->
drawSpeechBoundingBox(text = prediction.originalText, boundingBox = prediction.rect, textMeasurer = textMeasurer)
}
},
)
}
private fun DrawScope.drawSpeechBoundingBox(
boundingBox: Rect,
text: String,
textMeasurer: TextMeasurer,
) {
val currentWidth = textMeasurer.measure(text).size.width
val currentHeight = textMeasurer.measure(text).size.height
val desiredWidth = boundingBox.width()
val desiredHeight = boundingBox.height()
val centerTextY = (boundingBox.height() - currentHeight) / 2.5
val font = calculateScaledFontSize(
currentWidth = currentWidth,
currentHeight = currentHeight,
desiredWidth = desiredWidth,
desiredHeight = desiredHeight,
minFontSize = 12.sp.toPx(),
maxFontSize = 14.sp.value,
text = text,
)
val style = TextStyle(
fontSize = font.sp,
fontFamily = FontFamily(Font(R.font.manga_master_bb)),
color = Color.Black,
background = Color.White,
textAlign = TextAlign.Center,
fontWeight = FontWeight.Bold,
)
drawOval(
color = Color.White,
topLeft = Offset(boundingBox.left.toFloat(), boundingBox.top.toFloat()),
size = Size(boundingBox.width().toFloat(), boundingBox.height().toFloat()),
)
drawOval(
color = Color.Black,
style = Stroke(width = 2f),
topLeft = Offset(boundingBox.left.toFloat(), boundingBox.top.toFloat()),
size = Size(boundingBox.width().toFloat(), boundingBox.height().toFloat()),
)
drawText(
textMeasurer = textMeasurer,
style = style,
size = Size(boundingBox.width().toFloat(), boundingBox.height().toFloat()),
topLeft = Offset(
boundingBox.left.toFloat(),
boundingBox.top.toFloat() + centerTextY.toFloat(),
),
text = text.uppercase(),
)
}
@Suppress("LongParameterList")
private fun calculateScaledFontSize(
currentWidth: Int,
currentHeight: Int,
desiredWidth: Int,
desiredHeight: Int,
minFontSize: Float,
maxFontSize: Float,
text: String,
): Float {
val widthScaleFactor = minFontSize * desiredWidth / currentWidth
val heightScaleFactor = minFontSize * desiredHeight / currentHeight
return if (min(widthScaleFactor, heightScaleFactor) >= maxFontSize) {
if (text.length <= 10) {
min(widthScaleFactor, heightScaleFactor) / 3
} else {
min(widthScaleFactor, heightScaleFactor) / 2
}
} else {
min(widthScaleFactor, heightScaleFactor)
}
}
You're calculating the Y-coordinate for centering the text using a fixed value 2.5 which can cause the text to be off-center.
@Composable
internal fun ScreenShotDrawSpeech(
bubbleDomain: ImmutableList<SpeechBubbleDomain>,
modifier: Modifier = Modifier,
) {
val textMeasurer = rememberTextMeasurer()
val zoom = rememberZoomableState()
Canvas(
modifier = modifier
.fillMaxSize()
.zoomable(zoom),
onDraw = {
bubbleDomain.forEach { prediction ->
drawSpeechBoundingBox(text = prediction.originalText, boundingBox = prediction.rect, textMeasurer = textMeasurer)
}
},
)
}
private fun DrawScope.drawSpeechBoundingBox(
boundingBox: Rect,
text: String,
textMeasurer: TextMeasurer,)
{
val currentSize = textMeasurer.measure(text).size
val desiredSize = Size(boundingBox.width(), boundingBox.height())
// Calculate the font size to fit both width and height
val font = calculateScaledFontSize(currentSize, desiredSize)
val style = TextStyle(
fontSize = font.sp,
fontFamily = FontFamily(Font(R.font.manga_master_bb)),
color = Color.Black,
background = Color.White,
textAlign = TextAlign.Center,
fontWeight = FontWeight.Bold,
)
// Draw bounding box
drawOval(
color = Color.White,
topLeft = Offset(boundingBox.left.toFloat(), boundingBox.top.toFloat()),
size = Size(boundingBox.width().toFloat(), boundingBox.height().toFloat()),
)
drawOval(
color = Color.Black,
style = Stroke(width = 2f),
topLeft = Offset(boundingBox.left.toFloat(), boundingBox.top.toFloat()),
size = Size(boundingBox.width().toFloat(), boundingBox.height().toFloat()),
)
// Calculate the position to center the text within the bounding box
val centerTextX = boundingBox.left + (boundingBox.width() - currentSize.width * font) / 2
val centerTextY = boundingBox.top + (boundingBox.height() - currentSize.height * font) / 2
// Draw text centered within the bounding box
drawIntoCanvas {
it.nativeCanvas.drawText(
text.uppercase(),
centerTextX,
centerTextY,
Paint().apply {
this.textSize = font.sp
this.color = Color.Black.toArgb()
this.typeface = Typeface.create(Font(R.font.manga_master_bb).toFamily().first(), Typeface.BOLD)
}
)
}
//Updated font scaling function
private fun calculateScaledFontSize(
currentSize: Size,
desiredSize: Size,
): Float {
val widthScaleFactor = desiredSize.width / currentSize.width
val heightScaleFactor = desiredSize.height / currentSize.height
// Choose the smaller scale factor to ensure the text fits within the bounding box
val scaleFactor = min(widthScaleFactor, heightScaleFactor)
// Cap the scale factor to avoid excessive font enlargement
return min(1f, scaleFactor) // Adjust the cap value as needed
}
I have updated your code with sample I was having from in code, please give it a try.