Travis Griggs
02/06/2024, 6:05 PMonTextLayout
closure. But I was asking myself, why not just use a Canvas rather than a Text if I'm just after simplified single line Text drawing, and want to specify a "minimum font size". Curious why this would be a bad idea? Or if I'm missing obvious things? Code on threadTravis Griggs
02/06/2024, 6:06 PM@Composable
fun Label(text: String, modifier: Modifier = Modifier, style: TextStyle = LocalTextStyle.current, lowerSize: TextUnit = 10.sp) {
val typeSetter = rememberTextMeasurer()
var layout = typeSetter.measure(text = text, style = style, maxLines = 1)
var (targetWidth, targetHeight) = with(LocalDensity.current) { layout.size.width.toDp() to layout.size.height.toDp() }
Canvas(modifier = modifier.size(targetWidth, targetHeight)) {
var layout = typeSetter.measure(text = text, style = style, maxLines = 1)
if (layout.size.width > size.width) {
// use minimum first it will tell us A) isn't going to fit even that small, and B give use a second point to interpolate with
val smallestLayout = typeSetter.measure(text = text, style = style.copy(fontSize = lowerSize))
layout = if (smallestLayout.size.width > size.width) {
typeSetter.measure(
text = text,
style = style.copy(fontSize = lowerSize),
maxLines = 1,
overflow = TextOverflow.Ellipsis,
constraints = Constraints.fixedWidth(size.width.toInt())
)
} else {
// good old y = mx + b, but (w)idth is x, and s(p) is y,
// so p = m*w + b, m = (p1 - p2) / (w1 - w2), and b = p - m*w
val m = (style.fontSize.value - lowerSize.value) / (layout.size.width - smallestLayout.size.width).toFloat()
val b = style.fontSize.value - (m * layout.size.width.toFloat())
val p = (m * size.width.toFloat() + b).sp
typeSetter.measure(text = text, style = style.copy(fontSize = p), maxLines = 1)
}
}
val left = when(style.textAlign) {
TextAlign.Center -> (size.width - layout.size.width) / 2
TextAlign.Right, TextAlign.End -> size.width - layout.size.width
else -> 0f
}
drawText(layout, color = style.color, topLeft = Offset(x = left, y = (size.height - layout.size.height) / 2))
}
}
Kirill Grouchnikov
02/06/2024, 6:08 PMHalil Ozercan
02/06/2024, 6:32 PMTextMeasurer
to do your binary search of the font size in a single layout phase then render your Text composable when the font size is ready.Travis Griggs
02/06/2024, 8:53 PMfun MyLabel(text: String, modifier: Modifier = Modifier, style: TextStyle = LocalTextStyle.current, lowerSize: TextUnit = 10.sp) {
var bestStyle by remember(text, style) { mutableStateOf(style) }
var bestOverflow by remember(text, style) { mutableStateOf(TextOverflow.Clip) }
Layout(content = {
Text(text, modifier, bestStyle, bestOverflow)
}, modifier = modifier) { m, c ->
... SEARCH FOR BEST style AND OVERLFOW with a TEXT MEASURE here
meausre and place it as a pass through basically}
}
?dorche
02/06/2024, 9:27 PMTextMeasurer.measure()
until you get false for hasVisualOverflow
. Assuming you pass the correct constraints it will work fine.Travis Griggs
02/06/2024, 9:28 PM@Composable
fun Label2(text: String, modifier: Modifier = Modifier, style: TextStyle = LocalTextStyle.current, lowerSize: TextUnit = 10.sp) {
var bestStyle by remember(text, style) { mutableStateOf(style) }
var bestOverflow by remember(text, style) { mutableStateOf(TextOverflow.Clip) }
val typeSetter = rememberTextMeasurer()
val density = LocalDensity.current
Layout(content = {
Text(text = text, style = bestStyle, overflow = bestOverflow, softWrap = false, maxLines = 1)
}, modifier = modifier) { measureables, constraints ->
var fullParagraph = typeSetter.measure(text = text, style = style, maxLines = 1)
if (fullParagraph.width > constraints.maxWidth) {
// use minimum first it will tell us A) isn't going to fit even that small, and B) give use a second point to interpolate with
val smallStyle = style.copy(fontSize = lowerSize)
val minParagraph = typeSetter.measure(text = text, style = smallStyle, maxLines = 1)
if (minParagraph.width > constraints.maxWidth) {
bestStyle = smallStyle
bestOverflow = TextOverflow.Ellipsis
} else {
// good old y = mx + b, but (w)idth is x, and s(p) is y,
// so p = m*w + b, m = (p1 - p2) / (w1 - w2), and b = p - m*w
val m = (style.fontSize.value - lowerSize.value) / (fullParagraph.width - minParagraph.width).toFloat()
val b = style.fontSize.value - (m * fullParagraph.width.toFloat())
val p = (m * constraints.maxWidth.toFloat() + b).sp
p.logged("smallSize")
bestStyle = style.copy(fontSize = p)
}
}
val placeble = measureables.first().measure(constraints.copy(minWidth = 0, minHeight = 0))
layout(constraints.maxWidth, with(density) { bestStyle.lineHeight.roundToPx() }) {
placeble.place(0, 0)
}
}
}
Kirill Grouchnikov
02/06/2024, 9:34 PMBoxWithConstraints
or a vanilla SubcomposeLayout
, @Halil Ozercan? Sounds like this needs to have two passes, one to determine the size of the text, and the other to add the configured Text
composable as a child.dorche
02/06/2024, 9:39 PMText
parameters that you need + Constraints
and have it return a new TextStyle
that fits the constraints. Then you move the problem to how to pass the correct constraints, which I've done via BoxWithConstraints
Halil Ozercan
02/07/2024, 1:12 PMSubcomposeLayout/BoxWithConstraints
.Travis Griggs
02/07/2024, 6:27 PMHalil Ozercan
02/08/2024, 12:45 PMText
could figure out the correct font size during layout, then draw the calculated TextLayoutResult in a single frame. Unfortunately Text
only accepts a single fontSize during composition. Since you have to figure out the right fontSize before composition ends, you also need to know your layout constraints, hence the need for BoxWithConstraints
.dorche
02/08/2024, 12:50 PMBoxWithConstraints
less impactful for performance if it's at a very leaf node? i.e. in this case, out of the whole layout only some inner most part (Text
) needs to go in it, is that less of a sin compared to using BoxWithConstraints
at some top level?Halil Ozercan
02/08/2024, 12:52 PMshikasd
02/08/2024, 4:40 PMBoxWithConstraints
creates a composition regardless of its place in hierarchy.
Consider using Modifier.layout instead, if possibleshikasd
02/08/2024, 4:43 PMdorche
02/08/2024, 4:54 PMshikasd
02/08/2024, 6:47 PMColton Idle
02/10/2024, 3:07 AM