commonMain.io.github.lyxnx.compose.ui.text.AutoResizableText.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ui Show documentation
Show all versions of ui Show documentation
Jetpack Compose UI extensions and utilities
package io.github.lyxnx.compose.ui.text
import androidx.compose.foundation.text.InlineTextContent
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.NonRestartableComposable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawWithContent
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.drawscope.ContentDrawScope
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.TextLayoutResult
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.sp
/**
* A component similar to a [Text] but will auto resize the text content based on the parameters provided by [fontSizeRange]
* if the text is too large to fit in the container
*
* @param text text to display
* @param fontSizeRange the font size range values to be used to resize the text should it not fit within the container
* at the size given by [style]
* @param style style configuration for the text such as color, font, line height etc.
* @param modifier the [Modifier] to be applied to this layout node
* @param overflowStrategy the strategy used to test whether the text should be resized. By default this is [TextOverflowStrategy.Width] -
* the text will be resized if it overflows the width of the container
* @param color [Color] to apply to the text. If [Color.Unspecified], and [style] has no color set, this will be
* [LocalContentColor].
* @param fontStyle the typeface variant to use when drawing the letters (e.g., italic). See [TextStyle.fontStyle].
* @param fontWeight the typeface thickness to use when painting the text (e.g., [FontWeight.Bold]).
* @param fontFamily the font family to be used when rendering the text. See [TextStyle.fontFamily].
* @param letterSpacing the amount of space to add between each letter. See [TextStyle.letterSpacing].
* @param textDecoration the decorations to paint on the text (e.g., an underline). See [TextStyle.textDecoration].
* @param textAlign the alignment of the text within the lines of the paragraph. See [TextStyle.textAlign].
* @param lineHeight line height for the [Paragraph] in [TextUnit] unit, e.g. SP or EM. See [TextStyle.lineHeight].
* @param overflow how visual overflow should be handled.
* @param softWrap whether the text should break at soft line breaks. If false, the glyphs in the
* text will be positioned as if there was unlimited horizontal space. If [softWrap] is false,
* [overflow] and TextAlign may have unexpected effects.
* @param minLines The minimum height in terms of minimum number of visible lines. It is required
* that 1 <= [minLines] <= [maxLines].
* @param maxLines An optional maximum number of lines for the text to span, wrapping if
* necessary. If the text exceeds the given number of lines, it will be truncated according to
* [overflow] and [softWrap]. It is required that 1 <= [minLines] <= [maxLines].
* @param inlineContent a map storing composables that replaces certain ranges of the text, used to
* insert composables into text layout. See [InlineTextContent].
* @param onTextLayout a callback that is invoked when the text layout is ready
* @param onDrawContent a callback that is invoked when the text is ready to be drawn, just before
* [drawContent][ContentDrawScope.drawContent] is called
*/
@Composable
@NonRestartableComposable
public fun AutoResizableText(
text: String,
fontSizeRange: FontSizeRange,
style: TextStyle,
modifier: Modifier = Modifier,
overflowStrategy: TextOverflowStrategy = TextOverflowStrategy.Width,
color: Color = Color.Unspecified,
fontStyle: FontStyle? = null,
fontWeight: FontWeight? = null,
fontFamily: FontFamily? = null,
letterSpacing: TextUnit = TextUnit.Unspecified,
textDecoration: TextDecoration? = null,
textAlign: TextAlign? = null,
lineHeight: TextUnit = TextUnit.Unspecified,
overflow: TextOverflow = TextOverflow.Clip,
softWrap: Boolean = true,
minLines: Int = 1,
maxLines: Int = Int.MAX_VALUE,
inlineContent: Map = mapOf(),
onTextLayout: (TextLayoutResult) -> Unit = {},
onDrawContent: () -> Unit = {}
) {
AutoResizableText(
text = AnnotatedString(text),
fontSizeRange = fontSizeRange,
style = style,
modifier = modifier,
overflowStrategy = overflowStrategy,
color = color,
fontStyle = fontStyle,
fontWeight = fontWeight,
fontFamily = fontFamily,
letterSpacing = letterSpacing,
textDecoration = textDecoration,
textAlign = textAlign,
lineHeight = lineHeight,
overflow = overflow,
softWrap = softWrap,
minLines = minLines,
maxLines = maxLines,
inlineContent = inlineContent,
onTextLayout = onTextLayout,
onDrawContent = onDrawContent
)
}
/**
* A component similar to a [Text] but will auto resize the text content based on the parameters provided by [fontSizeRange]
* if the text is too large to fit in the container
*
* @param text text to display
* @param fontSizeRange the font size range values to be used to resize the text should it not fit within the container
* at the size given by [style]
* @param style style configuration for the text such as color, font, line height etc.
* @param modifier the [Modifier] to be applied to this layout node
* @param overflowStrategy the strategy used to test whether the text should be resized. By default this is [TextOverflowStrategy.Width] -
* the text will be resized if it overflows the width of the container
* @param color [Color] to apply to the text. If [Color.Unspecified], and [style] has no color set, this will be
* [LocalContentColor].
* @param fontStyle the typeface variant to use when drawing the letters (e.g., italic). See [TextStyle.fontStyle].
* @param fontWeight the typeface thickness to use when painting the text (e.g., [FontWeight.Bold]).
* @param fontFamily the font family to be used when rendering the text. See [TextStyle.fontFamily].
* @param letterSpacing the amount of space to add between each letter. See [TextStyle.letterSpacing].
* @param textDecoration the decorations to paint on the text (e.g., an underline). See [TextStyle.textDecoration].
* @param textAlign the alignment of the text within the lines of the paragraph. See [TextStyle.textAlign].
* @param lineHeight line height for the [Paragraph] in [TextUnit] unit, e.g. SP or EM. See [TextStyle.lineHeight].
* @param overflow how visual overflow should be handled.
* @param softWrap whether the text should break at soft line breaks. If false, the glyphs in the
* text will be positioned as if there was unlimited horizontal space. If [softWrap] is false,
* [overflow] and TextAlign may have unexpected effects.
* @param minLines The minimum height in terms of minimum number of visible lines. It is required
* that 1 <= [minLines] <= [maxLines].
* @param maxLines An optional maximum number of lines for the text to span, wrapping if
* necessary. If the text exceeds the given number of lines, it will be truncated according to
* [overflow] and [softWrap]. It is required that 1 <= [minLines] <= [maxLines].
* @param inlineContent a map storing composables that replaces certain ranges of the text, used to
* insert composables into text layout. See [InlineTextContent].
* @param onTextLayout a callback that is invoked when the text layout is ready
* @param onDrawContent a callback that is invoked when the text is ready to be drawn, just before
* [drawContent][ContentDrawScope.drawContent] is called
*/
@Composable
public fun AutoResizableText(
text: AnnotatedString,
fontSizeRange: FontSizeRange,
style: TextStyle,
modifier: Modifier = Modifier,
overflowStrategy: TextOverflowStrategy = TextOverflowStrategy.Width,
color: Color = Color.Unspecified,
fontStyle: FontStyle? = null,
fontWeight: FontWeight? = null,
fontFamily: FontFamily? = null,
letterSpacing: TextUnit = TextUnit.Unspecified,
textDecoration: TextDecoration? = null,
textAlign: TextAlign? = null,
lineHeight: TextUnit = TextUnit.Unspecified,
overflow: TextOverflow = TextOverflow.Clip,
softWrap: Boolean = true,
minLines: Int = 1,
maxLines: Int = Int.MAX_VALUE,
inlineContent: Map = mapOf(),
onTextLayout: (TextLayoutResult) -> Unit = {},
onDrawContent: () -> Unit = {}
) {
var fontSizeValue by remember { mutableFloatStateOf(fontSizeRange.max.value) }
var readyToDraw by remember { mutableStateOf(false) }
Text(
text = text,
color = color,
fontStyle = fontStyle,
fontWeight = fontWeight,
fontFamily = fontFamily,
letterSpacing = letterSpacing,
textDecoration = textDecoration,
textAlign = textAlign,
lineHeight = lineHeight,
overflow = overflow,
softWrap = softWrap,
minLines = minLines,
maxLines = maxLines,
style = style,
fontSize = fontSizeValue.sp,
onTextLayout = {
if (overflowStrategy.shouldResize(it) && !readyToDraw) {
val nextFontSizeValue = fontSizeValue - fontSizeRange.step.value
if (nextFontSizeValue <= fontSizeRange.min.value) {
fontSizeValue = fontSizeRange.min.value
readyToDraw = true
} else {
fontSizeValue = nextFontSizeValue
}
} else {
readyToDraw = true
}
onTextLayout(it)
},
inlineContent = inlineContent,
modifier = modifier.drawWithContent {
if (readyToDraw) {
onDrawContent()
drawContent()
}
}
)
}
/**
* Represents a font size range for an [AutoResizableText] component
*
* @property min the minimum allowed text size
* @property max the maximum allowed text size - usually this is the desired text size
* @property step unit to step size down by when content is too large for its container
*/
public data class FontSizeRange(
val min: TextUnit,
val max: TextUnit,
val step: TextUnit = DEFAULT_TEXT_STEP
) {
init {
require(min <= max) { "min should be less than max, $this" }
require(step.value > 0) { "step should be greater than 0, $this" }
}
public companion object {
/**
* The default text step to size down by when content is too large for the container
*/
public val DEFAULT_TEXT_STEP: TextUnit = 1.sp
}
}
/**
* Represents a text overflow strategy used for [AutoResizableText] components
*/
public sealed interface TextOverflowStrategy {
/**
* Strategy that will resize the text if it overflows the width of the container
*/
public data object Width : TextOverflowStrategy {
override fun shouldResize(result: TextLayoutResult): Boolean = result.didOverflowWidth
}
/**
* Strategy that will resize the text if it overflows the height of the container
*/
public data object Height : TextOverflowStrategy {
override fun shouldResize(result: TextLayoutResult): Boolean = result.didOverflowHeight
}
/**
* Returns whether the text should be resized based on the given [result]
*/
public fun shouldResize(result: TextLayoutResult): Boolean
/**
* Combines 2 strategies together, resizing the text if either of the strategies would [resize][shouldResize] the text
*/
public infix fun or(other: TextOverflowStrategy): TextOverflowStrategy = Combined {
this.shouldResize(it) || other.shouldResize(it)
}
/**
* Combines 2 strategies together, resizing the text if and only if both of the strategies would [resize][shouldResize] the text
*/
public infix fun and(other: TextOverflowStrategy): TextOverflowStrategy = Combined {
this.shouldResize(it) && other.shouldResize(it)
}
}
private data class Combined(private val predicate: (TextLayoutResult) -> Boolean) : TextOverflowStrategy {
override fun shouldResize(result: TextLayoutResult): Boolean = predicate(result)
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy