Merge "Text doesn't crash when text is very very tall" into androidx-main
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/modifiers/LayoutUtilsKtTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/modifiers/LayoutUtilsKtTest.kt
index 6d8eba5..2935ce6 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/modifiers/LayoutUtilsKtTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/modifiers/LayoutUtilsKtTest.kt
@@ -89,4 +89,22 @@
val subject = finalMaxLines(false, TextOverflow.Ellipsis, 4)
assertThat(subject).isEqualTo(1)
}
+
+ @Test
+ fun fixedCoercedNeverCrashes() {
+ var a = 0
+ var b = 0
+ while (1 shl a > 0) {
+ val width = 1 shl a
+ while (1 shl b > 0) {
+ val height = 1 shl b
+ /* shouldn't crash */
+ val constraints = Constraints.fixedCoerceHeightAndWidthForBits(width, height)
+ println("$width $height => $constraints")
+ b++
+ }
+ b = 0
+ a++
+ }
+ }
}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/modifiers/TextAnnotatedStringNodeTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/modifiers/TextAnnotatedStringNodeTest.kt
index c1fc291..dda2f9a 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/modifiers/TextAnnotatedStringNodeTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/modifiers/TextAnnotatedStringNodeTest.kt
@@ -2,7 +2,11 @@
import android.content.Context
import androidx.compose.foundation.Canvas
+import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.text.BasicText
+import androidx.compose.foundation.verticalScroll
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.drawscope.ContentDrawScope
import androidx.compose.ui.graphics.drawscope.DrawScope
@@ -10,6 +14,7 @@
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.createFontFamilyResolver
+import androidx.compose.ui.unit.sp
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
import androidx.test.platform.app.InstrumentationRegistry
@@ -44,4 +49,17 @@
}
rule.waitForIdle()
}
+
+ @Test
+ fun exceedsMaxConstraintSize_doesNotCrash() {
+ rule.setContent {
+ val state = rememberScrollState()
+ Column(Modifier.verticalScroll(state)) {
+ BasicText(
+ text = AnnotatedString("text\n".repeat(10_000)),
+ style = TextStyle(fontSize = 50.sp),
+ )
+ }
+ }
+ }
}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/modifiers/TextStringSimpleNodeTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/modifiers/TextStringSimpleNodeTest.kt
index ac0b381..7cd8687 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/modifiers/TextStringSimpleNodeTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/modifiers/TextStringSimpleNodeTest.kt
@@ -19,7 +19,11 @@
import android.content.Context
import android.graphics.Typeface
import androidx.compose.foundation.Canvas
+import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.text.BasicText
+import androidx.compose.foundation.verticalScroll
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.geometry.Size
@@ -37,6 +41,7 @@
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.font.createFontFamilyResolver
import androidx.compose.ui.text.font.toFontFamily
+import androidx.compose.ui.unit.sp
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
import androidx.test.platform.app.InstrumentationRegistry
@@ -81,6 +86,19 @@
rule.waitForIdle()
}
+ @Test
+ fun exceedsMaxConstraintSize_doesNotCrash() {
+ rule.setContent {
+ val state = rememberScrollState()
+ Column(Modifier.verticalScroll(state)) {
+ BasicText(
+ text = "text\n".repeat(10_000),
+ style = TextStyle(fontSize = 50.sp),
+ )
+ }
+ }
+ }
+
// TODO(b/279797016) re-enable this test, and add a path for AnnotatedString
@Ignore("b/279797016 drawBehind is currently broken in tot")
@OptIn(ExperimentalCoroutinesApi::class)
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/LayoutUtils.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/LayoutUtils.kt
index 93823de..d1ce33a 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/LayoutUtils.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/LayoutUtils.kt
@@ -86,3 +86,37 @@
val overwriteMaxLines = !softWrap && overflow == TextOverflow.Ellipsis
return if (overwriteMaxLines) 1 else maxLinesIn.coerceAtLeast(1)
}
+
+private const val BigConstraintValue = (1 shl 18) - 1
+private const val MediumConstraintValue = (1 shl 16) - 1
+private const val SmallConstraintValue = (1 shl 15) - 1
+private const val TinyConstraintValue = (1 shl 13) - 1
+
+/**
+ * Make constraints that never throw from being too large. Prefer to keep accurate width information
+ * first, then constrain height based on the size of width.
+ *
+ * This will return a Constraint with the same or smaller dimensions than the passed (width, height)
+ *
+ * see b/312294386 for more details
+ *
+ * This particular logic is text specific, so not generalizing.
+ *
+ * @param width desired width (has priority)
+ * @param height desired height (uses the remaining bits after width)
+ *
+ * @return a safe Constraint that never throws for running out of bits
+ */
+internal fun Constraints.Companion.fixedCoerceHeightAndWidthForBits(
+ width: Int,
+ height: Int
+): Constraints {
+ val safeWidth = minOf(width, BigConstraintValue - 1)
+ val safeHeight = when {
+ safeWidth < TinyConstraintValue -> minOf(height, BigConstraintValue - 1)
+ safeWidth < SmallConstraintValue -> minOf(height, MediumConstraintValue - 1)
+ safeWidth < MediumConstraintValue -> minOf(height, SmallConstraintValue - 1)
+ else -> minOf(height, TinyConstraintValue - 1)
+ }
+ return fixed(safeWidth, safeHeight)
+}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/TextAnnotatedStringNode.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/TextAnnotatedStringNode.kt
index 2852f89..7ea53ff 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/TextAnnotatedStringNode.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/TextAnnotatedStringNode.kt
@@ -405,9 +405,9 @@
// then allow children to measure _inside_ our final box, with the above placeholders
val placeable = measurable.measure(
- Constraints.fixed(
- textLayoutResult.size.width,
- textLayoutResult.size.height
+ Constraints.fixedCoerceHeightAndWidthForBits(
+ width = textLayoutResult.size.width,
+ height = textLayoutResult.size.height
)
)
@@ -416,7 +416,6 @@
textLayoutResult.size.height,
baselineCache!!
) {
- // this is basically a graphicsLayer
placeable.place(0, 0)
}
}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/TextStringSimpleNode.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/TextStringSimpleNode.kt
index 4764b12..b6fbe0e 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/TextStringSimpleNode.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/TextStringSimpleNode.kt
@@ -340,7 +340,7 @@
// then allow children to measure _inside_ our final box, with the above placeholders
val placeable = measurable.measure(
- Constraints.fixed(
+ Constraints.fixedCoerceHeightAndWidthForBits(
layoutSize.width,
layoutSize.height
)
@@ -351,7 +351,6 @@
layoutSize.height,
baselineCache!!
) {
- // this is basically a graphicsLayer
placeable.place(0, 0)
}
}